nex-framework-cli 1.0.9 → 1.0.12
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 +2 -2
- package/package.json +2 -1
- package/src/services/nex-marketplace/NEXMarketplace.js +1247 -1191
|
@@ -1,1191 +1,1247 @@
|
|
|
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 and API URL
|
|
35
|
-
*/
|
|
36
|
-
initializeSupabase() {
|
|
37
|
-
const supabaseUrl = process.env.VITE_SUPABASE_URL
|
|
38
|
-
const supabaseKey = process.env.VITE_SUPABASE_ANON_KEY
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
} else {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if (
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
//
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (await fs.pathExists(
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
.
|
|
772
|
-
|
|
773
|
-
.
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
//
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if (
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
console.log(chalk.
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
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 and API URL
|
|
35
|
+
*/
|
|
36
|
+
initializeSupabase() {
|
|
37
|
+
const supabaseUrl = process.env.VITE_SUPABASE_URL
|
|
38
|
+
const supabaseKey = process.env.VITE_SUPABASE_ANON_KEY
|
|
39
|
+
|
|
40
|
+
// Guardar anon key para usar nos headers da API
|
|
41
|
+
this.anonKey = supabaseKey || null
|
|
42
|
+
|
|
43
|
+
// URL padrão da Edge Function (funciona sem configuração)
|
|
44
|
+
const DEFAULT_API_URL = 'https://auqfubbpxjzuzzqfazdp.supabase.co/functions/v1/nex-marketplace-api'
|
|
45
|
+
|
|
46
|
+
// API URL da Edge Function (preferida - sem expor anon key)
|
|
47
|
+
// Prioridade: 1) NEX_MARKETPLACE_API_URL, 2) Construída a partir de VITE_SUPABASE_URL, 3) Default
|
|
48
|
+
this.apiUrl = process.env.NEX_MARKETPLACE_API_URL ||
|
|
49
|
+
(supabaseUrl ? `${supabaseUrl}/functions/v1/nex-marketplace-api` : DEFAULT_API_URL)
|
|
50
|
+
|
|
51
|
+
// Cliente Supabase (apenas para operações que requerem autenticação)
|
|
52
|
+
if (supabaseUrl && supabaseKey) {
|
|
53
|
+
this.supabase = createClient(supabaseUrl, supabaseKey)
|
|
54
|
+
} else if (this.apiUrl) {
|
|
55
|
+
// Se tiver API URL mas não tiver anon key, ainda funciona para leitura
|
|
56
|
+
// Não mostra mensagem para não poluir o output
|
|
57
|
+
} else {
|
|
58
|
+
console.warn(chalk.yellow('⚠️ Supabase not configured. Marketplace will work in local-only mode.'))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get headers for Edge Function requests
|
|
64
|
+
* Inclui apikey se disponível (requerido pelo gateway do Supabase)
|
|
65
|
+
*/
|
|
66
|
+
getApiHeaders() {
|
|
67
|
+
const headers = {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
'Accept': 'application/json'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Adicionar apikey se disponível (requerido pelo gateway do Supabase)
|
|
73
|
+
// Mesmo com verify_jwt = false, o gateway pode exigir este header
|
|
74
|
+
if (this.anonKey) {
|
|
75
|
+
headers['apikey'] = this.anonKey
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return headers
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load registry configuration
|
|
83
|
+
*/
|
|
84
|
+
async loadConfig() {
|
|
85
|
+
const configPath = path.join(this.registryPath, '.meta', 'registry.yaml')
|
|
86
|
+
|
|
87
|
+
if (await fs.pathExists(configPath)) {
|
|
88
|
+
const configFile = await fs.readFile(configPath, 'utf8')
|
|
89
|
+
this.config = yaml.parse(configFile)
|
|
90
|
+
} else {
|
|
91
|
+
this.config = this.getDefaultConfig()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return this.config
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Default configuration
|
|
99
|
+
*/
|
|
100
|
+
getDefaultConfig() {
|
|
101
|
+
return {
|
|
102
|
+
registry: {
|
|
103
|
+
name: 'NEX Expert Agent Marketplace',
|
|
104
|
+
version: '1.0.0',
|
|
105
|
+
type: 'hybrid'
|
|
106
|
+
},
|
|
107
|
+
defaults: {
|
|
108
|
+
install_location: '.nex-core/agents',
|
|
109
|
+
method: 'symlink',
|
|
110
|
+
backup_before_update: true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
116
|
+
// SEARCH & DISCOVERY
|
|
117
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Search for agents
|
|
121
|
+
*/
|
|
122
|
+
async search(query, options = {}) {
|
|
123
|
+
const spinner = ora(`Searching for "${query}"...`).start()
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
let results = []
|
|
127
|
+
let apiError = null
|
|
128
|
+
|
|
129
|
+
// Try remote search first
|
|
130
|
+
if (this.apiUrl || this.supabase) {
|
|
131
|
+
try {
|
|
132
|
+
results = await this.searchRemote(query, options)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
apiError = error
|
|
135
|
+
// Continue with local search only
|
|
136
|
+
console.warn(chalk.yellow(`\n⚠️ Could not connect to marketplace: ${error.message}`))
|
|
137
|
+
console.log(chalk.gray(' Searching local registry only...\n'))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Always search local registry
|
|
142
|
+
const localResults = await this.searchLocal(query, options)
|
|
143
|
+
|
|
144
|
+
// Merge results (deduplicate by agent_id)
|
|
145
|
+
const merged = this.mergeResults(results, localResults)
|
|
146
|
+
|
|
147
|
+
if (merged.length > 0) {
|
|
148
|
+
spinner.succeed(`Found ${merged.length} agents`)
|
|
149
|
+
} else {
|
|
150
|
+
spinner.warn('No agents found')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Display results
|
|
154
|
+
this.displaySearchResults(merged)
|
|
155
|
+
|
|
156
|
+
// Show warning if API failed
|
|
157
|
+
if (apiError && localResults.length > 0) {
|
|
158
|
+
console.log(chalk.yellow('\n💡 Tip: Some results may be missing. Check your connection.\n'))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return merged
|
|
162
|
+
|
|
163
|
+
} catch (error) {
|
|
164
|
+
spinner.fail('Search failed')
|
|
165
|
+
console.error(chalk.red(`\nError: ${error.message}`))
|
|
166
|
+
console.log(chalk.gray('\n💡 Try searching locally or check your connection.\n'))
|
|
167
|
+
throw error
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Search in Supabase (via Edge Function ou cliente direto)
|
|
173
|
+
*/
|
|
174
|
+
async searchRemote(query, options = {}) {
|
|
175
|
+
// Preferir Edge Function (mais seguro - sem anon key)
|
|
176
|
+
if (this.apiUrl) {
|
|
177
|
+
return await this.searchViaAPI(query, options)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Fallback para cliente direto (se anon key disponível)
|
|
181
|
+
if (this.supabase) {
|
|
182
|
+
return await this.searchViaClient(query, options)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return []
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Search via Edge Function API (SEGURO - sem anon key)
|
|
190
|
+
*/
|
|
191
|
+
async searchViaAPI(query, options = {}) {
|
|
192
|
+
try {
|
|
193
|
+
const params = new URLSearchParams()
|
|
194
|
+
if (query) params.append('q', query)
|
|
195
|
+
if (options.category) params.append('category', options.category)
|
|
196
|
+
if (options.official) params.append('official', 'true')
|
|
197
|
+
if (options.limit) params.append('limit', options.limit.toString())
|
|
198
|
+
|
|
199
|
+
// Timeout de 10 segundos
|
|
200
|
+
const controller = new AbortController()
|
|
201
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000)
|
|
202
|
+
|
|
203
|
+
const response = await fetch(`${this.apiUrl}/search?${params}`, {
|
|
204
|
+
signal: controller.signal,
|
|
205
|
+
headers: this.getApiHeaders()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
clearTimeout(timeoutId)
|
|
209
|
+
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
throw new Error(`API error: ${response.statusText}`)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const { agents } = await response.json()
|
|
215
|
+
return agents || []
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error.name === 'AbortError') {
|
|
218
|
+
throw new Error('Connection timeout. The marketplace API is not responding.')
|
|
219
|
+
}
|
|
220
|
+
if (error.code === 'UND_ERR_CONNECT_TIMEOUT' || error.message.includes('timeout')) {
|
|
221
|
+
throw new Error('Connection timeout. Please check your internet connection or try again later.')
|
|
222
|
+
}
|
|
223
|
+
throw error
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* List all available agents (browse marketplace)
|
|
229
|
+
*/
|
|
230
|
+
async listAll(options = {}) {
|
|
231
|
+
const spinner = ora('Loading available agents...').start()
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
let results = []
|
|
235
|
+
let apiError = null
|
|
236
|
+
|
|
237
|
+
// Try API/Remote first
|
|
238
|
+
if (this.apiUrl) {
|
|
239
|
+
try {
|
|
240
|
+
results = await this.listAllViaAPI(options)
|
|
241
|
+
} catch (error) {
|
|
242
|
+
apiError = error
|
|
243
|
+
console.warn(chalk.yellow(`\n⚠️ Could not connect to marketplace API: ${error.message}`))
|
|
244
|
+
console.log(chalk.gray(' Falling back to local registry...\n'))
|
|
245
|
+
|
|
246
|
+
// Try Supabase client as fallback
|
|
247
|
+
if (this.supabase) {
|
|
248
|
+
try {
|
|
249
|
+
results = await this.listAllViaClient(options)
|
|
250
|
+
} catch (clientError) {
|
|
251
|
+
// If both fail, continue with local only
|
|
252
|
+
results = []
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} else if (this.supabase) {
|
|
257
|
+
try {
|
|
258
|
+
results = await this.listAllViaClient(options)
|
|
259
|
+
} catch (error) {
|
|
260
|
+
apiError = error
|
|
261
|
+
results = []
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Always include local registry
|
|
266
|
+
const localResults = await this.searchLocal('', options)
|
|
267
|
+
|
|
268
|
+
// Merge results
|
|
269
|
+
const merged = this.mergeResults(results, localResults)
|
|
270
|
+
|
|
271
|
+
if (merged.length > 0) {
|
|
272
|
+
spinner.succeed(`Found ${merged.length} available agents`)
|
|
273
|
+
} else if (apiError && localResults.length === 0) {
|
|
274
|
+
spinner.warn('No agents found (API unavailable and no local registry)')
|
|
275
|
+
} else {
|
|
276
|
+
spinner.warn('No agents found')
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Display results
|
|
280
|
+
this.displaySearchResults(merged)
|
|
281
|
+
|
|
282
|
+
// Show helpful messages
|
|
283
|
+
if (apiError) {
|
|
284
|
+
if (localResults.length > 0) {
|
|
285
|
+
console.log(chalk.yellow('\n💡 Tip: Some agents may be missing. Check your connection or configure NEX_MARKETPLACE_API_URL\n'))
|
|
286
|
+
} else {
|
|
287
|
+
console.log(chalk.yellow('\n💡 Troubleshooting:'))
|
|
288
|
+
console.log(chalk.gray(' • The marketplace API is not responding'))
|
|
289
|
+
console.log(chalk.gray(' • No local registry found in this project'))
|
|
290
|
+
console.log(chalk.gray(' • Try: nex agent list (shows only installed agents)'))
|
|
291
|
+
console.log(chalk.gray(' • Or configure: export NEX_MARKETPLACE_API_URL=<your-url>'))
|
|
292
|
+
console.log(chalk.gray(' • Check your internet connection and try again later\n'))
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return merged
|
|
297
|
+
|
|
298
|
+
} catch (error) {
|
|
299
|
+
spinner.fail('Failed to load agents')
|
|
300
|
+
console.error(chalk.red(`\nError: ${error.message}`))
|
|
301
|
+
console.log(chalk.gray('\n💡 Troubleshooting:'))
|
|
302
|
+
console.log(chalk.gray(' 1. Check your internet connection'))
|
|
303
|
+
console.log(chalk.gray(' 2. Verify the API URL: ' + (this.apiUrl || 'not configured')))
|
|
304
|
+
console.log(chalk.gray(' 3. Try: nex agent list (shows only installed agents)'))
|
|
305
|
+
console.log(chalk.gray(' 4. Configure custom API: export NEX_MARKETPLACE_API_URL=<your-url>\n'))
|
|
306
|
+
throw error
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* List all agents via Edge Function API
|
|
312
|
+
*/
|
|
313
|
+
async listAllViaAPI(options = {}) {
|
|
314
|
+
try {
|
|
315
|
+
// Timeout de 10 segundos
|
|
316
|
+
const controller = new AbortController()
|
|
317
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000)
|
|
318
|
+
|
|
319
|
+
const response = await fetch(`${this.apiUrl}/list`, {
|
|
320
|
+
signal: controller.signal,
|
|
321
|
+
headers: this.getApiHeaders()
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
clearTimeout(timeoutId)
|
|
325
|
+
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(`API error: ${response.statusText}`)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const { agents } = await response.json()
|
|
331
|
+
return agents || []
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (error.name === 'AbortError') {
|
|
334
|
+
throw new Error('Connection timeout. The marketplace API is not responding.')
|
|
335
|
+
}
|
|
336
|
+
if (error.code === 'UND_ERR_CONNECT_TIMEOUT' || error.message.includes('timeout')) {
|
|
337
|
+
throw new Error('Connection timeout. Please check your internet connection or try again later.')
|
|
338
|
+
}
|
|
339
|
+
throw error
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* List all agents via Supabase client (fallback)
|
|
345
|
+
*/
|
|
346
|
+
async listAllViaClient(options = {}) {
|
|
347
|
+
let query = this.supabase
|
|
348
|
+
.from('nex_marketplace_agents')
|
|
349
|
+
.select('*')
|
|
350
|
+
.eq('is_active', true)
|
|
351
|
+
.order('total_installs', { ascending: false })
|
|
352
|
+
|
|
353
|
+
if (options.category) {
|
|
354
|
+
query = query.eq('category', options.category)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (options.official) {
|
|
358
|
+
query = query.eq('is_official', true)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const limit = options.limit || 100
|
|
362
|
+
query = query.limit(limit)
|
|
363
|
+
|
|
364
|
+
const { data, error } = await query
|
|
365
|
+
|
|
366
|
+
if (error) throw error
|
|
367
|
+
|
|
368
|
+
return data || []
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Search via Supabase client (fallback - requer anon key)
|
|
373
|
+
*/
|
|
374
|
+
async searchViaClient(query, options = {}) {
|
|
375
|
+
let dbQuery = this.supabase
|
|
376
|
+
.from('nex_marketplace_agents')
|
|
377
|
+
.select('*')
|
|
378
|
+
.eq('is_active', true)
|
|
379
|
+
|
|
380
|
+
// Text search in name, description, tags
|
|
381
|
+
if (query) {
|
|
382
|
+
dbQuery = dbQuery.or(`name.ilike.%${query}%,description.ilike.%${query}%,tags.cs.{${query}}`)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Filters
|
|
386
|
+
if (options.category) {
|
|
387
|
+
dbQuery = dbQuery.eq('category', options.category)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (options.official) {
|
|
391
|
+
dbQuery = dbQuery.eq('is_official', true)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Ordering
|
|
395
|
+
const orderBy = options.sort || 'total_installs'
|
|
396
|
+
dbQuery = dbQuery.order(orderBy, { ascending: false })
|
|
397
|
+
|
|
398
|
+
// Limit
|
|
399
|
+
const limit = options.limit || 50
|
|
400
|
+
dbQuery = dbQuery.limit(limit)
|
|
401
|
+
|
|
402
|
+
const { data, error } = await dbQuery
|
|
403
|
+
|
|
404
|
+
if (error) throw error
|
|
405
|
+
|
|
406
|
+
return data || []
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Search in local registry
|
|
411
|
+
*/
|
|
412
|
+
async searchLocal(query, options = {}) {
|
|
413
|
+
const results = []
|
|
414
|
+
|
|
415
|
+
// Check if registry path exists
|
|
416
|
+
if (!await fs.pathExists(this.registryPath)) {
|
|
417
|
+
return results
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const categories = ['planning', 'execution', 'community']
|
|
421
|
+
|
|
422
|
+
for (const category of categories) {
|
|
423
|
+
const categoryPath = path.join(this.registryPath, category)
|
|
424
|
+
|
|
425
|
+
if (!await fs.pathExists(categoryPath)) continue
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const agents = await fs.readdir(categoryPath)
|
|
429
|
+
|
|
430
|
+
for (const agentId of agents) {
|
|
431
|
+
const manifestPath = path.join(categoryPath, agentId, 'manifest.yaml')
|
|
432
|
+
|
|
433
|
+
if (!await fs.pathExists(manifestPath)) continue
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const manifestFile = await fs.readFile(manifestPath, 'utf8')
|
|
437
|
+
const manifest = yaml.parse(manifestFile)
|
|
438
|
+
|
|
439
|
+
// Filter by query
|
|
440
|
+
if (query) {
|
|
441
|
+
const searchable = [
|
|
442
|
+
manifest.name,
|
|
443
|
+
manifest.description,
|
|
444
|
+
manifest.tagline,
|
|
445
|
+
...(manifest.tags || [])
|
|
446
|
+
].join(' ').toLowerCase()
|
|
447
|
+
|
|
448
|
+
if (!searchable.includes(query.toLowerCase())) {
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Filter by category
|
|
454
|
+
if (options.category && manifest.category !== options.category) {
|
|
455
|
+
continue
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
results.push({
|
|
459
|
+
agent_id: agentId,
|
|
460
|
+
...manifest
|
|
461
|
+
})
|
|
462
|
+
} catch (error) {
|
|
463
|
+
// Skip invalid manifests
|
|
464
|
+
continue
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} catch (error) {
|
|
468
|
+
// Skip categories that can't be read
|
|
469
|
+
continue
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return results
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Merge and deduplicate results
|
|
478
|
+
*/
|
|
479
|
+
mergeResults(remote, local) {
|
|
480
|
+
const map = new Map()
|
|
481
|
+
|
|
482
|
+
// Add remote results first (they have stats)
|
|
483
|
+
remote.forEach(agent => {
|
|
484
|
+
map.set(agent.agent_id, { ...agent, source: 'remote' })
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
// Add local results if not already present
|
|
488
|
+
local.forEach(agent => {
|
|
489
|
+
if (!map.has(agent.id)) {
|
|
490
|
+
map.set(agent.id, {
|
|
491
|
+
agent_id: agent.id,
|
|
492
|
+
...agent,
|
|
493
|
+
source: 'local'
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
return Array.from(map.values())
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Display search results
|
|
503
|
+
*/
|
|
504
|
+
displaySearchResults(results) {
|
|
505
|
+
if (results.length === 0) {
|
|
506
|
+
console.log(chalk.yellow('\n😕 No agents found matching your search.\n'))
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
console.log('\n')
|
|
511
|
+
|
|
512
|
+
results.forEach(agent => {
|
|
513
|
+
const icon = agent.icon || '🤖'
|
|
514
|
+
const name = agent.name
|
|
515
|
+
const version = agent.current_version || agent.version
|
|
516
|
+
const tagline = agent.tagline || agent.description?.substring(0, 60) + '...'
|
|
517
|
+
|
|
518
|
+
console.log(chalk.bold.cyan(`${icon} ${name} v${version}`))
|
|
519
|
+
console.log(chalk.gray(` ${tagline}`))
|
|
520
|
+
|
|
521
|
+
if (agent.total_installs || agent.stats?.installs) {
|
|
522
|
+
const installs = agent.total_installs || agent.stats?.installs || 0
|
|
523
|
+
const rating = agent.average_rating || agent.stats?.rating || 0
|
|
524
|
+
console.log(chalk.gray(` ${installs} installs • ⭐ ${rating}/5`))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (agent.tags && agent.tags.length > 0) {
|
|
528
|
+
const tags = Array.isArray(agent.tags) ? agent.tags : Object.values(agent.tags)
|
|
529
|
+
console.log(chalk.gray(` Tags: ${tags.slice(0, 5).join(', ')}`))
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
console.log(chalk.dim(` ${chalk.cyan('nex agent install')} ${agent.agent_id || agent.id}`))
|
|
533
|
+
console.log()
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
538
|
+
// AGENT INFO
|
|
539
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Get detailed agent information
|
|
543
|
+
*/
|
|
544
|
+
async info(agentId) {
|
|
545
|
+
const spinner = ora(`Loading info for ${agentId}...`).start()
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
// Try to load from API/Remote first (prefer API)
|
|
549
|
+
let agent = null
|
|
550
|
+
|
|
551
|
+
if (this.apiUrl) {
|
|
552
|
+
try {
|
|
553
|
+
const response = await fetch(`${this.apiUrl}/info/${agentId}`, {
|
|
554
|
+
headers: this.getApiHeaders()
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
if (response.ok) {
|
|
558
|
+
const { agent: apiAgent } = await response.json()
|
|
559
|
+
agent = apiAgent
|
|
560
|
+
}
|
|
561
|
+
} catch (error) {
|
|
562
|
+
// Silently fallback
|
|
563
|
+
}
|
|
564
|
+
} else if (this.supabase) {
|
|
565
|
+
const { data } = await this.supabase
|
|
566
|
+
.from('nex_marketplace_agents')
|
|
567
|
+
.select('*')
|
|
568
|
+
.eq('agent_id', agentId)
|
|
569
|
+
.single()
|
|
570
|
+
|
|
571
|
+
agent = data
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Fallback to local
|
|
575
|
+
if (!agent) {
|
|
576
|
+
agent = await this.loadLocalManifest(agentId)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (!agent) {
|
|
580
|
+
spinner.fail(`Agent ${agentId} not found`)
|
|
581
|
+
return null
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
spinner.succeed(`Loaded info for ${agentId}`)
|
|
585
|
+
|
|
586
|
+
// Display detailed info
|
|
587
|
+
this.displayAgentInfo(agent)
|
|
588
|
+
|
|
589
|
+
return agent
|
|
590
|
+
|
|
591
|
+
} catch (error) {
|
|
592
|
+
spinner.fail('Failed to load agent info')
|
|
593
|
+
console.error(chalk.red(`Error: ${error.message}`))
|
|
594
|
+
throw error
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Load manifest from local registry
|
|
600
|
+
*/
|
|
601
|
+
async loadLocalManifest(agentId) {
|
|
602
|
+
const categories = ['planning', 'execution', 'community']
|
|
603
|
+
|
|
604
|
+
for (const category of categories) {
|
|
605
|
+
const manifestPath = path.join(this.registryPath, category, agentId, 'manifest.yaml')
|
|
606
|
+
|
|
607
|
+
if (await fs.pathExists(manifestPath)) {
|
|
608
|
+
const manifestFile = await fs.readFile(manifestPath, 'utf8')
|
|
609
|
+
const manifest = yaml.parse(manifestFile)
|
|
610
|
+
return { agent_id: agentId, ...manifest }
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return null
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Display detailed agent information
|
|
619
|
+
*/
|
|
620
|
+
displayAgentInfo(agent) {
|
|
621
|
+
const icon = agent.icon || '🤖'
|
|
622
|
+
const name = agent.name
|
|
623
|
+
const version = agent.current_version || agent.version
|
|
624
|
+
|
|
625
|
+
console.log('\n' + chalk.bold.cyan('═'.repeat(60)))
|
|
626
|
+
console.log(chalk.bold.cyan(`${icon} ${name} v${version}`))
|
|
627
|
+
console.log(chalk.bold.cyan('═'.repeat(60)))
|
|
628
|
+
|
|
629
|
+
if (agent.tagline) {
|
|
630
|
+
console.log(chalk.bold('\n💫 Tagline:'))
|
|
631
|
+
console.log(chalk.gray(` ${agent.tagline}`))
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
console.log(chalk.bold('\n📝 Description:'))
|
|
635
|
+
console.log(chalk.gray(` ${agent.description || agent.long_description || 'N/A'}`))
|
|
636
|
+
|
|
637
|
+
if (agent.author_name || agent.author?.name) {
|
|
638
|
+
console.log(chalk.bold('\n👤 Author:'))
|
|
639
|
+
const authorName = agent.author_name || agent.author.name
|
|
640
|
+
const authorEmail = agent.author_email || agent.author?.email
|
|
641
|
+
console.log(chalk.gray(` ${authorName}${authorEmail ? ` <${authorEmail}>` : ''}`))
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (agent.tags && agent.tags.length > 0) {
|
|
645
|
+
console.log(chalk.bold('\n🏷️ Tags:'))
|
|
646
|
+
const tags = Array.isArray(agent.tags) ? agent.tags : Object.values(agent.tags)
|
|
647
|
+
console.log(chalk.gray(` ${tags.join(', ')}`))
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (agent.capabilities) {
|
|
651
|
+
console.log(chalk.bold('\n⚡ Capabilities:'))
|
|
652
|
+
const capabilities = Array.isArray(agent.capabilities)
|
|
653
|
+
? agent.capabilities
|
|
654
|
+
: Object.values(agent.capabilities)
|
|
655
|
+
capabilities.slice(0, 10).forEach(cap => {
|
|
656
|
+
console.log(chalk.gray(` • ${cap}`))
|
|
657
|
+
})
|
|
658
|
+
if (capabilities.length > 10) {
|
|
659
|
+
console.log(chalk.gray(` ... and ${capabilities.length - 10} more`))
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (agent.total_installs !== undefined) {
|
|
664
|
+
console.log(chalk.bold('\n📊 Stats:'))
|
|
665
|
+
console.log(chalk.gray(` Installs: ${agent.total_installs}`))
|
|
666
|
+
console.log(chalk.gray(` Rating: ${'⭐'.repeat(Math.floor(agent.average_rating || 0))} (${agent.average_rating || 0}/5)`))
|
|
667
|
+
if (agent.total_stars) {
|
|
668
|
+
console.log(chalk.gray(` Stars: ${agent.total_stars}`))
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (agent.dependencies?.agents && agent.dependencies.agents.length > 0) {
|
|
673
|
+
console.log(chalk.bold('\n🔗 Dependencies:'))
|
|
674
|
+
agent.dependencies.agents.forEach(dep => {
|
|
675
|
+
console.log(chalk.gray(` • ${dep}`))
|
|
676
|
+
})
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
console.log(chalk.bold('\n🔧 Installation:'))
|
|
680
|
+
console.log(chalk.cyan(` nex agent install ${agent.agent_id || agent.id}`))
|
|
681
|
+
|
|
682
|
+
if (agent.repository_url || agent.links?.repository) {
|
|
683
|
+
console.log(chalk.bold('\n🌐 Links:'))
|
|
684
|
+
const repoUrl = agent.repository_url || agent.links?.repository
|
|
685
|
+
console.log(chalk.gray(` Repository: ${repoUrl}`))
|
|
686
|
+
|
|
687
|
+
if (agent.documentation_url || agent.links?.documentation) {
|
|
688
|
+
const docUrl = agent.documentation_url || agent.links?.documentation
|
|
689
|
+
console.log(chalk.gray(` Documentation: ${docUrl}`))
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
console.log('\n' + chalk.bold.cyan('═'.repeat(60)) + '\n')
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
697
|
+
// INSTALLATION
|
|
698
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Install an agent
|
|
702
|
+
*/
|
|
703
|
+
async install(agentId, options = {}) {
|
|
704
|
+
const spinner = ora(`Installing ${agentId}...`).start()
|
|
705
|
+
|
|
706
|
+
try {
|
|
707
|
+
// 1. Check if agent is registered in NEX Hub (if Supabase is configured)
|
|
708
|
+
if (this.supabase && !options.skipRegistrationCheck) {
|
|
709
|
+
spinner.text = 'Checking registration...'
|
|
710
|
+
|
|
711
|
+
const { data: { user } } = await this.supabase.auth.getUser()
|
|
712
|
+
|
|
713
|
+
if (user) {
|
|
714
|
+
// User is authenticated, check registration
|
|
715
|
+
const isRegistered = await this.checkAgentRegistration(agentId)
|
|
716
|
+
|
|
717
|
+
if (!isRegistered) {
|
|
718
|
+
spinner.fail('Agent not registered')
|
|
719
|
+
console.log(chalk.yellow(`\n⚠️ Agent "${agentId}" is not registered in your NEX Hub account.\n`))
|
|
720
|
+
console.log(chalk.cyan('💡 Register it first:'))
|
|
721
|
+
console.log(chalk.cyan(` nex agent register ${agentId}\n`))
|
|
722
|
+
console.log(chalk.gray(' Or browse available agents:'))
|
|
723
|
+
console.log(chalk.gray(` nex agent search ${agentId}\n`))
|
|
724
|
+
return false
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
spinner.text = `Installing ${agentId}...`
|
|
728
|
+
} else {
|
|
729
|
+
// User not authenticated, show warning but allow local install
|
|
730
|
+
spinner.warn('Not authenticated - installing locally only')
|
|
731
|
+
console.log(chalk.yellow('\n⚠️ You are not logged in to NEX Hub.'))
|
|
732
|
+
console.log(chalk.gray(' This agent will be installed locally only.\n'))
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// 2. Resolve version
|
|
737
|
+
const version = options.version || await this.getLatestVersion(agentId)
|
|
738
|
+
spinner.text = `Installing ${agentId}@${version}...`
|
|
739
|
+
|
|
740
|
+
// 3. Load agent manifest
|
|
741
|
+
const manifest = await this.loadLocalManifest(agentId)
|
|
742
|
+
|
|
743
|
+
if (!manifest) {
|
|
744
|
+
spinner.fail(`Agent ${agentId} not found in registry`)
|
|
745
|
+
return false
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// 4. Check dependencies
|
|
749
|
+
spinner.text = 'Checking dependencies...'
|
|
750
|
+
await this.checkDependencies(manifest)
|
|
751
|
+
|
|
752
|
+
// 5. Determine install location
|
|
753
|
+
const installPath = path.join(this.installPath, agentId)
|
|
754
|
+
|
|
755
|
+
// 5. Check if already installed
|
|
756
|
+
if (await fs.pathExists(installPath)) {
|
|
757
|
+
spinner.info(`Agent ${agentId} already installed`)
|
|
758
|
+
|
|
759
|
+
// Ask user if they want to update
|
|
760
|
+
return await this.update(agentId, options)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// 6. Install (symlink or copy)
|
|
764
|
+
const method = options.method || this.config.defaults.method || 'symlink'
|
|
765
|
+
const sourcePath = await this.getAgentSourcePath(agentId, version)
|
|
766
|
+
|
|
767
|
+
spinner.text = `Installing ${agentId} (${method})...`
|
|
768
|
+
|
|
769
|
+
if (method === 'symlink') {
|
|
770
|
+
await fs.ensureDir(path.dirname(installPath))
|
|
771
|
+
await fs.ensureSymlink(sourcePath, installPath)
|
|
772
|
+
} else {
|
|
773
|
+
await fs.copy(sourcePath, installPath)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// 7. Track installation
|
|
777
|
+
await this.trackInstallation(agentId, version, method)
|
|
778
|
+
|
|
779
|
+
spinner.succeed(chalk.green(`✅ ${manifest.icon} ${manifest.name} v${version} installed!`))
|
|
780
|
+
|
|
781
|
+
// 8. Show quick start
|
|
782
|
+
console.log(chalk.cyan('\n📖 Quick Start:'))
|
|
783
|
+
console.log(chalk.gray(` @${agentId}`))
|
|
784
|
+
console.log(chalk.gray(` nex agent run ${agentId} <command>\n`))
|
|
785
|
+
|
|
786
|
+
return true
|
|
787
|
+
|
|
788
|
+
} catch (error) {
|
|
789
|
+
spinner.fail(chalk.red(`Failed to install ${agentId}`))
|
|
790
|
+
console.error(chalk.red(`Error: ${error.message}`))
|
|
791
|
+
throw error
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Get agent source path
|
|
797
|
+
*/
|
|
798
|
+
async getAgentSourcePath(agentId, version) {
|
|
799
|
+
const categories = ['planning', 'execution', 'community']
|
|
800
|
+
|
|
801
|
+
for (const category of categories) {
|
|
802
|
+
// Try versioned path first
|
|
803
|
+
let sourcePath = path.join(this.registryPath, category, agentId, 'versions', version)
|
|
804
|
+
|
|
805
|
+
if (await fs.pathExists(sourcePath)) {
|
|
806
|
+
return sourcePath
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Fallback to non-versioned
|
|
810
|
+
sourcePath = path.join(this.registryPath, category, agentId)
|
|
811
|
+
|
|
812
|
+
if (await fs.pathExists(sourcePath)) {
|
|
813
|
+
return sourcePath
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
throw new Error(`Agent source not found: ${agentId}@${version}`)
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Get latest version of agent
|
|
822
|
+
*/
|
|
823
|
+
async getLatestVersion(agentId) {
|
|
824
|
+
// Try Supabase first
|
|
825
|
+
if (this.supabase) {
|
|
826
|
+
const { data } = await this.supabase
|
|
827
|
+
.from('nex_marketplace_versions')
|
|
828
|
+
.select('version')
|
|
829
|
+
.eq('agent_id', agentId)
|
|
830
|
+
.eq('is_latest', true)
|
|
831
|
+
.single()
|
|
832
|
+
|
|
833
|
+
if (data) return data.version
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Fallback to manifest
|
|
837
|
+
const manifest = await this.loadLocalManifest(agentId)
|
|
838
|
+
return manifest?.version || manifest?.current_version || '1.0.0'
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Check agent dependencies
|
|
843
|
+
*/
|
|
844
|
+
async checkDependencies(manifest) {
|
|
845
|
+
if (!manifest.dependencies || !manifest.dependencies.agents) {
|
|
846
|
+
return true
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const missing = []
|
|
850
|
+
|
|
851
|
+
for (const depId of manifest.dependencies.agents) {
|
|
852
|
+
const depPath = path.join(this.installPath, depId)
|
|
853
|
+
|
|
854
|
+
if (!await fs.pathExists(depPath)) {
|
|
855
|
+
missing.push(depId)
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (missing.length > 0) {
|
|
860
|
+
console.log(chalk.yellow(`\n⚠️ Missing dependencies: ${missing.join(', ')}`))
|
|
861
|
+
console.log(chalk.gray('These agents will be installed automatically.\n'))
|
|
862
|
+
|
|
863
|
+
// Auto-install dependencies
|
|
864
|
+
for (const depId of missing) {
|
|
865
|
+
await this.install(depId)
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return true
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Track installation
|
|
874
|
+
*/
|
|
875
|
+
async trackInstallation(agentId, version, method) {
|
|
876
|
+
// Save to local registry
|
|
877
|
+
const installedFile = path.join(this.installPath, 'installed.json')
|
|
878
|
+
await fs.ensureDir(this.installPath)
|
|
879
|
+
|
|
880
|
+
let installed = {}
|
|
881
|
+
if (await fs.pathExists(installedFile)) {
|
|
882
|
+
installed = await fs.readJSON(installedFile)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (!installed.agents) {
|
|
886
|
+
installed.agents = []
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Remove old entry if exists
|
|
890
|
+
installed.agents = installed.agents.filter(a => a.id !== agentId)
|
|
891
|
+
|
|
892
|
+
// Add new entry
|
|
893
|
+
installed.agents.push({
|
|
894
|
+
id: agentId,
|
|
895
|
+
version: version,
|
|
896
|
+
method: method,
|
|
897
|
+
installed_at: new Date().toISOString(),
|
|
898
|
+
project_path: this.projectRoot
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
await fs.writeJSON(installedFile, installed, { spaces: 2 })
|
|
902
|
+
|
|
903
|
+
// Track in Supabase if available
|
|
904
|
+
if (this.supabase) {
|
|
905
|
+
await this.supabase
|
|
906
|
+
.from('nex_marketplace_installs')
|
|
907
|
+
.upsert({
|
|
908
|
+
agent_id: agentId,
|
|
909
|
+
version: version,
|
|
910
|
+
install_method: method,
|
|
911
|
+
project_path: this.projectRoot,
|
|
912
|
+
project_name: path.basename(this.projectRoot),
|
|
913
|
+
is_active: true
|
|
914
|
+
})
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
919
|
+
// LIST INSTALLED
|
|
920
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* List installed agents
|
|
924
|
+
*/
|
|
925
|
+
async list(options = {}) {
|
|
926
|
+
const installedFile = path.join(this.installPath, 'installed.json')
|
|
927
|
+
|
|
928
|
+
if (!await fs.pathExists(installedFile)) {
|
|
929
|
+
console.log(chalk.yellow('📭 No agents installed in this project.'))
|
|
930
|
+
console.log(chalk.gray('\nInstall agents with: nex agent install <agent-id>\n'))
|
|
931
|
+
return []
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const installed = await fs.readJSON(installedFile)
|
|
935
|
+
const agents = installed.agents || []
|
|
936
|
+
|
|
937
|
+
if (agents.length === 0) {
|
|
938
|
+
console.log(chalk.yellow('📭 No agents installed in this project.\n'))
|
|
939
|
+
return []
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
console.log(chalk.bold.cyan(`\n📦 Installed Agents (${agents.length})`))
|
|
943
|
+
console.log(chalk.gray(`Project: ${path.basename(this.projectRoot)}\n`))
|
|
944
|
+
|
|
945
|
+
for (const agent of agents) {
|
|
946
|
+
const manifest = await this.loadLocalManifest(agent.id)
|
|
947
|
+
|
|
948
|
+
if (manifest) {
|
|
949
|
+
const icon = manifest.icon || '🤖'
|
|
950
|
+
console.log(chalk.bold(`${icon} ${manifest.name || agent.id}`))
|
|
951
|
+
} else {
|
|
952
|
+
console.log(chalk.bold(`🤖 ${agent.id}`))
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
console.log(chalk.gray(` Version: ${agent.version}`))
|
|
956
|
+
console.log(chalk.gray(` Method: ${agent.method}`))
|
|
957
|
+
console.log(chalk.gray(` Installed: ${new Date(agent.installed_at).toLocaleDateString()}`))
|
|
958
|
+
console.log()
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return agents
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Uninstall agent
|
|
966
|
+
*/
|
|
967
|
+
async uninstall(agentId) {
|
|
968
|
+
const spinner = ora(`Uninstalling ${agentId}...`).start()
|
|
969
|
+
|
|
970
|
+
try {
|
|
971
|
+
const installPath = path.join(this.installPath, agentId)
|
|
972
|
+
|
|
973
|
+
if (!await fs.pathExists(installPath)) {
|
|
974
|
+
spinner.fail(`Agent ${agentId} is not installed`)
|
|
975
|
+
return false
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Remove files
|
|
979
|
+
await fs.remove(installPath)
|
|
980
|
+
|
|
981
|
+
// Update installed.json
|
|
982
|
+
const installedFile = path.join(this.installPath, 'installed.json')
|
|
983
|
+
const installed = await fs.readJSON(installedFile)
|
|
984
|
+
installed.agents = (installed.agents || []).filter(a => a.id !== agentId)
|
|
985
|
+
await fs.writeJSON(installedFile, installed, { spaces: 2 })
|
|
986
|
+
|
|
987
|
+
// Update Supabase
|
|
988
|
+
if (this.supabase) {
|
|
989
|
+
await this.supabase
|
|
990
|
+
.from('nex_marketplace_installs')
|
|
991
|
+
.update({ is_active: false, uninstalled_at: new Date().toISOString() })
|
|
992
|
+
.eq('agent_id', agentId)
|
|
993
|
+
.eq('project_path', this.projectRoot)
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
spinner.succeed(chalk.green(`✅ Agent ${agentId} uninstalled`))
|
|
997
|
+
return true
|
|
998
|
+
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
spinner.fail(`Failed to uninstall ${agentId}`)
|
|
1001
|
+
console.error(chalk.red(`Error: ${error.message}`))
|
|
1002
|
+
throw error
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Update agent
|
|
1008
|
+
*/
|
|
1009
|
+
async update(agentId, options = {}) {
|
|
1010
|
+
console.log(chalk.blue(`🔄 Updating ${agentId}...`))
|
|
1011
|
+
|
|
1012
|
+
// Uninstall old version
|
|
1013
|
+
await this.uninstall(agentId)
|
|
1014
|
+
|
|
1015
|
+
// Install new version
|
|
1016
|
+
return await this.install(agentId, options)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1020
|
+
// AGENT REGISTRATION (HUB REGISTRY SYSTEM)
|
|
1021
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Register an agent in the NEX Hub
|
|
1025
|
+
*/
|
|
1026
|
+
async register(agentId) {
|
|
1027
|
+
const spinner = ora(`Registering ${agentId} in NEX Hub...`).start()
|
|
1028
|
+
|
|
1029
|
+
try {
|
|
1030
|
+
// 1. Check if Supabase is configured
|
|
1031
|
+
if (!this.supabase) {
|
|
1032
|
+
spinner.fail('Supabase not configured')
|
|
1033
|
+
console.log(chalk.yellow('\n⚠️ NEX Hub requires Supabase configuration.'))
|
|
1034
|
+
console.log(chalk.cyan('💡 Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY environment variables.\n'))
|
|
1035
|
+
return false
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// 2. Check if user is authenticated
|
|
1039
|
+
const { data: { user }, error: authError } = await this.supabase.auth.getUser()
|
|
1040
|
+
|
|
1041
|
+
if (authError || !user) {
|
|
1042
|
+
spinner.fail('Not authenticated')
|
|
1043
|
+
console.log(chalk.yellow('\n⚠️ You need to be logged in to register agents.'))
|
|
1044
|
+
console.log(chalk.cyan('💡 Run: nex auth login\n'))
|
|
1045
|
+
return false
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// 3. Get agent info from database
|
|
1049
|
+
const { data: agent, error: agentError } = await this.supabase
|
|
1050
|
+
.from('nex_marketplace_agents')
|
|
1051
|
+
.select('agent_id, name, agent_type, icon')
|
|
1052
|
+
.eq('agent_id', agentId)
|
|
1053
|
+
.single()
|
|
1054
|
+
|
|
1055
|
+
if (agentError || !agent) {
|
|
1056
|
+
spinner.fail(`Agent ${agentId} not found`)
|
|
1057
|
+
console.log(chalk.yellow(`\n⚠️ Agent "${agentId}" not found in NEX Hub.`))
|
|
1058
|
+
console.log(chalk.cyan(`💡 Search for agents: nex agent search ${agentId}\n`))
|
|
1059
|
+
return false
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// 4. Call Edge Function to register
|
|
1063
|
+
const EDGE_FUNCTION_URL = process.env.VITE_SUPABASE_URL?.replace('.supabase.co', '.supabase.co/functions/v1')
|
|
1064
|
+
const { data: session } = await this.supabase.auth.getSession()
|
|
1065
|
+
|
|
1066
|
+
const response = await fetch(`${EDGE_FUNCTION_URL}/nex-agent-registry/register`, {
|
|
1067
|
+
method: 'POST',
|
|
1068
|
+
headers: {
|
|
1069
|
+
'Content-Type': 'application/json',
|
|
1070
|
+
'Authorization': `Bearer ${session.session?.access_token}`,
|
|
1071
|
+
'apikey': process.env.VITE_SUPABASE_ANON_KEY
|
|
1072
|
+
},
|
|
1073
|
+
body: JSON.stringify({ agent_id: agentId })
|
|
1074
|
+
})
|
|
1075
|
+
|
|
1076
|
+
const result = await response.json()
|
|
1077
|
+
|
|
1078
|
+
if (!response.ok) {
|
|
1079
|
+
spinner.fail('Registration failed')
|
|
1080
|
+
|
|
1081
|
+
if (result.details?.reason === 'limit_reached') {
|
|
1082
|
+
console.log(chalk.red(`\n❌ ${result.error}\n`))
|
|
1083
|
+
console.log(chalk.yellow(`📊 Your Plan: ${result.details.current_plan}`))
|
|
1084
|
+
console.log(chalk.yellow(` Agent Type: ${result.details.agent_type}`))
|
|
1085
|
+
console.log(chalk.yellow(` Current: ${result.details.current_count}/${result.details.max_allowed}\n`))
|
|
1086
|
+
|
|
1087
|
+
if (result.details.upgrade_required) {
|
|
1088
|
+
console.log(chalk.cyan(`💡 Upgrade to ${result.details.upgrade_required} plan to register more agents.`))
|
|
1089
|
+
console.log(chalk.cyan(` Visit: https://nexhub.dev/pricing\n`))
|
|
1090
|
+
}
|
|
1091
|
+
} else if (result.details?.reason === 'already_registered') {
|
|
1092
|
+
spinner.info('Already registered')
|
|
1093
|
+
console.log(chalk.blue(`\nℹ️ Agent "${agent.name}" is already registered.\n`))
|
|
1094
|
+
return true
|
|
1095
|
+
} else {
|
|
1096
|
+
console.log(chalk.red(`\n❌ ${result.error}\n`))
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return false
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// 5. Success!
|
|
1103
|
+
spinner.succeed(chalk.green(`✅ Agent registered successfully!`))
|
|
1104
|
+
console.log(chalk.cyan(`\n${agent.icon || '🤖'} ${agent.name} (${agentId})`))
|
|
1105
|
+
console.log(chalk.gray(` Type: ${agent.agent_type}`))
|
|
1106
|
+
console.log(chalk.gray(` Registration ID: ${result.registration?.registration_id}\n`))
|
|
1107
|
+
console.log(chalk.green(`✨ You can now install this agent:`))
|
|
1108
|
+
console.log(chalk.cyan(` nex agent install ${agentId}\n`))
|
|
1109
|
+
|
|
1110
|
+
return true
|
|
1111
|
+
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
spinner.fail('Registration failed')
|
|
1114
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}\n`))
|
|
1115
|
+
return false
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* List registered agents from NEX Hub
|
|
1121
|
+
*/
|
|
1122
|
+
async listRegisteredAgents() {
|
|
1123
|
+
const spinner = ora('Loading your registered agents...').start()
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
// 1. Check if Supabase is configured
|
|
1127
|
+
if (!this.supabase) {
|
|
1128
|
+
spinner.fail('Supabase not configured')
|
|
1129
|
+
console.log(chalk.yellow('\n⚠️ NEX Hub requires Supabase configuration.\n'))
|
|
1130
|
+
return []
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// 2. Check if user is authenticated
|
|
1134
|
+
const { data: { user }, error: authError } = await this.supabase.auth.getUser()
|
|
1135
|
+
|
|
1136
|
+
if (authError || !user) {
|
|
1137
|
+
spinner.fail('Not authenticated')
|
|
1138
|
+
console.log(chalk.yellow('\n⚠️ You need to be logged in.\n'))
|
|
1139
|
+
return []
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// 3. Call Edge Function to get registered agents
|
|
1143
|
+
const EDGE_FUNCTION_URL = process.env.VITE_SUPABASE_URL?.replace('.supabase.co', '.supabase.co/functions/v1')
|
|
1144
|
+
const { data: session } = await this.supabase.auth.getSession()
|
|
1145
|
+
|
|
1146
|
+
const response = await fetch(`${EDGE_FUNCTION_URL}/nex-agent-registry/my-agents`, {
|
|
1147
|
+
method: 'GET',
|
|
1148
|
+
headers: {
|
|
1149
|
+
'Authorization': `Bearer ${session.session?.access_token}`,
|
|
1150
|
+
'apikey': process.env.VITE_SUPABASE_ANON_KEY
|
|
1151
|
+
}
|
|
1152
|
+
})
|
|
1153
|
+
|
|
1154
|
+
const result = await response.json()
|
|
1155
|
+
|
|
1156
|
+
if (!response.ok) {
|
|
1157
|
+
spinner.fail('Failed to load agents')
|
|
1158
|
+
console.log(chalk.red(`\n❌ ${result.error}\n`))
|
|
1159
|
+
return []
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const agents = result.agents || []
|
|
1163
|
+
|
|
1164
|
+
spinner.succeed(`Found ${agents.length} registered agents`)
|
|
1165
|
+
|
|
1166
|
+
// 4. Display agents
|
|
1167
|
+
if (agents.length === 0) {
|
|
1168
|
+
console.log(chalk.yellow('\n😕 You have no agents registered yet.\n'))
|
|
1169
|
+
console.log(chalk.cyan('💡 Register an agent:'))
|
|
1170
|
+
console.log(chalk.cyan(' nex agent search <query>'))
|
|
1171
|
+
console.log(chalk.cyan(' nex agent register <agent-id>\n'))
|
|
1172
|
+
return []
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
console.log('\n')
|
|
1176
|
+
console.log(chalk.bold.cyan('📦 YOUR REGISTERED AGENTS\n'))
|
|
1177
|
+
|
|
1178
|
+
agents.forEach(reg => {
|
|
1179
|
+
const agent = reg.nex_marketplace_agents
|
|
1180
|
+
const icon = agent?.icon || '🤖'
|
|
1181
|
+
const name = agent?.name || reg.agent_id
|
|
1182
|
+
const type = agent?.agent_type || 'unknown'
|
|
1183
|
+
const registeredAt = new Date(reg.registered_at).toLocaleDateString()
|
|
1184
|
+
|
|
1185
|
+
console.log(chalk.bold(`${icon} ${name}`))
|
|
1186
|
+
console.log(chalk.gray(` ID: ${reg.agent_id}`))
|
|
1187
|
+
console.log(chalk.gray(` Type: ${type}`))
|
|
1188
|
+
console.log(chalk.gray(` Registered: ${registeredAt}`))
|
|
1189
|
+
console.log(chalk.dim(` ${chalk.cyan('nex agent install')} ${reg.agent_id}`))
|
|
1190
|
+
console.log()
|
|
1191
|
+
})
|
|
1192
|
+
|
|
1193
|
+
// 5. Show plan info
|
|
1194
|
+
const { data: planData } = await this.supabase.rpc('get_user_plan', { p_user_id: user.id })
|
|
1195
|
+
|
|
1196
|
+
if (planData && planData.length > 0) {
|
|
1197
|
+
const plan = planData[0]
|
|
1198
|
+
const nexCount = agents.filter(a => a.nex_marketplace_agents?.agent_type === 'nex_free').length
|
|
1199
|
+
const bmadCount = agents.filter(a => a.nex_marketplace_agents?.agent_type === 'bmad_free').length
|
|
1200
|
+
const premiumCount = agents.filter(a => a.nex_marketplace_agents?.agent_type === 'premium').length
|
|
1201
|
+
|
|
1202
|
+
console.log(chalk.bold.cyan('📊 YOUR PLAN\n'))
|
|
1203
|
+
console.log(chalk.bold(` ${plan.plan_name}`))
|
|
1204
|
+
console.log(chalk.gray(` NEX Free: ${nexCount}/${plan.max_nex_free}`))
|
|
1205
|
+
console.log(chalk.gray(` BMAD Free: ${bmadCount}/${plan.max_bmad_free}`))
|
|
1206
|
+
console.log(chalk.gray(` Premium: ${premiumCount}/${plan.max_premium}`))
|
|
1207
|
+
console.log()
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
return agents
|
|
1211
|
+
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
spinner.fail('Failed to load agents')
|
|
1214
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}\n`))
|
|
1215
|
+
return []
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Check if agent is registered (internal method)
|
|
1221
|
+
*/
|
|
1222
|
+
async checkAgentRegistration(agentId) {
|
|
1223
|
+
try {
|
|
1224
|
+
if (!this.supabase) return false
|
|
1225
|
+
|
|
1226
|
+
const { data: { user } } = await this.supabase.auth.getUser()
|
|
1227
|
+
if (!user) return false
|
|
1228
|
+
|
|
1229
|
+
const EDGE_FUNCTION_URL = process.env.VITE_SUPABASE_URL?.replace('.supabase.co', '.supabase.co/functions/v1')
|
|
1230
|
+
const { data: session } = await this.supabase.auth.getSession()
|
|
1231
|
+
|
|
1232
|
+
const response = await fetch(`${EDGE_FUNCTION_URL}/nex-agent-registry/check/${agentId}`, {
|
|
1233
|
+
method: 'GET',
|
|
1234
|
+
headers: {
|
|
1235
|
+
'Authorization': `Bearer ${session.session?.access_token}`,
|
|
1236
|
+
'apikey': process.env.VITE_SUPABASE_ANON_KEY
|
|
1237
|
+
}
|
|
1238
|
+
})
|
|
1239
|
+
|
|
1240
|
+
const result = await response.json()
|
|
1241
|
+
return result.is_registered || false
|
|
1242
|
+
|
|
1243
|
+
} catch (error) {
|
|
1244
|
+
return false
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|