@neus/sdk 1.1.1 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -206
- package/SECURITY.md +38 -38
- package/cjs/client.cjs +23 -15
- package/cjs/index.cjs +23 -15
- package/cjs/mcp-hosts.cjs +125 -0
- package/cli/neus.mjs +2150 -1895
- package/client.js +32 -17
- package/mcp-hosts.js +121 -0
- package/package.json +147 -142
- package/types.d.ts +39 -8
- package/widgets/README.md +41 -45
- package/widgets/verify-gate/dist/ProofBadge.js +14 -4
- package/widgets/verify-gate/dist/VerifyGate.js +66 -203
package/cli/neus.mjs
CHANGED
|
@@ -1,1895 +1,2150 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
-
import { createHash, randomBytes } from 'node:crypto';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import os from 'node:os';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function
|
|
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
|
-
return
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
if (
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (
|
|
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
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
if (
|
|
444
|
-
return
|
|
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
|
-
return
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
const
|
|
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
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
scope
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
return
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if (
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
return {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (!
|
|
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
|
-
|
|
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
|
-
return
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
if (
|
|
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
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
const
|
|
1246
|
-
.
|
|
1247
|
-
.
|
|
1248
|
-
.join(
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
if (
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
);
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
)
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
);
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
};
|
|
1821
|
-
|
|
1822
|
-
if (options.
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import {
|
|
9
|
+
NEUS_MCP_SERVER_NAME,
|
|
10
|
+
NEUS_MCP_URL,
|
|
11
|
+
buildNeusMcpHttpConfig
|
|
12
|
+
} from '../mcp-hosts.js';
|
|
13
|
+
|
|
14
|
+
const __cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
const NEUS_APP_URL = 'https://neus.network';
|
|
17
|
+
const NEUS_TOKEN_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/token';
|
|
18
|
+
const NEUS_DISCONNECT_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/revoke';
|
|
19
|
+
const NEUS_PROFILE_KEY_ENDPOINT = 'https://api.neus.network/api/v1/auth/profile-key';
|
|
20
|
+
const SUPPORTED_CLIENTS = ['claude', 'codex', 'cursor', 'vscode'];
|
|
21
|
+
const PROJECT_CLIENTS = ['claude', 'cursor', 'vscode'];
|
|
22
|
+
const CODEX_OAUTH_SCOPES = 'neus:core,neus:profile,neus:secrets,offline_access';
|
|
23
|
+
const IMPORT_SCHEMA = 'neus.portable-agent.v1';
|
|
24
|
+
const SUPPORTED_IMPORT_SOURCES = [
|
|
25
|
+
'auto',
|
|
26
|
+
'cursor',
|
|
27
|
+
'claude-code',
|
|
28
|
+
'claude-desktop'
|
|
29
|
+
];
|
|
30
|
+
const SUPPORTED_EXPORT_FORMATS = ['manifest', 'json'];
|
|
31
|
+
|
|
32
|
+
const ansi = {
|
|
33
|
+
reset: '\x1b[0m',
|
|
34
|
+
dim: '\x1b[2m',
|
|
35
|
+
cyan: '\x1b[36m',
|
|
36
|
+
green: '\x1b[32m',
|
|
37
|
+
yellow: '\x1b[33m',
|
|
38
|
+
red: '\x1b[31m',
|
|
39
|
+
bold: '\x1b[1m'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function isTruthyEnv(value) {
|
|
43
|
+
const normalized = String(value || '')
|
|
44
|
+
.trim()
|
|
45
|
+
.toLowerCase();
|
|
46
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveColorEnabled() {
|
|
50
|
+
if (isTruthyEnv(process.env.NO_COLOR)) return false;
|
|
51
|
+
if (process.env.TERM === 'dumb') return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function paint(value, color) {
|
|
56
|
+
if (!resolveColorEnabled()) return String(value);
|
|
57
|
+
return `${ansi[color] || ''}${value}${ansi.reset}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function terminalColumns() {
|
|
61
|
+
const cols = Number(process.stderr.columns || process.stdout.columns || 0);
|
|
62
|
+
if (Number.isFinite(cols) && cols >= 40) return cols;
|
|
63
|
+
return 80;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function truncateDetail(text) {
|
|
67
|
+
const raw = String(text || '');
|
|
68
|
+
const max = Math.max(24, terminalColumns() - 18);
|
|
69
|
+
if (raw.length <= max) return raw;
|
|
70
|
+
return `${raw.slice(0, Math.max(0, max - 3))}...`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cliSymbols() {
|
|
74
|
+
return { ok: 'ok', warn: '!', next: '>', skip: '-' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function writeCliLine(line) {
|
|
78
|
+
process.stderr.write(`${line}\n`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let cliBannerEmitted = false;
|
|
82
|
+
|
|
83
|
+
function readCliVersion() {
|
|
84
|
+
try {
|
|
85
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf8'));
|
|
86
|
+
return String(pkg.version || '0.0.0').trim();
|
|
87
|
+
} catch {
|
|
88
|
+
return '0.0.0';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function shouldEmitCliBanner(cliOptions = {}) {
|
|
93
|
+
if (cliBannerEmitted) return false;
|
|
94
|
+
if (cliOptions.json) return false;
|
|
95
|
+
if (!process.stderr.isTTY) return false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function emitCliBanner(cliOptions = {}) {
|
|
100
|
+
if (!shouldEmitCliBanner(cliOptions)) return;
|
|
101
|
+
const version = readCliVersion();
|
|
102
|
+
const title = paint('NEUS', 'green');
|
|
103
|
+
const meta = `${paint(`v${version}`, 'dim')}${paint(' | trust receipts', 'dim')}`;
|
|
104
|
+
writeCliLine('');
|
|
105
|
+
writeCliLine(` ${title} ${meta}`);
|
|
106
|
+
writeCliLine('');
|
|
107
|
+
cliBannerEmitted = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function logStep(kind, label, detail = '') {
|
|
111
|
+
const symbols = cliSymbols();
|
|
112
|
+
const iconKey = kind === 'ok' ? 'ok' : kind === 'warn' ? 'warn' : kind === 'next' ? 'next' : 'skip';
|
|
113
|
+
const iconColor = kind === 'ok' ? 'green' : kind === 'warn' ? 'yellow' : kind === 'next' ? 'cyan' : 'dim';
|
|
114
|
+
const iconCell = symbols[iconKey].padEnd(2);
|
|
115
|
+
const icon = paint(iconCell, iconColor);
|
|
116
|
+
const name = paint(String(label).padEnd(10), 'cyan');
|
|
117
|
+
const suffix = detail ? ` ${paint(truncateDetail(detail), 'dim')}` : '';
|
|
118
|
+
writeCliLine(` ${icon} ${name}${suffix}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function writeGuidanceLine(text) {
|
|
122
|
+
writeCliLine(` ${paint('-', 'dim')} ${text}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function describeClientResult(command, result) {
|
|
126
|
+
if (result.dryRun && result.changed) {
|
|
127
|
+
if (result.client === 'codex') {
|
|
128
|
+
return `would update ${result.targetPath || '~/.codex/config.toml'}`;
|
|
129
|
+
}
|
|
130
|
+
return 'would update';
|
|
131
|
+
}
|
|
132
|
+
if (result.client === 'codex' && result.configured) {
|
|
133
|
+
if (command === 'auth') {
|
|
134
|
+
return result.authConfigured ? 'Codex OAuth complete' : 'Codex MCP config ready';
|
|
135
|
+
}
|
|
136
|
+
return `Codex MCP config: ${result.targetPath || '~/.codex/config.toml'}`;
|
|
137
|
+
}
|
|
138
|
+
if (result.changed) return 'updated';
|
|
139
|
+
if (result.authConfigured) return 'signed in';
|
|
140
|
+
return 'ready';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function printBuilderGuidance(command, results) {
|
|
144
|
+
if (!['setup', 'auth'].includes(command)) return;
|
|
145
|
+
const hasCodex = results.some(result => result.client === 'codex');
|
|
146
|
+
writeCliLine('');
|
|
147
|
+
writeCliLine(paint('Builder notes', 'cyan'));
|
|
148
|
+
writeGuidanceLine('Use from any shell without a global install: `npx -y -p @neus/sdk neus ...`.');
|
|
149
|
+
if (hasCodex) {
|
|
150
|
+
writeGuidanceLine('Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.');
|
|
151
|
+
}
|
|
152
|
+
writeGuidanceLine(
|
|
153
|
+
'Claude plugin commands run inside Claude Code chat, not as `claude install`: `/plugin marketplace add https://github.com/neus/network`.'
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function selectedClientNames(results) {
|
|
158
|
+
return results.map(result => result.client).filter(Boolean);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function preferredSetupCommand(results) {
|
|
162
|
+
const clients = selectedClientNames(results);
|
|
163
|
+
const suffix = clients.length === 1 ? ` --client ${clients[0]}` : '';
|
|
164
|
+
return `npx -y -p @neus/sdk neus setup${suffix}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function preferredAuthCommand(results) {
|
|
168
|
+
const clients = selectedClientNames(results);
|
|
169
|
+
if (clients.length === 1 && clients[0] === 'codex') {
|
|
170
|
+
return 'npx -y -p @neus/sdk neus auth --client codex';
|
|
171
|
+
}
|
|
172
|
+
return 'npx -y -p @neus/sdk neus auth';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function printStatusGuidance(results) {
|
|
176
|
+
writeCliLine('');
|
|
177
|
+
writeCliLine(paint('MCP endpoint', 'cyan'));
|
|
178
|
+
writeGuidanceLine(NEUS_MCP_URL);
|
|
179
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
180
|
+
if (results.some(result => result.configured)) {
|
|
181
|
+
writeGuidanceLine('Saved config found. Run `npx -y -p @neus/sdk neus doctor --live` to confirm live Profile context.');
|
|
182
|
+
} else {
|
|
183
|
+
writeGuidanceLine(`No selected MCP host is configured yet. Run \`${preferredSetupCommand(results)}\`.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function printHostAuthIntro(host, cliOptions = {}) {
|
|
188
|
+
if (cliOptions.json) return;
|
|
189
|
+
emitCliBanner(cliOptions);
|
|
190
|
+
writeCliLine(paint('auth', 'green'));
|
|
191
|
+
if (host === 'codex') {
|
|
192
|
+
logStep('next', 'codex', 'starting Codex-owned MCP OAuth');
|
|
193
|
+
logStep('next', 'command', 'codex mcp login neus');
|
|
194
|
+
writeCliLine('');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function printFlowSummary(command, scope, results, { nextStep = '', cliOptions = {} } = {}) {
|
|
199
|
+
emitCliBanner(cliOptions);
|
|
200
|
+
writeCliLine(paint(String(command), 'green'));
|
|
201
|
+
|
|
202
|
+
for (const result of results) {
|
|
203
|
+
const client = result.client;
|
|
204
|
+
if (result.error) {
|
|
205
|
+
logStep('warn', client, result.error);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (result.configured) {
|
|
209
|
+
const detail = describeClientResult(command, result);
|
|
210
|
+
logStep('ok', client, detail);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (result.authConfigured === null) {
|
|
214
|
+
logStep('skip', client, 'not installed');
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
logStep('skip', client, 'not configured');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (nextStep) {
|
|
221
|
+
writeCliLine('');
|
|
222
|
+
logStep('next', 'next', nextStep);
|
|
223
|
+
}
|
|
224
|
+
if (command === 'status') {
|
|
225
|
+
printStatusGuidance(results);
|
|
226
|
+
}
|
|
227
|
+
printBuilderGuidance(command, results);
|
|
228
|
+
writeCliLine('');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function printAuthBrowserIntro(authUrl, cliOptions = {}) {
|
|
232
|
+
emitCliBanner(cliOptions);
|
|
233
|
+
writeCliLine(paint('auth', 'green'));
|
|
234
|
+
logStep('next', 'sign-in', 'opens in your browser');
|
|
235
|
+
writeCliLine('');
|
|
236
|
+
writeCliLine(` ${paint(truncateDetail(authUrl), 'dim')}`);
|
|
237
|
+
writeCliLine('');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function parseBearerHeader(value) {
|
|
241
|
+
const raw = String(value || '').trim();
|
|
242
|
+
if (!raw.toLowerCase().startsWith('bearer ')) return '';
|
|
243
|
+
return raw.slice(7).trim();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function readCursorBearer(scope, cwd) {
|
|
247
|
+
const targetPath = cursorConfigPath(scope, cwd);
|
|
248
|
+
if (!fileExists(targetPath)) return '';
|
|
249
|
+
const doc = readJsonFile(targetPath, {});
|
|
250
|
+
return parseBearerHeader(doc.mcpServers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function readVsCodeBearer(scope, cwd) {
|
|
254
|
+
const targetPath = vscodeConfigPath(scope, cwd);
|
|
255
|
+
if (!fileExists(targetPath)) return '';
|
|
256
|
+
const doc = readJsonFile(targetPath, {});
|
|
257
|
+
return parseBearerHeader(doc.servers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function readClaudeBearer(scope, cwd) {
|
|
261
|
+
if (scope === 'project') {
|
|
262
|
+
const targetPath = claudeProjectConfigPath(cwd);
|
|
263
|
+
if (!fileExists(targetPath)) return '';
|
|
264
|
+
const doc = readJsonFile(targetPath, {});
|
|
265
|
+
return parseBearerHeader(doc.mcpServers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
266
|
+
}
|
|
267
|
+
if (!commandExists('claude')) return '';
|
|
268
|
+
const result = spawnSync('claude', ['mcp', 'list'], {
|
|
269
|
+
encoding: 'utf8',
|
|
270
|
+
env: process.env
|
|
271
|
+
});
|
|
272
|
+
if (result.status !== 0) return '';
|
|
273
|
+
const lines = String(result.stdout || '').split(/\r?\n/);
|
|
274
|
+
if (!lines.includes(NEUS_MCP_SERVER_NAME)) return '';
|
|
275
|
+
const statePath = process.env.NEUS_TEST_CLAUDE_STATE;
|
|
276
|
+
if (statePath && fileExists(statePath)) {
|
|
277
|
+
const state = readJsonFile(statePath, { servers: {} });
|
|
278
|
+
const headers = state.servers?.[NEUS_MCP_SERVER_NAME]?.headers || [];
|
|
279
|
+
const authLine = headers.find(line => String(line).toLowerCase().startsWith('authorization:'));
|
|
280
|
+
if (authLine) {
|
|
281
|
+
return parseBearerHeader(authLine.replace(/^authorization:\s*/i, ''));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return '';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function readInstalledAccessKey(scope, cwd) {
|
|
288
|
+
for (const reader of [readCursorBearer, readVsCodeBearer, readClaudeBearer]) {
|
|
289
|
+
const token = reader(scope, cwd);
|
|
290
|
+
if (token) return token;
|
|
291
|
+
}
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function envAccessKey() {
|
|
296
|
+
return String(process.env.NEUS_ACCESS_KEY || '').trim();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** --access-key flag, else NEUS_ACCESS_KEY from the environment, else browser sign-in. */
|
|
300
|
+
function resolveAccessKey(options) {
|
|
301
|
+
const explicit = String(options.accessKey || '').trim();
|
|
302
|
+
if (explicit) return explicit;
|
|
303
|
+
return envAccessKey();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** --access-key, IDE MCP config, then NEUS_ACCESS_KEY from the environment. */
|
|
307
|
+
function resolveLiveAccessKey(options, scope, cwd) {
|
|
308
|
+
const explicit = String(options.accessKey || '').trim();
|
|
309
|
+
if (explicit) return explicit;
|
|
310
|
+
const installed = readInstalledAccessKey(scope, cwd);
|
|
311
|
+
if (installed) return installed;
|
|
312
|
+
return envAccessKey();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function resolveAuthMethod(options, accessKey) {
|
|
316
|
+
if (!accessKey) return 'browser';
|
|
317
|
+
if (String(options.accessKey || '').trim()) return 'access-key';
|
|
318
|
+
return 'env-key';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function fileExists(targetPath) {
|
|
322
|
+
try {
|
|
323
|
+
fs.accessSync(targetPath);
|
|
324
|
+
return true;
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function jsonStringify(value) {
|
|
331
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function readJsonFile(targetPath, fallback) {
|
|
335
|
+
if (!fileExists(targetPath)) return fallback;
|
|
336
|
+
const raw = fs.readFileSync(targetPath, 'utf8').trim();
|
|
337
|
+
if (!raw) return fallback;
|
|
338
|
+
try {
|
|
339
|
+
const parsed = JSON.parse(raw);
|
|
340
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (error instanceof SyntaxError) {
|
|
343
|
+
throw new Error(`Invalid JSON in ${targetPath}`);
|
|
344
|
+
}
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function writeJsonFile(targetPath, nextValue, dryRun) {
|
|
350
|
+
const serialized = jsonStringify(nextValue);
|
|
351
|
+
const hadExistingFile = fileExists(targetPath);
|
|
352
|
+
const previous = hadExistingFile ? fs.readFileSync(targetPath, 'utf8') : null;
|
|
353
|
+
const changed = previous !== serialized;
|
|
354
|
+
const backupPath = hadExistingFile && changed ? `${targetPath}.bak` : null;
|
|
355
|
+
|
|
356
|
+
if (!dryRun && changed) {
|
|
357
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
358
|
+
if (backupPath) {
|
|
359
|
+
fs.copyFileSync(targetPath, backupPath);
|
|
360
|
+
}
|
|
361
|
+
fs.writeFileSync(targetPath, serialized, 'utf8');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
changed,
|
|
366
|
+
targetPath,
|
|
367
|
+
backupPath,
|
|
368
|
+
dryRun
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function readTextFile(targetPath) {
|
|
373
|
+
if (!fileExists(targetPath)) return '';
|
|
374
|
+
return fs.readFileSync(targetPath, 'utf8');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function sha256(value) {
|
|
378
|
+
return createHash('sha256').update(value).digest('hex');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function statBytes(targetPath) {
|
|
382
|
+
try {
|
|
383
|
+
return fs.statSync(targetPath).size;
|
|
384
|
+
} catch {
|
|
385
|
+
return 0;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function listDirectoryNames(targetPath) {
|
|
390
|
+
if (!fileExists(targetPath)) return [];
|
|
391
|
+
try {
|
|
392
|
+
return fs
|
|
393
|
+
.readdirSync(targetPath, { withFileTypes: true })
|
|
394
|
+
.filter(entry => entry.isDirectory())
|
|
395
|
+
.map(entry => entry.name)
|
|
396
|
+
.sort((a, b) => a.localeCompare(b));
|
|
397
|
+
} catch {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function listFileNames(targetPath, extensions) {
|
|
403
|
+
if (!fileExists(targetPath)) return [];
|
|
404
|
+
try {
|
|
405
|
+
return fs
|
|
406
|
+
.readdirSync(targetPath, { withFileTypes: true })
|
|
407
|
+
.filter(entry => entry.isFile())
|
|
408
|
+
.map(entry => entry.name)
|
|
409
|
+
.filter(name => extensions.some(extension => name.toLowerCase().endsWith(extension)))
|
|
410
|
+
.sort((a, b) => a.localeCompare(b));
|
|
411
|
+
} catch {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function safeReadJson(targetPath, warnings) {
|
|
417
|
+
if (!fileExists(targetPath)) return null;
|
|
418
|
+
try {
|
|
419
|
+
return readJsonFile(targetPath, null);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
warnings.push(`Skipped malformed JSON at ${targetPath}: ${errorMessage(error)}`);
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function portablePath(targetPath) {
|
|
427
|
+
const homeDir = os.homedir();
|
|
428
|
+
const cwd = process.cwd();
|
|
429
|
+
const normalized = path.resolve(targetPath);
|
|
430
|
+
const homeRelative = path.relative(homeDir, normalized);
|
|
431
|
+
if (homeRelative && !homeRelative.startsWith('..') && !path.isAbsolute(homeRelative)) {
|
|
432
|
+
return `~/${homeRelative.replaceAll(path.sep, '/')}`;
|
|
433
|
+
}
|
|
434
|
+
const cwdRelative = path.relative(cwd, normalized);
|
|
435
|
+
if (cwdRelative && !cwdRelative.startsWith('..') && !path.isAbsolute(cwdRelative)) {
|
|
436
|
+
return cwdRelative.replaceAll(path.sep, '/');
|
|
437
|
+
}
|
|
438
|
+
return normalized.replaceAll(path.sep, '/');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function instructionEntry(targetPath, name) {
|
|
442
|
+
const raw = readTextFile(targetPath);
|
|
443
|
+
if (!raw) return null;
|
|
444
|
+
return {
|
|
445
|
+
name,
|
|
446
|
+
path: portablePath(targetPath),
|
|
447
|
+
bytes: statBytes(targetPath),
|
|
448
|
+
sha256: sha256(raw)
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function readMcpServers(targetPath, source, warnings) {
|
|
453
|
+
const doc = safeReadJson(targetPath, warnings);
|
|
454
|
+
if (!doc) return [];
|
|
455
|
+
const mcpSection = doc.mcp && typeof doc.mcp === 'object' && !Array.isArray(doc.mcp) ? doc.mcp : null;
|
|
456
|
+
const servers =
|
|
457
|
+
doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
458
|
+
? doc.mcpServers
|
|
459
|
+
: mcpSection?.servers &&
|
|
460
|
+
typeof mcpSection.servers === 'object' &&
|
|
461
|
+
!Array.isArray(mcpSection.servers)
|
|
462
|
+
? mcpSection.servers
|
|
463
|
+
: doc.servers && typeof doc.servers === 'object' && !Array.isArray(doc.servers)
|
|
464
|
+
? doc.servers
|
|
465
|
+
: {};
|
|
466
|
+
return Object.keys(servers)
|
|
467
|
+
.sort((a, b) => a.localeCompare(b))
|
|
468
|
+
.map(name => ({
|
|
469
|
+
name,
|
|
470
|
+
source,
|
|
471
|
+
path: portablePath(targetPath),
|
|
472
|
+
type:
|
|
473
|
+
servers[name]?.type ||
|
|
474
|
+
(servers[name]?.url ? 'http' : servers[name]?.command ? 'stdio' : 'unknown'),
|
|
475
|
+
url:
|
|
476
|
+
typeof servers[name]?.url === 'string' && !servers[name].headers
|
|
477
|
+
? servers[name].url
|
|
478
|
+
: undefined
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function resolveCommand(command) {
|
|
483
|
+
const checker = process.platform === 'win32' ? 'where' : 'which';
|
|
484
|
+
const result = spawnSync(checker, [command], {
|
|
485
|
+
encoding: 'utf8',
|
|
486
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
487
|
+
});
|
|
488
|
+
if (result.status !== 0) return null;
|
|
489
|
+
const firstMatch = result.stdout
|
|
490
|
+
.split(/\r?\n/)
|
|
491
|
+
.map(line => line.trim())
|
|
492
|
+
.find(Boolean);
|
|
493
|
+
return firstMatch || null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function runCommand(command, args, cwd, tolerateFailure = false) {
|
|
497
|
+
const resolvedCommand = resolveCommand(command) || command;
|
|
498
|
+
const isWindowsScript = process.platform === 'win32' && /\.(cmd|bat)$/i.test(resolvedCommand);
|
|
499
|
+
const result = isWindowsScript
|
|
500
|
+
? spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', resolvedCommand, ...args], {
|
|
501
|
+
cwd,
|
|
502
|
+
encoding: 'utf8',
|
|
503
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
504
|
+
})
|
|
505
|
+
: spawnSync(resolvedCommand, args, {
|
|
506
|
+
cwd,
|
|
507
|
+
encoding: 'utf8',
|
|
508
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
if (result.error && !tolerateFailure) {
|
|
512
|
+
throw result.error;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (result.status !== 0 && !tolerateFailure) {
|
|
516
|
+
const detail =
|
|
517
|
+
[result.stderr, result.stdout].find(value => typeof value === 'string' && value.trim()) || '';
|
|
518
|
+
throw new Error(detail.trim() || `Command failed: ${command} ${args.join(' ')}`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function commandExists(command) {
|
|
525
|
+
return Boolean(resolveCommand(command));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function cursorInstalled() {
|
|
529
|
+
const homeDir = os.homedir();
|
|
530
|
+
const appData = process.env.APPDATA || '';
|
|
531
|
+
const localAppData = process.env.LOCALAPPDATA || '';
|
|
532
|
+
return [
|
|
533
|
+
path.join(homeDir, '.cursor'),
|
|
534
|
+
path.join(appData, 'Cursor'),
|
|
535
|
+
path.join(localAppData, 'Programs', 'Cursor', 'Cursor.exe')
|
|
536
|
+
].some(fileExists);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function defaultUserClients() {
|
|
540
|
+
const detected = [];
|
|
541
|
+
if (commandExists('claude')) detected.push('claude');
|
|
542
|
+
if (commandExists('codex')) detected.push('codex');
|
|
543
|
+
if (cursorInstalled()) detected.push('cursor');
|
|
544
|
+
if (commandExists('code') || fileExists(path.join(process.env.APPDATA || '', 'Code')))
|
|
545
|
+
detected.push('vscode');
|
|
546
|
+
return detected;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function parseClientOption(raw) {
|
|
550
|
+
return String(raw || '')
|
|
551
|
+
.split(',')
|
|
552
|
+
.map(value => value.trim().toLowerCase())
|
|
553
|
+
.filter(Boolean);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function parseArgs(argv) {
|
|
557
|
+
if (argv.length === 0) {
|
|
558
|
+
return {
|
|
559
|
+
command: 'help',
|
|
560
|
+
options: {
|
|
561
|
+
accessKey: '',
|
|
562
|
+
clients: [],
|
|
563
|
+
source: 'auto',
|
|
564
|
+
format: 'manifest',
|
|
565
|
+
output: '',
|
|
566
|
+
live: false,
|
|
567
|
+
json: false,
|
|
568
|
+
dryRun: false,
|
|
569
|
+
project: false
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const command = argv[0];
|
|
575
|
+
const options = {
|
|
576
|
+
accessKey: '',
|
|
577
|
+
clients: [],
|
|
578
|
+
source: 'auto',
|
|
579
|
+
format: 'manifest',
|
|
580
|
+
output: '',
|
|
581
|
+
live: false,
|
|
582
|
+
json: false,
|
|
583
|
+
dryRun: false,
|
|
584
|
+
project: false
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
588
|
+
const token = argv[index];
|
|
589
|
+
if (token === '--json') {
|
|
590
|
+
options.json = true;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (token === '--dry-run') {
|
|
594
|
+
options.dryRun = true;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (token === '--live') {
|
|
598
|
+
options.live = true;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (token === '--project') {
|
|
602
|
+
options.project = true;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (token === '--from') {
|
|
606
|
+
const value = argv[index + 1];
|
|
607
|
+
if (!value) throw new Error('--from requires a value');
|
|
608
|
+
options.source = value.trim().toLowerCase();
|
|
609
|
+
index += 1;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (token === '--to') {
|
|
613
|
+
const value = argv[index + 1];
|
|
614
|
+
if (!value) throw new Error('--to requires a value');
|
|
615
|
+
options.format = value.trim().toLowerCase();
|
|
616
|
+
index += 1;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (token === '--output') {
|
|
620
|
+
const value = argv[index + 1];
|
|
621
|
+
if (!value) throw new Error('--output requires a value');
|
|
622
|
+
options.output = value;
|
|
623
|
+
index += 1;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (token === '--client') {
|
|
627
|
+
const value = argv[index + 1];
|
|
628
|
+
if (!value) throw new Error('--client requires a value');
|
|
629
|
+
options.clients.push(...parseClientOption(value));
|
|
630
|
+
index += 1;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (token === '--access-key') {
|
|
634
|
+
const value = argv[index + 1];
|
|
635
|
+
if (!value) throw new Error('--access-key requires a value');
|
|
636
|
+
options.accessKey = value;
|
|
637
|
+
index += 1;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (token === '--help' || token === '-h') {
|
|
641
|
+
return { command: 'help', options };
|
|
642
|
+
}
|
|
643
|
+
throw new Error(`Unknown option: ${token}`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
options.accessKey = String(options.accessKey || '').trim();
|
|
647
|
+
options.clients = [...new Set(options.clients)];
|
|
648
|
+
|
|
649
|
+
return { command, options };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function printUsage(exitCode = 0) {
|
|
653
|
+
const lines = [
|
|
654
|
+
'Usage: neus <command> [options]',
|
|
655
|
+
'',
|
|
656
|
+
'Commands:',
|
|
657
|
+
' setup Configure hosted NEUS MCP for supported clients',
|
|
658
|
+
' init Configure supported MCP clients automatically',
|
|
659
|
+
' auth Sign in (browser, or NEUS_ACCESS_KEY / --access-key when set)',
|
|
660
|
+
' disconnect Disconnect NEUS MCP (revoke the stored OAuth token or access key)',
|
|
661
|
+
' status Show current NEUS MCP setup',
|
|
662
|
+
' doctor Deep check: config status, profile connection, and live MCP context',
|
|
663
|
+
' import Detect and package supported assistant context for NEUS portability',
|
|
664
|
+
' export Export the latest local NEUS portable agent manifest',
|
|
665
|
+
' help Show this message',
|
|
666
|
+
'',
|
|
667
|
+
'Options:',
|
|
668
|
+
' --client <name[,name]> Limit setup to claude, codex, cursor, or vscode',
|
|
669
|
+
' --project Write shared project config instead of user config',
|
|
670
|
+
' --access-key <npk_...> Override profile access key (else uses NEUS_ACCESS_KEY if set)',
|
|
671
|
+
' --from <source> Import source: auto, cursor, claude-code, or claude-desktop',
|
|
672
|
+
' --to <format> Export format: manifest or json',
|
|
673
|
+
' --output <path> Write exported manifest to a specific path',
|
|
674
|
+
' --live Run live MCP checks (uses IDE credential or --access-key)',
|
|
675
|
+
' --json Print JSON output',
|
|
676
|
+
' --dry-run Preview changes without writing files'
|
|
677
|
+
];
|
|
678
|
+
const stream = exitCode === 0 ? process.stdout : process.stderr;
|
|
679
|
+
stream.write(`${lines.join('\n')}\n`);
|
|
680
|
+
process.exit(exitCode);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function assertValidClients(clients) {
|
|
684
|
+
for (const client of clients) {
|
|
685
|
+
if (!SUPPORTED_CLIENTS.includes(client)) {
|
|
686
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function resolveScope(options) {
|
|
692
|
+
return options.project ? 'project' : 'user';
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function resolveClients(scope, requestedClients) {
|
|
696
|
+
assertValidClients(requestedClients);
|
|
697
|
+
if (requestedClients.length > 0) return requestedClients;
|
|
698
|
+
if (scope === 'project') return [...PROJECT_CLIENTS];
|
|
699
|
+
return defaultUserClients();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function ensureClientSelection(scope, clients) {
|
|
703
|
+
if (clients.length > 0) return;
|
|
704
|
+
if (scope === 'project') return;
|
|
705
|
+
throw new Error(
|
|
706
|
+
'No supported clients detected. Re-run with --project or use --client to target a specific client.'
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function ensureSafeAuth(command, scope, accessKey) {
|
|
711
|
+
if ((command === 'auth' || command === 'setup') && scope !== 'user') {
|
|
712
|
+
throw new Error(
|
|
713
|
+
'`neus ${command}` only supports user scope so access keys never land in shared project config.'
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
if (scope === 'project' && accessKey) {
|
|
717
|
+
throw new Error(
|
|
718
|
+
'Access keys are only supported in user scope. Remove --project or omit --access-key.'
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function buildCursorServer(accessKey) {
|
|
724
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function buildVsCodeServer(accessKey) {
|
|
728
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function buildClaudeServer(accessKey) {
|
|
732
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function cursorConfigPath(scope, cwd) {
|
|
736
|
+
return scope === 'user'
|
|
737
|
+
? path.join(os.homedir(), '.cursor', 'mcp.json')
|
|
738
|
+
: path.join(cwd, '.cursor', 'mcp.json');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function vscodeConfigPath(scope, cwd) {
|
|
742
|
+
if (scope !== 'user') {
|
|
743
|
+
return path.join(cwd, '.vscode', 'mcp.json');
|
|
744
|
+
}
|
|
745
|
+
if (process.platform === 'darwin') {
|
|
746
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
|
|
747
|
+
}
|
|
748
|
+
if (process.platform === 'win32') {
|
|
749
|
+
return path.join(
|
|
750
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
751
|
+
'Code',
|
|
752
|
+
'User',
|
|
753
|
+
'mcp.json'
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
return path.join(os.homedir(), '.config', 'Code', 'User', 'mcp.json');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function claudeProjectConfigPath(cwd) {
|
|
760
|
+
return path.join(cwd, '.mcp.json');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function codexConfigPath() {
|
|
764
|
+
return path.join(os.homedir(), '.codex', 'config.toml');
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function installCursor(scope, accessKey, dryRun, cwd) {
|
|
768
|
+
const targetPath = cursorConfigPath(scope, cwd);
|
|
769
|
+
const doc = readJsonFile(targetPath, { mcpServers: {} });
|
|
770
|
+
const next = {
|
|
771
|
+
...doc,
|
|
772
|
+
mcpServers: {
|
|
773
|
+
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
774
|
+
? doc.mcpServers
|
|
775
|
+
: {}),
|
|
776
|
+
[NEUS_MCP_SERVER_NAME]: buildCursorServer(accessKey)
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
780
|
+
return {
|
|
781
|
+
client: 'cursor',
|
|
782
|
+
scope,
|
|
783
|
+
configured: true,
|
|
784
|
+
authConfigured: Boolean(accessKey),
|
|
785
|
+
changed: writeResult.changed,
|
|
786
|
+
targetPath,
|
|
787
|
+
backupPath: writeResult.backupPath,
|
|
788
|
+
dryRun,
|
|
789
|
+
error: null
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function installVsCode(scope, accessKey, dryRun, cwd) {
|
|
794
|
+
const targetPath = vscodeConfigPath(scope, cwd);
|
|
795
|
+
const doc = readJsonFile(targetPath, { servers: {} });
|
|
796
|
+
const next = {
|
|
797
|
+
...doc,
|
|
798
|
+
servers: {
|
|
799
|
+
...(doc.servers && typeof doc.servers === 'object' && !Array.isArray(doc.servers)
|
|
800
|
+
? doc.servers
|
|
801
|
+
: {}),
|
|
802
|
+
[NEUS_MCP_SERVER_NAME]: buildVsCodeServer(accessKey)
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
806
|
+
return {
|
|
807
|
+
client: 'vscode',
|
|
808
|
+
scope,
|
|
809
|
+
configured: true,
|
|
810
|
+
authConfigured: Boolean(accessKey),
|
|
811
|
+
changed: writeResult.changed,
|
|
812
|
+
targetPath,
|
|
813
|
+
backupPath: writeResult.backupPath,
|
|
814
|
+
dryRun,
|
|
815
|
+
error: null
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function installClaudeProject(scope, accessKey, dryRun, cwd) {
|
|
820
|
+
const targetPath = claudeProjectConfigPath(cwd);
|
|
821
|
+
const doc = readJsonFile(targetPath, { mcpServers: {} });
|
|
822
|
+
const next = {
|
|
823
|
+
...doc,
|
|
824
|
+
mcpServers: {
|
|
825
|
+
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
826
|
+
? doc.mcpServers
|
|
827
|
+
: {}),
|
|
828
|
+
[NEUS_MCP_SERVER_NAME]: buildClaudeServer(accessKey)
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
832
|
+
return {
|
|
833
|
+
client: 'claude',
|
|
834
|
+
scope,
|
|
835
|
+
configured: true,
|
|
836
|
+
authConfigured: Boolean(accessKey),
|
|
837
|
+
changed: writeResult.changed,
|
|
838
|
+
targetPath,
|
|
839
|
+
backupPath: writeResult.backupPath,
|
|
840
|
+
dryRun,
|
|
841
|
+
error: null
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function installClaudeUser(scope, accessKey, dryRun, cwd) {
|
|
846
|
+
if (!commandExists('claude')) {
|
|
847
|
+
throw new Error('Claude Code CLI is not installed or not on PATH.');
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (!dryRun) {
|
|
851
|
+
runCommand('claude', ['mcp', 'remove', '--scope', 'user', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
852
|
+
const addArgs = [
|
|
853
|
+
'mcp',
|
|
854
|
+
'add',
|
|
855
|
+
'--transport',
|
|
856
|
+
'http',
|
|
857
|
+
'--scope',
|
|
858
|
+
'user',
|
|
859
|
+
NEUS_MCP_SERVER_NAME,
|
|
860
|
+
NEUS_MCP_URL
|
|
861
|
+
];
|
|
862
|
+
if (accessKey) {
|
|
863
|
+
addArgs.push('--header', `Authorization: Bearer ${accessKey}`);
|
|
864
|
+
}
|
|
865
|
+
runCommand('claude', addArgs, cwd);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return {
|
|
869
|
+
client: 'claude',
|
|
870
|
+
scope,
|
|
871
|
+
configured: true,
|
|
872
|
+
authConfigured: Boolean(accessKey),
|
|
873
|
+
changed: true,
|
|
874
|
+
targetPath: '~/.claude.json',
|
|
875
|
+
backupPath: null,
|
|
876
|
+
dryRun,
|
|
877
|
+
error: null
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function installClaude(scope, accessKey, dryRun, cwd) {
|
|
882
|
+
if (scope === 'project') {
|
|
883
|
+
return installClaudeProject(scope, accessKey, dryRun, cwd);
|
|
884
|
+
}
|
|
885
|
+
return installClaudeUser(scope, accessKey, dryRun, cwd);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function installCodex(scope, accessKey, dryRun, cwd) {
|
|
889
|
+
if (scope !== 'user') {
|
|
890
|
+
throw new Error('Codex MCP setup is user-scoped through ~/.codex/config.toml.');
|
|
891
|
+
}
|
|
892
|
+
if (!commandExists('codex')) {
|
|
893
|
+
throw new Error('Codex CLI is not installed or not on PATH.');
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const bearerTokenEnvVar = envAccessKey() ? 'NEUS_ACCESS_KEY' : '';
|
|
897
|
+
|
|
898
|
+
if (!dryRun) {
|
|
899
|
+
runCommand('codex', ['mcp', 'remove', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
900
|
+
const addArgs = [
|
|
901
|
+
'mcp',
|
|
902
|
+
'add',
|
|
903
|
+
NEUS_MCP_SERVER_NAME,
|
|
904
|
+
'--url',
|
|
905
|
+
NEUS_MCP_URL,
|
|
906
|
+
'--oauth-client-id',
|
|
907
|
+
NEUS_OAUTH_CLIENT_ID,
|
|
908
|
+
'--oauth-resource',
|
|
909
|
+
NEUS_MCP_RESOURCE
|
|
910
|
+
];
|
|
911
|
+
if (bearerTokenEnvVar) {
|
|
912
|
+
addArgs.push('--bearer-token-env-var', bearerTokenEnvVar);
|
|
913
|
+
}
|
|
914
|
+
runCommand('codex', addArgs, cwd);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return {
|
|
918
|
+
client: 'codex',
|
|
919
|
+
scope,
|
|
920
|
+
configured: true,
|
|
921
|
+
authConfigured: bearerTokenEnvVar ? true : null,
|
|
922
|
+
changed: true,
|
|
923
|
+
targetPath: portablePath(codexConfigPath()),
|
|
924
|
+
backupPath: null,
|
|
925
|
+
dryRun,
|
|
926
|
+
error: null
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function authCodex(scope, dryRun, cwd, cliOptions = {}) {
|
|
931
|
+
const setupResult = installCodex(scope, '', dryRun, cwd);
|
|
932
|
+
if (!dryRun) {
|
|
933
|
+
printHostAuthIntro('codex', cliOptions);
|
|
934
|
+
runCommand('codex', ['mcp', 'login', NEUS_MCP_SERVER_NAME, '--scopes', CODEX_OAUTH_SCOPES], cwd);
|
|
935
|
+
}
|
|
936
|
+
return {
|
|
937
|
+
...setupResult,
|
|
938
|
+
authConfigured: !dryRun,
|
|
939
|
+
changed: true
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function installClient(client, scope, accessKey, dryRun, cwd) {
|
|
944
|
+
if (client === 'cursor') return installCursor(scope, accessKey, dryRun, cwd);
|
|
945
|
+
if (client === 'vscode') return installVsCode(scope, accessKey, dryRun, cwd);
|
|
946
|
+
if (client === 'claude') return installClaude(scope, accessKey, dryRun, cwd);
|
|
947
|
+
if (client === 'codex') return installCodex(scope, accessKey, dryRun, cwd);
|
|
948
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function inspectCursor(scope, cwd) {
|
|
952
|
+
const targetPath = cursorConfigPath(scope, cwd);
|
|
953
|
+
if (!fileExists(targetPath)) {
|
|
954
|
+
return {
|
|
955
|
+
client: 'cursor',
|
|
956
|
+
scope,
|
|
957
|
+
configured: false,
|
|
958
|
+
authConfigured: false,
|
|
959
|
+
targetPath,
|
|
960
|
+
error: null
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
const doc = readJsonFile(targetPath, {});
|
|
964
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
965
|
+
return {
|
|
966
|
+
client: 'cursor',
|
|
967
|
+
scope,
|
|
968
|
+
configured: Boolean(server && server.url === NEUS_MCP_URL),
|
|
969
|
+
authConfigured: Boolean(server?.headers?.Authorization),
|
|
970
|
+
targetPath,
|
|
971
|
+
error: null
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function inspectVsCode(scope, cwd) {
|
|
976
|
+
const targetPath = vscodeConfigPath(scope, cwd);
|
|
977
|
+
if (!fileExists(targetPath)) {
|
|
978
|
+
return {
|
|
979
|
+
client: 'vscode',
|
|
980
|
+
scope,
|
|
981
|
+
configured: false,
|
|
982
|
+
authConfigured: false,
|
|
983
|
+
targetPath,
|
|
984
|
+
error: null
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
const doc = readJsonFile(targetPath, {});
|
|
988
|
+
const server = doc.servers?.[NEUS_MCP_SERVER_NAME];
|
|
989
|
+
return {
|
|
990
|
+
client: 'vscode',
|
|
991
|
+
scope,
|
|
992
|
+
configured: Boolean(server && server.url === NEUS_MCP_URL),
|
|
993
|
+
authConfigured: Boolean(server?.headers?.Authorization),
|
|
994
|
+
targetPath,
|
|
995
|
+
error: null
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function inspectClaude(scope, cwd) {
|
|
1000
|
+
if (scope === 'project') {
|
|
1001
|
+
const targetPath = claudeProjectConfigPath(cwd);
|
|
1002
|
+
if (!fileExists(targetPath)) {
|
|
1003
|
+
return {
|
|
1004
|
+
client: 'claude',
|
|
1005
|
+
scope,
|
|
1006
|
+
configured: false,
|
|
1007
|
+
authConfigured: false,
|
|
1008
|
+
targetPath,
|
|
1009
|
+
error: null
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const doc = readJsonFile(targetPath, {});
|
|
1013
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
1014
|
+
return {
|
|
1015
|
+
client: 'claude',
|
|
1016
|
+
scope,
|
|
1017
|
+
configured: Boolean(server && server.url === NEUS_MCP_URL),
|
|
1018
|
+
authConfigured: Boolean(server?.headers?.Authorization),
|
|
1019
|
+
targetPath,
|
|
1020
|
+
error: null
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (!commandExists('claude')) {
|
|
1025
|
+
return {
|
|
1026
|
+
client: 'claude',
|
|
1027
|
+
scope,
|
|
1028
|
+
configured: false,
|
|
1029
|
+
authConfigured: null,
|
|
1030
|
+
targetPath: '~/.claude.json',
|
|
1031
|
+
error: null
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const result = runCommand('claude', ['mcp', 'list'], cwd, true);
|
|
1036
|
+
const configured =
|
|
1037
|
+
result.status === 0 &&
|
|
1038
|
+
result.stdout.split(/\r?\n/).some(line => line.trim() === NEUS_MCP_SERVER_NAME);
|
|
1039
|
+
return {
|
|
1040
|
+
client: 'claude',
|
|
1041
|
+
scope,
|
|
1042
|
+
configured,
|
|
1043
|
+
authConfigured: configured ? null : false,
|
|
1044
|
+
targetPath: '~/.claude.json',
|
|
1045
|
+
error: null
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function inspectCodex(scope, cwd) {
|
|
1050
|
+
const targetPath = portablePath(codexConfigPath());
|
|
1051
|
+
if (scope !== 'user') {
|
|
1052
|
+
return {
|
|
1053
|
+
client: 'codex',
|
|
1054
|
+
scope,
|
|
1055
|
+
configured: false,
|
|
1056
|
+
authConfigured: null,
|
|
1057
|
+
targetPath,
|
|
1058
|
+
error: 'Codex MCP setup is user-scoped through ~/.codex/config.toml.'
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
if (!commandExists('codex')) {
|
|
1062
|
+
return {
|
|
1063
|
+
client: 'codex',
|
|
1064
|
+
scope,
|
|
1065
|
+
configured: false,
|
|
1066
|
+
authConfigured: null,
|
|
1067
|
+
targetPath,
|
|
1068
|
+
error: null
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const result = runCommand('codex', ['mcp', 'get', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
1073
|
+
const configured =
|
|
1074
|
+
result.status === 0 &&
|
|
1075
|
+
result.stdout.split(/\r?\n/).some(line => line.trim() === `url: ${NEUS_MCP_URL}`);
|
|
1076
|
+
return {
|
|
1077
|
+
client: 'codex',
|
|
1078
|
+
scope,
|
|
1079
|
+
configured,
|
|
1080
|
+
authConfigured: configured ? null : false,
|
|
1081
|
+
targetPath,
|
|
1082
|
+
error: null
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function inspectClient(client, scope, cwd) {
|
|
1087
|
+
if (client === 'cursor') return inspectCursor(scope, cwd);
|
|
1088
|
+
if (client === 'vscode') return inspectVsCode(scope, cwd);
|
|
1089
|
+
if (client === 'claude') return inspectClaude(scope, cwd);
|
|
1090
|
+
if (client === 'codex') return inspectCodex(scope, cwd);
|
|
1091
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function createEmptyManifest(source) {
|
|
1095
|
+
return {
|
|
1096
|
+
schema: IMPORT_SCHEMA,
|
|
1097
|
+
source,
|
|
1098
|
+
generatedAt: new Date().toISOString(),
|
|
1099
|
+
instructions: [],
|
|
1100
|
+
memories: [],
|
|
1101
|
+
rules: [],
|
|
1102
|
+
skills: [],
|
|
1103
|
+
mcpServers: [],
|
|
1104
|
+
secretRefs: [],
|
|
1105
|
+
proofHints: {
|
|
1106
|
+
status: 'not-issued',
|
|
1107
|
+
qHashes: [],
|
|
1108
|
+
next: ['neus setup', 'neus auth', 'neus doctor --live']
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function sourceDetected(source) {
|
|
1114
|
+
if (source === 'cursor') {
|
|
1115
|
+
return (
|
|
1116
|
+
fileExists(path.join(process.cwd(), '.cursor', 'rules')) ||
|
|
1117
|
+
fileExists(path.join(process.cwd(), '.cursor', 'mcp.json'))
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
if (source === 'claude-code') {
|
|
1121
|
+
return (
|
|
1122
|
+
fileExists(path.join(os.homedir(), '.claude', 'skills')) ||
|
|
1123
|
+
fileExists(path.join(process.cwd(), '.claude', 'settings.json'))
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
if (source === 'claude-desktop') {
|
|
1127
|
+
return fileExists(path.join(os.homedir(), '.claude.json'));
|
|
1128
|
+
}
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function detectImportSources() {
|
|
1133
|
+
return SUPPORTED_IMPORT_SOURCES.filter(source => source !== 'auto' && sourceDetected(source)).map(
|
|
1134
|
+
source => ({
|
|
1135
|
+
source,
|
|
1136
|
+
detected: true
|
|
1137
|
+
})
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function chooseImportSource(requestedSource, detectedSources) {
|
|
1142
|
+
if (requestedSource && requestedSource !== 'auto') return requestedSource;
|
|
1143
|
+
const preference = ['claude-code', 'cursor', 'claude-desktop'];
|
|
1144
|
+
return (
|
|
1145
|
+
preference.find(source => detectedSources.some(candidate => candidate.source === source)) ||
|
|
1146
|
+
'cursor'
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function mergeManifest(base, next) {
|
|
1151
|
+
return {
|
|
1152
|
+
...base,
|
|
1153
|
+
instructions: [...base.instructions, ...next.instructions],
|
|
1154
|
+
memories: [...base.memories, ...next.memories],
|
|
1155
|
+
rules: [...base.rules, ...next.rules],
|
|
1156
|
+
skills: [...base.skills, ...next.skills],
|
|
1157
|
+
mcpServers: [...base.mcpServers, ...next.mcpServers],
|
|
1158
|
+
secretRefs: [...base.secretRefs, ...next.secretRefs]
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function buildCursorManifest(warnings) {
|
|
1163
|
+
const source = 'cursor';
|
|
1164
|
+
const manifest = createEmptyManifest(source);
|
|
1165
|
+
const rulesDir = path.join(process.cwd(), '.cursor', 'rules');
|
|
1166
|
+
for (const fileName of listFileNames(rulesDir, ['.mdc', '.md'])) {
|
|
1167
|
+
const targetPath = path.join(rulesDir, fileName);
|
|
1168
|
+
manifest.rules.push({
|
|
1169
|
+
name: fileName,
|
|
1170
|
+
source,
|
|
1171
|
+
path: portablePath(targetPath),
|
|
1172
|
+
bytes: statBytes(targetPath),
|
|
1173
|
+
sha256: sha256(readTextFile(targetPath))
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
manifest.mcpServers.push(
|
|
1177
|
+
...readMcpServers(path.join(process.cwd(), '.cursor', 'mcp.json'), source, warnings)
|
|
1178
|
+
);
|
|
1179
|
+
return manifest;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function buildClaudeCodeManifest(warnings) {
|
|
1183
|
+
const source = 'claude-code';
|
|
1184
|
+
const manifest = createEmptyManifest(source);
|
|
1185
|
+
const settings = instructionEntry(
|
|
1186
|
+
path.join(process.cwd(), '.claude', 'settings.json'),
|
|
1187
|
+
'.claude/settings.json'
|
|
1188
|
+
);
|
|
1189
|
+
if (settings) manifest.rules.push({ ...settings, source });
|
|
1190
|
+
for (const skillName of listDirectoryNames(path.join(os.homedir(), '.claude', 'skills'))) {
|
|
1191
|
+
manifest.skills.push({
|
|
1192
|
+
name: skillName,
|
|
1193
|
+
kind: 'skill',
|
|
1194
|
+
source,
|
|
1195
|
+
path: portablePath(path.join(os.homedir(), '.claude', 'skills', skillName)),
|
|
1196
|
+
hasSkillMd: fileExists(path.join(os.homedir(), '.claude', 'skills', skillName, 'SKILL.md'))
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
manifest.mcpServers.push(
|
|
1200
|
+
...readMcpServers(path.join(process.cwd(), '.mcp.json'), source, warnings)
|
|
1201
|
+
);
|
|
1202
|
+
return manifest;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function buildClaudeDesktopManifest(warnings) {
|
|
1206
|
+
const source = 'claude-desktop';
|
|
1207
|
+
const manifest = createEmptyManifest(source);
|
|
1208
|
+
manifest.mcpServers.push(
|
|
1209
|
+
...readMcpServers(path.join(os.homedir(), '.claude.json'), source, warnings)
|
|
1210
|
+
);
|
|
1211
|
+
return manifest;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function buildSourceManifest(source, warnings) {
|
|
1215
|
+
if (source === 'cursor') return buildCursorManifest(warnings);
|
|
1216
|
+
if (source === 'claude-code') return buildClaudeCodeManifest(warnings);
|
|
1217
|
+
if (source === 'claude-desktop') return buildClaudeDesktopManifest(warnings);
|
|
1218
|
+
throw new Error(`Unsupported import source: ${source}`);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function buildPortableManifest(requestedSource) {
|
|
1222
|
+
const warnings = [];
|
|
1223
|
+
const detectedSources = detectImportSources();
|
|
1224
|
+
const selectedSource = chooseImportSource(requestedSource, detectedSources);
|
|
1225
|
+
let manifest = buildSourceManifest(selectedSource, warnings);
|
|
1226
|
+
|
|
1227
|
+
if (requestedSource === 'auto') {
|
|
1228
|
+
for (const candidate of detectedSources) {
|
|
1229
|
+
if (candidate.source === selectedSource) continue;
|
|
1230
|
+
manifest = mergeManifest(manifest, buildSourceManifest(candidate.source, warnings));
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
manifest.generatedAt = new Date().toISOString();
|
|
1235
|
+
return { manifest, detectedSources, warnings, selectedSource };
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function importedManifestPath(source, cwd) {
|
|
1239
|
+
return path.join(cwd, '.neus', 'imported', `${source}.json`);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function latestImportedManifest(cwd) {
|
|
1243
|
+
const dir = path.join(cwd, '.neus', 'imported');
|
|
1244
|
+
if (!fileExists(dir)) return null;
|
|
1245
|
+
const candidates = fs
|
|
1246
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
1247
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.json'))
|
|
1248
|
+
.map(entry => path.join(dir, entry.name))
|
|
1249
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
1250
|
+
return candidates[0] || null;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function printJson(payload) {
|
|
1254
|
+
process.stdout.write(jsonStringify(payload));
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function clientTargetPath(client, scope, cwd) {
|
|
1258
|
+
if (client === 'cursor') return cursorConfigPath(scope, cwd);
|
|
1259
|
+
if (client === 'vscode') return vscodeConfigPath(scope, cwd);
|
|
1260
|
+
if (client === 'claude') {
|
|
1261
|
+
return scope === 'project' ? claudeProjectConfigPath(cwd) : '~/.claude.json';
|
|
1262
|
+
}
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function errorMessage(error) {
|
|
1267
|
+
return error instanceof Error ? error.message : String(error || 'Unknown error');
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function parseSseMessages(text) {
|
|
1271
|
+
const messages = [];
|
|
1272
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
1273
|
+
if (!line.startsWith('data:')) continue;
|
|
1274
|
+
const payload = line.slice(5).trim();
|
|
1275
|
+
if (!payload) continue;
|
|
1276
|
+
try {
|
|
1277
|
+
messages.push(JSON.parse(payload));
|
|
1278
|
+
} catch {
|
|
1279
|
+
// Ignore malformed SSE fragments. The caller will report the raw body preview.
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return messages;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function parseMcpResponse(text) {
|
|
1286
|
+
const trimmed = String(text || '').trim();
|
|
1287
|
+
if (!trimmed) return null;
|
|
1288
|
+
try {
|
|
1289
|
+
return JSON.parse(trimmed);
|
|
1290
|
+
} catch {
|
|
1291
|
+
return parseSseMessages(trimmed)[0] || null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function firstTextContent(value) {
|
|
1296
|
+
const content = value?.result?.content ?? value?.content;
|
|
1297
|
+
if (!Array.isArray(content)) return '';
|
|
1298
|
+
const first = content.find(item => item?.type === 'text' && typeof item?.text === 'string');
|
|
1299
|
+
return first?.text || '';
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function parseMcpToolPayload(value) {
|
|
1303
|
+
const text = firstTextContent(value);
|
|
1304
|
+
if (text) {
|
|
1305
|
+
try {
|
|
1306
|
+
return JSON.parse(text);
|
|
1307
|
+
} catch {
|
|
1308
|
+
return { text };
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return value?.result ?? value;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
async function postMcpJsonRpc({ id, method, params, accessKey, sessionId, signal }) {
|
|
1315
|
+
const response = await fetch(NEUS_MCP_URL, {
|
|
1316
|
+
method: 'POST',
|
|
1317
|
+
headers: {
|
|
1318
|
+
accept: 'application/json, text/event-stream',
|
|
1319
|
+
'content-type': 'application/json',
|
|
1320
|
+
'mcp-protocol-version': '2025-11-25',
|
|
1321
|
+
...(accessKey ? { authorization: `Bearer ${accessKey}` } : {}),
|
|
1322
|
+
...(sessionId ? { 'mcp-session-id': sessionId } : {})
|
|
1323
|
+
},
|
|
1324
|
+
body: JSON.stringify({
|
|
1325
|
+
jsonrpc: '2.0',
|
|
1326
|
+
id,
|
|
1327
|
+
method,
|
|
1328
|
+
params: params ?? {}
|
|
1329
|
+
}),
|
|
1330
|
+
signal
|
|
1331
|
+
});
|
|
1332
|
+
const body = await response.text();
|
|
1333
|
+
return {
|
|
1334
|
+
response,
|
|
1335
|
+
body,
|
|
1336
|
+
json: parseMcpResponse(body),
|
|
1337
|
+
sessionId: response.headers.get('mcp-session-id') || sessionId || ''
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
async function callMcpTool({ name, args, accessKey, sessionId, signal }) {
|
|
1342
|
+
const result = await postMcpJsonRpc({
|
|
1343
|
+
id: 3,
|
|
1344
|
+
method: 'tools/call',
|
|
1345
|
+
params: { name, arguments: args ?? {} },
|
|
1346
|
+
accessKey,
|
|
1347
|
+
sessionId,
|
|
1348
|
+
signal
|
|
1349
|
+
});
|
|
1350
|
+
if (!result.response.ok || result.json?.error) {
|
|
1351
|
+
return {
|
|
1352
|
+
ok: false,
|
|
1353
|
+
name,
|
|
1354
|
+
status: result.response.status,
|
|
1355
|
+
error: result.json?.error?.message || result.json?.error || result.body.slice(0, 200)
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
return {
|
|
1359
|
+
ok: true,
|
|
1360
|
+
name,
|
|
1361
|
+
payload: parseMcpToolPayload(result.json)
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
async function runLiveMcpDiagnostics(accessKey) {
|
|
1366
|
+
if (!accessKey) {
|
|
1367
|
+
return {
|
|
1368
|
+
live: false,
|
|
1369
|
+
reachable: false,
|
|
1370
|
+
authenticated: false,
|
|
1371
|
+
toolsCount: 0,
|
|
1372
|
+
tools: [],
|
|
1373
|
+
checks: [{ name: 'access-key', ok: false, status: 'missing' }]
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const controller = new AbortController();
|
|
1378
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
1379
|
+
try {
|
|
1380
|
+
const init = await postMcpJsonRpc({
|
|
1381
|
+
id: 1,
|
|
1382
|
+
method: 'initialize',
|
|
1383
|
+
params: {
|
|
1384
|
+
protocolVersion: '2025-11-25',
|
|
1385
|
+
capabilities: {},
|
|
1386
|
+
clientInfo: { name: 'neus-cli', version: '1.0.0' }
|
|
1387
|
+
},
|
|
1388
|
+
accessKey,
|
|
1389
|
+
signal: controller.signal
|
|
1390
|
+
});
|
|
1391
|
+
if (!init.response.ok || init.json?.error) {
|
|
1392
|
+
return {
|
|
1393
|
+
live: true,
|
|
1394
|
+
reachable: false,
|
|
1395
|
+
authenticated: false,
|
|
1396
|
+
toolsCount: 0,
|
|
1397
|
+
tools: [],
|
|
1398
|
+
checks: [
|
|
1399
|
+
{
|
|
1400
|
+
name: 'initialize',
|
|
1401
|
+
ok: false,
|
|
1402
|
+
status: init.response.status,
|
|
1403
|
+
error: init.json?.error?.message || init.body.slice(0, 200)
|
|
1404
|
+
}
|
|
1405
|
+
]
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const list = await postMcpJsonRpc({
|
|
1410
|
+
id: 2,
|
|
1411
|
+
method: 'tools/list',
|
|
1412
|
+
params: {},
|
|
1413
|
+
accessKey,
|
|
1414
|
+
sessionId: init.sessionId,
|
|
1415
|
+
signal: controller.signal
|
|
1416
|
+
});
|
|
1417
|
+
const tools = list.json?.result?.tools ?? list.json?.tools ?? [];
|
|
1418
|
+
const toolNames = Array.isArray(tools) ? tools.map(tool => tool.name).filter(Boolean) : [];
|
|
1419
|
+
const context = await callMcpTool({
|
|
1420
|
+
name: 'neus_context',
|
|
1421
|
+
args: {},
|
|
1422
|
+
accessKey,
|
|
1423
|
+
sessionId: init.sessionId,
|
|
1424
|
+
signal: controller.signal
|
|
1425
|
+
});
|
|
1426
|
+
const mode = context.ok ? context.payload?.mode?.current || context.payload?.mode || '' : '';
|
|
1427
|
+
return {
|
|
1428
|
+
live: true,
|
|
1429
|
+
reachable: true,
|
|
1430
|
+
authenticated: Boolean(accessKey) && context.ok,
|
|
1431
|
+
toolsCount: toolNames.length,
|
|
1432
|
+
tools: toolNames,
|
|
1433
|
+
contextMode: mode,
|
|
1434
|
+
checks: [
|
|
1435
|
+
{
|
|
1436
|
+
name: 'initialize',
|
|
1437
|
+
ok: true,
|
|
1438
|
+
protocolVersion: init.json?.result?.protocolVersion || null
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
name: 'tools/list',
|
|
1442
|
+
ok: list.response.ok && !list.json?.error,
|
|
1443
|
+
status: list.response.status,
|
|
1444
|
+
toolsCount: toolNames.length
|
|
1445
|
+
},
|
|
1446
|
+
{ name: 'neus_context', ok: context.ok, mode }
|
|
1447
|
+
]
|
|
1448
|
+
};
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
return {
|
|
1451
|
+
live: true,
|
|
1452
|
+
reachable: false,
|
|
1453
|
+
authenticated: false,
|
|
1454
|
+
toolsCount: 0,
|
|
1455
|
+
tools: [],
|
|
1456
|
+
checks: [{ name: 'network', ok: false, error: errorMessage(error) }]
|
|
1457
|
+
};
|
|
1458
|
+
} finally {
|
|
1459
|
+
clearTimeout(timeout);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function buildClientFailure(client, scope, cwd, dryRun, error) {
|
|
1464
|
+
return {
|
|
1465
|
+
client,
|
|
1466
|
+
scope,
|
|
1467
|
+
configured: false,
|
|
1468
|
+
authConfigured: false,
|
|
1469
|
+
changed: false,
|
|
1470
|
+
targetPath: clientTargetPath(client, scope, cwd),
|
|
1471
|
+
backupPath: null,
|
|
1472
|
+
dryRun,
|
|
1473
|
+
error: errorMessage(error)
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function runClientOperations(clients, scope, cwd, dryRun, runner) {
|
|
1478
|
+
return clients.map(client => {
|
|
1479
|
+
try {
|
|
1480
|
+
return runner(client);
|
|
1481
|
+
} catch (error) {
|
|
1482
|
+
return buildClientFailure(client, scope, cwd, dryRun, error);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
function printImportSummary(payload, cliOptions = {}) {
|
|
1489
|
+
emitCliBanner(cliOptions);
|
|
1490
|
+
const manifest = payload.manifest;
|
|
1491
|
+
writeCliLine(paint('import', 'green'));
|
|
1492
|
+
logStep('ok', 'source', `${manifest.source}${payload.dryRun ? ' (dry run)' : ''}`);
|
|
1493
|
+
logStep('ok', 'skills', String(manifest.skills.length));
|
|
1494
|
+
logStep('ok', 'servers', String(manifest.mcpServers.length));
|
|
1495
|
+
writeCliLine('');
|
|
1496
|
+
logStep('next', 'next', 'neus setup | neus auth');
|
|
1497
|
+
writeCliLine('');
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function printExportSummary(payload, cliOptions = {}) {
|
|
1501
|
+
emitCliBanner(cliOptions);
|
|
1502
|
+
writeCliLine(paint('export', 'green'));
|
|
1503
|
+
logStep('ok', 'format', payload.format);
|
|
1504
|
+
logStep('ok', 'source', payload.manifest.source);
|
|
1505
|
+
if (payload.outputPath) {
|
|
1506
|
+
logStep('ok', 'output', payload.outputPath);
|
|
1507
|
+
}
|
|
1508
|
+
writeCliLine('');
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function runInit(options) {
|
|
1512
|
+
const scope = resolveScope(options);
|
|
1513
|
+
const accessKey = resolveAccessKey(options);
|
|
1514
|
+
ensureSafeAuth('init', scope, accessKey);
|
|
1515
|
+
const cwd = process.cwd();
|
|
1516
|
+
|
|
1517
|
+
const clients = resolveClients(scope, options.clients);
|
|
1518
|
+
ensureClientSelection(scope, clients);
|
|
1519
|
+
|
|
1520
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1521
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1522
|
+
);
|
|
1523
|
+
const payload = {
|
|
1524
|
+
command: 'init',
|
|
1525
|
+
scope,
|
|
1526
|
+
detectedClients: defaultUserClients(),
|
|
1527
|
+
clients,
|
|
1528
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
1529
|
+
results,
|
|
1530
|
+
hasErrors: results.some(result => result.error)
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
if (options.json) {
|
|
1534
|
+
printJson(payload);
|
|
1535
|
+
} else {
|
|
1536
|
+
printFlowSummary('init', scope, results, {
|
|
1537
|
+
nextStep: accessKey ? '' : 'neus auth',
|
|
1538
|
+
cliOptions: options
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
if (payload.hasErrors) {
|
|
1543
|
+
process.exitCode = 1;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const NEUS_OAUTH_CLIENT_ID = 'neus-cli';
|
|
1548
|
+
const NEUS_MCP_RESOURCE = 'https://mcp.neus.network/mcp';
|
|
1549
|
+
|
|
1550
|
+
function base64url(buffer) {
|
|
1551
|
+
return Buffer.from(buffer)
|
|
1552
|
+
.toString('base64')
|
|
1553
|
+
.replace(/\+/g, '-')
|
|
1554
|
+
.replace(/\//g, '_')
|
|
1555
|
+
.replace(/=+$/, '');
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function generateCodeVerifier() {
|
|
1559
|
+
return base64url(randomBytes(32));
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function deriveCodeChallenge(verifier) {
|
|
1563
|
+
return base64url(createHash('sha256').update(verifier).digest());
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
async function runAuthBrowser(options) {
|
|
1567
|
+
const scope = resolveScope(options);
|
|
1568
|
+
if (scope !== 'user') {
|
|
1569
|
+
throw new Error('Browser auth only supports user scope. Remove --project flag.');
|
|
1570
|
+
}
|
|
1571
|
+
const clients = resolveClients(scope, options.clients);
|
|
1572
|
+
ensureClientSelection(scope, clients);
|
|
1573
|
+
const browserManagedClients = clients.filter(client => client !== 'codex');
|
|
1574
|
+
const hostManagedClients = clients.filter(client => client === 'codex');
|
|
1575
|
+
const cwd = process.cwd();
|
|
1576
|
+
|
|
1577
|
+
const { createServer } = await import('node:http');
|
|
1578
|
+
|
|
1579
|
+
const csrfState = randomBytes(16).toString('hex');
|
|
1580
|
+
const codeVerifier = generateCodeVerifier();
|
|
1581
|
+
const codeChallenge = deriveCodeChallenge(codeVerifier);
|
|
1582
|
+
|
|
1583
|
+
return new Promise((resolve, reject) => {
|
|
1584
|
+
const server = createServer((req, res) => {
|
|
1585
|
+
const url = new URL(req.url, `http://127.0.0.1:${server.address().port}`);
|
|
1586
|
+
if (url.pathname === '/callback') {
|
|
1587
|
+
const returnedState = url.searchParams.get('state');
|
|
1588
|
+
if (!returnedState || returnedState !== csrfState) {
|
|
1589
|
+
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
1590
|
+
res.end('<html><body><h2>Security check failed</h2><p>Invalid request. Try again.</p></body></html>');
|
|
1591
|
+
server.close();
|
|
1592
|
+
reject(new Error('CSRF state mismatch'));
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
const code = url.searchParams.get('code');
|
|
1597
|
+
const error = url.searchParams.get('error');
|
|
1598
|
+
|
|
1599
|
+
if (error) {
|
|
1600
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1601
|
+
res.end('<html><body><h2>Authentication failed</h2><p>You can close this tab and try again.</p></body></html>');
|
|
1602
|
+
server.close();
|
|
1603
|
+
reject(new Error(`Authentication failed: ${error}`));
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
if (!code) {
|
|
1608
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1609
|
+
res.end('<html><body><h2>Missing auth code</h2><p>You can close this tab and try again.</p></body></html>');
|
|
1610
|
+
server.close();
|
|
1611
|
+
reject(new Error('No auth code received from callback'));
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
|
|
1616
|
+
const params = new URLSearchParams();
|
|
1617
|
+
params.set('grant_type', 'authorization_code');
|
|
1618
|
+
params.set('code', code);
|
|
1619
|
+
params.set('redirect_uri', redirectUri);
|
|
1620
|
+
params.set('client_id', NEUS_OAUTH_CLIENT_ID);
|
|
1621
|
+
params.set('code_verifier', codeVerifier);
|
|
1622
|
+
params.set('resource', NEUS_MCP_RESOURCE);
|
|
1623
|
+
|
|
1624
|
+
fetch(NEUS_TOKEN_ENDPOINT, {
|
|
1625
|
+
method: 'POST',
|
|
1626
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
1627
|
+
body: params.toString(),
|
|
1628
|
+
signal: AbortSignal.timeout(15_000),
|
|
1629
|
+
})
|
|
1630
|
+
.then(tokenResp => tokenResp.json())
|
|
1631
|
+
.then(tokenJson => {
|
|
1632
|
+
if (!tokenJson.access_token) {
|
|
1633
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1634
|
+
res.end('<html><body><h2>Token exchange failed</h2><p>Please try again.</p></body></html>');
|
|
1635
|
+
server.close();
|
|
1636
|
+
reject(new Error(tokenJson.error_description || tokenJson.error || 'Token exchange failed'));
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
const accessToken = tokenJson.access_token;
|
|
1641
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1642
|
+
res.end('<html><body><h2>Authenticated</h2><p>You can close this tab and return to your terminal.</p></body></html>');
|
|
1643
|
+
server.close();
|
|
1644
|
+
|
|
1645
|
+
const results = runClientOperations(browserManagedClients, scope, cwd, options.dryRun, client =>
|
|
1646
|
+
installClient(client, scope, accessToken, options.dryRun, cwd)
|
|
1647
|
+
);
|
|
1648
|
+
results.push(
|
|
1649
|
+
...runClientOperations(hostManagedClients, scope, cwd, options.dryRun, () =>
|
|
1650
|
+
authCodex(scope, options.dryRun, cwd, options)
|
|
1651
|
+
)
|
|
1652
|
+
);
|
|
1653
|
+
const payload = {
|
|
1654
|
+
command: 'auth',
|
|
1655
|
+
scope,
|
|
1656
|
+
clients,
|
|
1657
|
+
accessKeyConfigured: true,
|
|
1658
|
+
authMethod: 'browser',
|
|
1659
|
+
results,
|
|
1660
|
+
hasErrors: results.some(result => result.error)
|
|
1661
|
+
};
|
|
1662
|
+
resolve(payload);
|
|
1663
|
+
})
|
|
1664
|
+
.catch(err => {
|
|
1665
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1666
|
+
res.end('<html><body><h2>Connection error</h2><p>Please try again.</p></body></html>');
|
|
1667
|
+
server.close();
|
|
1668
|
+
reject(err);
|
|
1669
|
+
});
|
|
1670
|
+
} else {
|
|
1671
|
+
res.writeHead(404);
|
|
1672
|
+
res.end();
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
server.listen(0, '127.0.0.1', () => {
|
|
1677
|
+
const port = server.address().port;
|
|
1678
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
1679
|
+
const authParams = new URLSearchParams({
|
|
1680
|
+
response_type: 'code',
|
|
1681
|
+
client_id: NEUS_OAUTH_CLIENT_ID,
|
|
1682
|
+
redirect_uri: redirectUri,
|
|
1683
|
+
code_challenge: codeChallenge,
|
|
1684
|
+
code_challenge_method: 'S256',
|
|
1685
|
+
state: csrfState,
|
|
1686
|
+
scope: 'neus:core neus:profile neus:secrets offline_access',
|
|
1687
|
+
resource: NEUS_MCP_RESOURCE
|
|
1688
|
+
});
|
|
1689
|
+
const authUrl = `${NEUS_APP_URL}/oauth/authorize?${authParams.toString()}`;
|
|
1690
|
+
|
|
1691
|
+
if (!options.json) {
|
|
1692
|
+
printAuthBrowserIntro(authUrl, options);
|
|
1693
|
+
logStep('next', 'wait', 'finish sign-in in the browser');
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
const { exec } = require('node:child_process');
|
|
1697
|
+
const openCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
1698
|
+
exec(`${openCmd} "${authUrl}"`, err => {
|
|
1699
|
+
if (err && !options.json) {
|
|
1700
|
+
logStep('warn', 'browser', 'open the URL above manually');
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
// Timeout after 5 minutes
|
|
1706
|
+
setTimeout(() => {
|
|
1707
|
+
server.close();
|
|
1708
|
+
reject(new Error('Authentication timed out after 5 minutes. Try again.'));
|
|
1709
|
+
}, 5 * 60 * 1000);
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function runAuth(options) {
|
|
1714
|
+
const scope = resolveScope(options);
|
|
1715
|
+
const accessKey = resolveAccessKey(options);
|
|
1716
|
+
ensureSafeAuth('auth', scope, accessKey);
|
|
1717
|
+
const cwd = process.cwd();
|
|
1718
|
+
const clients = resolveClients(scope, options.clients);
|
|
1719
|
+
ensureClientSelection(scope, clients);
|
|
1720
|
+
|
|
1721
|
+
if (!accessKey) {
|
|
1722
|
+
if (clients.length === 1 && clients[0] === 'codex') {
|
|
1723
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, () =>
|
|
1724
|
+
authCodex(scope, options.dryRun, cwd, options)
|
|
1725
|
+
);
|
|
1726
|
+
return {
|
|
1727
|
+
command: 'auth',
|
|
1728
|
+
scope,
|
|
1729
|
+
clients,
|
|
1730
|
+
accessKeyConfigured: results.some(result => result.authConfigured === true),
|
|
1731
|
+
authMethod: 'host-oauth',
|
|
1732
|
+
results,
|
|
1733
|
+
hasErrors: results.some(result => result.error)
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
return runAuthBrowser(options);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1740
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1741
|
+
);
|
|
1742
|
+
const payload = {
|
|
1743
|
+
command: 'auth',
|
|
1744
|
+
scope,
|
|
1745
|
+
clients,
|
|
1746
|
+
accessKeyConfigured: true,
|
|
1747
|
+
authMethod: resolveAuthMethod(options, accessKey),
|
|
1748
|
+
results,
|
|
1749
|
+
hasErrors: results.some(result => result.error)
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1752
|
+
return payload;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function runStatus(options) {
|
|
1756
|
+
const scope = resolveScope(options);
|
|
1757
|
+
const cwd = process.cwd();
|
|
1758
|
+
const clients = resolveClients(scope, options.clients);
|
|
1759
|
+
ensureClientSelection(scope, clients);
|
|
1760
|
+
|
|
1761
|
+
const inspected = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1762
|
+
inspectClient(client, scope, cwd)
|
|
1763
|
+
);
|
|
1764
|
+
const payload = {
|
|
1765
|
+
command: 'status',
|
|
1766
|
+
scope,
|
|
1767
|
+
clients: inspected,
|
|
1768
|
+
hasErrors: inspected.some(result => result.error)
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
if (options.json) {
|
|
1772
|
+
printJson(payload);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
printFlowSummary('status', scope, inspected, { cliOptions: options });
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
async function runSetup(options) {
|
|
1779
|
+
const scope = resolveScope(options);
|
|
1780
|
+
const accessKey = resolveAccessKey(options);
|
|
1781
|
+
ensureSafeAuth('setup', scope, accessKey);
|
|
1782
|
+
const cwd = process.cwd();
|
|
1783
|
+
if (options.project && accessKey) {
|
|
1784
|
+
throw new Error(
|
|
1785
|
+
'Access keys are only supported in user scope. Remove --project or omit --access-key.'
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
const clients = resolveClients(scope, options.clients);
|
|
1790
|
+
ensureClientSelection(scope, clients);
|
|
1791
|
+
const initResults = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1792
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1793
|
+
);
|
|
1794
|
+
|
|
1795
|
+
const payload = {
|
|
1796
|
+
command: 'setup',
|
|
1797
|
+
scope,
|
|
1798
|
+
detectedClients: defaultUserClients(),
|
|
1799
|
+
clients,
|
|
1800
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
1801
|
+
results: initResults,
|
|
1802
|
+
hasErrors: initResults.some(result => result.error)
|
|
1803
|
+
};
|
|
1804
|
+
|
|
1805
|
+
if (payload.hasErrors) {
|
|
1806
|
+
if (options.json) printJson(payload);
|
|
1807
|
+
else printFlowSummary('setup', scope, initResults, { cliOptions: options });
|
|
1808
|
+
process.exitCode = 1;
|
|
1809
|
+
return payload;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
if (options.json) {
|
|
1813
|
+
printJson(payload);
|
|
1814
|
+
return payload;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
printFlowSummary('setup', scope, initResults, {
|
|
1818
|
+
nextStep: accessKey ? 'Open your MCP client and ask the assistant to use NEUS Trust.' : '',
|
|
1819
|
+
cliOptions: options
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
if (!accessKey && !options.dryRun) {
|
|
1823
|
+
const authResult = await runAuth(options);
|
|
1824
|
+
if (authResult && !authResult.hasErrors) {
|
|
1825
|
+
printFlowSummary('auth', authResult.scope, authResult.results, {
|
|
1826
|
+
nextStep: 'Open your MCP client and ask the assistant to use NEUS Trust.',
|
|
1827
|
+
cliOptions: options
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
if (authResult?.hasErrors) {
|
|
1831
|
+
process.exitCode = 1;
|
|
1832
|
+
}
|
|
1833
|
+
return authResult || payload;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
return payload;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
function runImport(options, { emitOutput = true } = {}) {
|
|
1840
|
+
if (!SUPPORTED_IMPORT_SOURCES.includes(options.source)) {
|
|
1841
|
+
throw new Error(`Unsupported import source: ${options.source}`);
|
|
1842
|
+
}
|
|
1843
|
+
const cwd = process.cwd();
|
|
1844
|
+
const { manifest, detectedSources, warnings } = buildPortableManifest(options.source);
|
|
1845
|
+
const targetPath = importedManifestPath(manifest.source, cwd);
|
|
1846
|
+
const writeResult = writeJsonFile(targetPath, manifest, options.dryRun);
|
|
1847
|
+
const payload = {
|
|
1848
|
+
command: 'import',
|
|
1849
|
+
source: options.source,
|
|
1850
|
+
selectedSource: manifest.source,
|
|
1851
|
+
dryRun: options.dryRun,
|
|
1852
|
+
detectedSources,
|
|
1853
|
+
manifest,
|
|
1854
|
+
targetPath,
|
|
1855
|
+
changed: writeResult.changed,
|
|
1856
|
+
warnings,
|
|
1857
|
+
hasErrors:
|
|
1858
|
+
manifest.instructions.length === 0 &&
|
|
1859
|
+
manifest.skills.length === 0 &&
|
|
1860
|
+
manifest.rules.length === 0 &&
|
|
1861
|
+
manifest.mcpServers.length === 0
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1864
|
+
if (emitOutput) {
|
|
1865
|
+
if (options.json) {
|
|
1866
|
+
printJson(payload);
|
|
1867
|
+
} else {
|
|
1868
|
+
printImportSummary(payload, options);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
if (emitOutput && payload.hasErrors) {
|
|
1873
|
+
process.exitCode = 1;
|
|
1874
|
+
}
|
|
1875
|
+
return payload;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
function runExport(options) {
|
|
1879
|
+
if (!SUPPORTED_EXPORT_FORMATS.includes(options.format)) {
|
|
1880
|
+
throw new Error(`Unsupported export format: ${options.format}`);
|
|
1881
|
+
}
|
|
1882
|
+
const cwd = process.cwd();
|
|
1883
|
+
const sourcePath = latestImportedManifest(cwd);
|
|
1884
|
+
if (!sourcePath) {
|
|
1885
|
+
throw new Error(
|
|
1886
|
+
'No local NEUS portable agent manifest found. Run `neus import --dry-run` first, then `neus import` to write one.'
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
const manifest = readJsonFile(sourcePath, null);
|
|
1890
|
+
if (!manifest || manifest.schema !== IMPORT_SCHEMA) {
|
|
1891
|
+
throw new Error(`Invalid NEUS portable agent manifest at ${sourcePath}`);
|
|
1892
|
+
}
|
|
1893
|
+
const outputPath = options.output ? path.resolve(cwd, options.output) : '';
|
|
1894
|
+
if (outputPath && !options.dryRun) {
|
|
1895
|
+
writeJsonFile(outputPath, manifest, false);
|
|
1896
|
+
}
|
|
1897
|
+
const payload = {
|
|
1898
|
+
command: 'export',
|
|
1899
|
+
format: options.format,
|
|
1900
|
+
sourcePath,
|
|
1901
|
+
outputPath,
|
|
1902
|
+
dryRun: options.dryRun,
|
|
1903
|
+
manifest
|
|
1904
|
+
};
|
|
1905
|
+
|
|
1906
|
+
if (options.json) {
|
|
1907
|
+
printJson(payload);
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
printExportSummary(payload, options);
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
async function runDoctor(options) {
|
|
1914
|
+
const scope = resolveScope(options);
|
|
1915
|
+
const cwd = process.cwd();
|
|
1916
|
+
const clients = resolveClients(scope, options.clients);
|
|
1917
|
+
ensureClientSelection(scope, clients);
|
|
1918
|
+
|
|
1919
|
+
const inspected = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1920
|
+
inspectClient(client, scope, cwd)
|
|
1921
|
+
);
|
|
1922
|
+
const configuredClients = inspected.filter(r => r.configured);
|
|
1923
|
+
const liveAccessKey = resolveLiveAccessKey(options, scope, cwd);
|
|
1924
|
+
const payload = {
|
|
1925
|
+
command: 'doctor',
|
|
1926
|
+
scope,
|
|
1927
|
+
clients: inspected,
|
|
1928
|
+
configuredCount: configuredClients.length,
|
|
1929
|
+
accessKeyPresent: Boolean(liveAccessKey),
|
|
1930
|
+
profileConnectable: false,
|
|
1931
|
+
agentVerified: false,
|
|
1932
|
+
live: options.live,
|
|
1933
|
+
mcp: null,
|
|
1934
|
+
summary: '',
|
|
1935
|
+
hasErrors: inspected.some(result => result.error)
|
|
1936
|
+
};
|
|
1937
|
+
|
|
1938
|
+
if (options.live) {
|
|
1939
|
+
payload.mcp = await runLiveMcpDiagnostics(liveAccessKey);
|
|
1940
|
+
if (liveAccessKey) {
|
|
1941
|
+
payload.profileConnectable = Boolean(payload.mcp.authenticated);
|
|
1942
|
+
payload.hasErrors =
|
|
1943
|
+
payload.hasErrors || !payload.mcp.reachable || !payload.mcp.authenticated;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
if (options.json) {
|
|
1948
|
+
printJson(payload);
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
if (configuredClients.length === 0) {
|
|
1953
|
+
emitCliBanner(options);
|
|
1954
|
+
writeCliLine(paint('doctor', 'green'));
|
|
1955
|
+
for (const result of inspected) {
|
|
1956
|
+
if (result.error) {
|
|
1957
|
+
logStep('warn', result.client, result.error);
|
|
1958
|
+
} else if (result.authConfigured === null) {
|
|
1959
|
+
logStep('skip', result.client, 'not installed');
|
|
1960
|
+
} else {
|
|
1961
|
+
logStep('skip', result.client, 'not configured');
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
writeCliLine('');
|
|
1965
|
+
writeCliLine(paint('MCP endpoint', 'cyan'));
|
|
1966
|
+
writeGuidanceLine(NEUS_MCP_URL);
|
|
1967
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
1968
|
+
writeGuidanceLine(`No selected MCP host is configured yet. Run \`${preferredSetupCommand(inspected)}\`.`);
|
|
1969
|
+
writeGuidanceLine(`Then run \`${preferredAuthCommand(inspected)}\` and re-check with \`npx -y -p @neus/sdk neus doctor --live\`.`);
|
|
1970
|
+
writeCliLine('');
|
|
1971
|
+
process.exitCode = 1;
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
printFlowSummary('doctor', scope, inspected, { cliOptions: options });
|
|
1976
|
+
const hasCodex = inspected.some(result => result.client === 'codex');
|
|
1977
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
1978
|
+
if (options.live && payload.mcp) {
|
|
1979
|
+
if (!liveAccessKey) {
|
|
1980
|
+
writeGuidanceLine(
|
|
1981
|
+
hasCodex
|
|
1982
|
+
? 'Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.'
|
|
1983
|
+
: 'No account credential found for the configured MCP clients. Run `neus auth`.'
|
|
1984
|
+
);
|
|
1985
|
+
} else {
|
|
1986
|
+
logStep(
|
|
1987
|
+
payload.mcp.authenticated ? 'ok' : 'warn',
|
|
1988
|
+
'profile',
|
|
1989
|
+
payload.mcp.authenticated
|
|
1990
|
+
? `live MCP context confirmed; ${payload.mcp.toolsCount || 0} tools discovered`
|
|
1991
|
+
: 'live MCP context was not confirmed'
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
} else if (liveAccessKey) {
|
|
1995
|
+
writeGuidanceLine('Saved credential found. Run `neus doctor --live` to confirm Profile context.');
|
|
1996
|
+
} else {
|
|
1997
|
+
writeGuidanceLine(
|
|
1998
|
+
hasCodex
|
|
1999
|
+
? 'Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.'
|
|
2000
|
+
: 'No account credential found. Run `neus auth` for browser sign-in.'
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
writeCliLine('');
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
async function runDisconnect(options) {
|
|
2007
|
+
const scope = resolveScope(options);
|
|
2008
|
+
if (scope !== 'user') {
|
|
2009
|
+
throw new Error('Disconnect only supports user scope. Remove --project flag.');
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
const cwd = process.cwd();
|
|
2013
|
+
const token = resolveLiveAccessKey(options, scope, cwd);
|
|
2014
|
+
if (!token) {
|
|
2015
|
+
throw new Error(
|
|
2016
|
+
'Credential required. Run `neus disconnect --access-key <token>` or sign in first (`neus auth`).'
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
try {
|
|
2021
|
+
const isProfileKey = token.startsWith('npk_');
|
|
2022
|
+
const resp = isProfileKey
|
|
2023
|
+
? await fetch(NEUS_PROFILE_KEY_ENDPOINT, {
|
|
2024
|
+
method: 'DELETE',
|
|
2025
|
+
headers: {
|
|
2026
|
+
Accept: 'application/json',
|
|
2027
|
+
Authorization: `Bearer ${token}`
|
|
2028
|
+
},
|
|
2029
|
+
signal: AbortSignal.timeout(10_000),
|
|
2030
|
+
})
|
|
2031
|
+
: await fetch(NEUS_DISCONNECT_ENDPOINT, {
|
|
2032
|
+
method: 'POST',
|
|
2033
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
2034
|
+
body: new URLSearchParams({
|
|
2035
|
+
token,
|
|
2036
|
+
token_type_hint: 'access_token',
|
|
2037
|
+
client_id: NEUS_OAUTH_CLIENT_ID
|
|
2038
|
+
}).toString(),
|
|
2039
|
+
signal: AbortSignal.timeout(10_000),
|
|
2040
|
+
});
|
|
2041
|
+
|
|
2042
|
+
if (!resp.ok) {
|
|
2043
|
+
const body = await resp.json().catch(() => ({}));
|
|
2044
|
+
throw new Error(body?.error?.message || `Disconnect failed with status ${resp.status}`);
|
|
2045
|
+
}
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
if (error.message && !error.message.includes('Disconnect failed')) {
|
|
2048
|
+
throw new Error(`Disconnect request failed: ${error.message}`);
|
|
2049
|
+
}
|
|
2050
|
+
throw error;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
const clients = resolveClients(scope, options.clients);
|
|
2054
|
+
ensureClientSelection(scope, clients);
|
|
2055
|
+
|
|
2056
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
2057
|
+
installClient(client, scope, '', options.dryRun, cwd)
|
|
2058
|
+
);
|
|
2059
|
+
|
|
2060
|
+
const payload = {
|
|
2061
|
+
command: 'disconnect',
|
|
2062
|
+
scope,
|
|
2063
|
+
clients,
|
|
2064
|
+
disconnected: true,
|
|
2065
|
+
results,
|
|
2066
|
+
hasErrors: results.some(result => result.error)
|
|
2067
|
+
};
|
|
2068
|
+
|
|
2069
|
+
if (options.json) {
|
|
2070
|
+
printJson(payload);
|
|
2071
|
+
} else {
|
|
2072
|
+
emitCliBanner(options);
|
|
2073
|
+
writeCliLine(paint('disconnect', 'green'));
|
|
2074
|
+
logStep('ok', 'signed-out', 'MCP configs updated');
|
|
2075
|
+
logStep('next', 'next', 'neus auth');
|
|
2076
|
+
writeCliLine('');
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
async function main() {
|
|
2081
|
+
try {
|
|
2082
|
+
const { command, options } = parseArgs(process.argv.slice(2));
|
|
2083
|
+
|
|
2084
|
+
if (command === 'help') {
|
|
2085
|
+
printUsage(0);
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
if (command === 'init') {
|
|
2089
|
+
runInit(options);
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
if (command === 'auth') {
|
|
2093
|
+
const result = await runAuth(options);
|
|
2094
|
+
if (result) {
|
|
2095
|
+
if (options.json) {
|
|
2096
|
+
printJson(result);
|
|
2097
|
+
} else if (result.authMethod !== 'browser') {
|
|
2098
|
+
printFlowSummary('auth', result.scope, result.results, {
|
|
2099
|
+
nextStep: 'Open your MCP client and ask the assistant to use NEUS Trust.',
|
|
2100
|
+
cliOptions: options
|
|
2101
|
+
});
|
|
2102
|
+
} else {
|
|
2103
|
+
printFlowSummary('auth', result.scope, result.results, {
|
|
2104
|
+
nextStep: 'Open your MCP client and ask the assistant to use NEUS Trust.',
|
|
2105
|
+
cliOptions: options
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
if (result.hasErrors) {
|
|
2109
|
+
process.exitCode = 1;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
if (command === 'status') {
|
|
2115
|
+
runStatus(options);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
if (command === 'setup') {
|
|
2119
|
+
const setupResult = await runSetup(options);
|
|
2120
|
+
if (setupResult?.hasErrors) {
|
|
2121
|
+
process.exitCode = 1;
|
|
2122
|
+
}
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
if (command === 'doctor') {
|
|
2126
|
+
await runDoctor(options);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (command === 'import') {
|
|
2130
|
+
runImport(options);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
if (command === 'export') {
|
|
2134
|
+
runExport(options);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
if (command === 'disconnect' || command === 'revoke') {
|
|
2138
|
+
await runDisconnect(options);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
process.stderr.write(`Unknown subcommand: ${command}\n`);
|
|
2143
|
+
printUsage(1);
|
|
2144
|
+
} catch (error) {
|
|
2145
|
+
process.stderr.write(`${error?.message || 'Unknown error'}\n`);
|
|
2146
|
+
process.exit(1);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
main();
|