@neus/sdk 1.2.1 → 1.2.2
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/cjs/index.cjs +3 -0
- package/cjs/mcp-hosts.cjs +3 -0
- package/cli/neus.mjs +2635 -2475
- package/mcp-hosts.js +25 -0
- package/package.json +1 -1
package/cli/neus.mjs
CHANGED
|
@@ -1,2475 +1,2635 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { exec, 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
|
-
import {
|
|
14
|
-
resolveRuntimeBundleFromMcp,
|
|
15
|
-
RUNTIME_MOUNT_SCHEMA,
|
|
16
|
-
normalizeWallet,
|
|
17
|
-
evaluateMountFileHealth
|
|
18
|
-
} from '../runtime-mount.js';
|
|
19
|
-
import { applyRuntimeBundle, readMountManifest } from '../runtime-adapters.js';
|
|
20
|
-
|
|
21
|
-
const __cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const CLI_PACKAGE_VERSION = (() => {
|
|
23
|
-
try {
|
|
24
|
-
return JSON.parse(fs.readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf8')).version;
|
|
25
|
-
} catch {
|
|
26
|
-
return '0.0.0';
|
|
27
|
-
}
|
|
28
|
-
})();
|
|
29
|
-
|
|
30
|
-
const NEUS_APP_URL = 'https://neus.network';
|
|
31
|
-
const NEUS_TOKEN_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/token';
|
|
32
|
-
const NEUS_DISCONNECT_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/revoke';
|
|
33
|
-
const NEUS_PROFILE_KEY_ENDPOINT = 'https://api.neus.network/api/v1/auth/profile-key';
|
|
34
|
-
const SUPPORTED_CLIENTS = ['claude', 'codex', 'cursor', 'vscode'];
|
|
35
|
-
const PROJECT_CLIENTS = ['claude', 'cursor', 'vscode'];
|
|
36
|
-
const CODEX_OAUTH_SCOPES = 'neus:core,neus:profile,neus:secrets,offline_access';
|
|
37
|
-
const IMPORT_SCHEMA = 'neus.portable-agent.v1';
|
|
38
|
-
const SUPPORTED_IMPORT_SOURCES = [
|
|
39
|
-
'auto',
|
|
40
|
-
'cursor',
|
|
41
|
-
'claude-code',
|
|
42
|
-
'claude-desktop'
|
|
43
|
-
];
|
|
44
|
-
const SUPPORTED_EXPORT_FORMATS = ['manifest', 'json'];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
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
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function
|
|
181
|
-
const
|
|
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
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
const
|
|
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
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
throw new Error(
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
client
|
|
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
|
-
if (
|
|
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
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
'
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
return {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
return
|
|
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
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
if (
|
|
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
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
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
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
|
|
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
|
-
const
|
|
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
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
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
|
-
accessKey,
|
|
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
|
-
} finally {
|
|
1657
|
-
clearTimeout(timeout);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
if (!
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
const
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
};
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
if (options.
|
|
2207
|
-
|
|
2208
|
-
if (
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { exec, 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
|
+
import {
|
|
14
|
+
resolveRuntimeBundleFromMcp,
|
|
15
|
+
RUNTIME_MOUNT_SCHEMA,
|
|
16
|
+
normalizeWallet,
|
|
17
|
+
evaluateMountFileHealth
|
|
18
|
+
} from '../runtime-mount.js';
|
|
19
|
+
import { applyRuntimeBundle, readMountManifest } from '../runtime-adapters.js';
|
|
20
|
+
|
|
21
|
+
const __cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const CLI_PACKAGE_VERSION = (() => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf8')).version;
|
|
25
|
+
} catch {
|
|
26
|
+
return '0.0.0';
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
|
|
30
|
+
const NEUS_APP_URL = 'https://neus.network';
|
|
31
|
+
const NEUS_TOKEN_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/token';
|
|
32
|
+
const NEUS_DISCONNECT_ENDPOINT = 'https://neus.network/api/v1/auth/mcp/revoke';
|
|
33
|
+
const NEUS_PROFILE_KEY_ENDPOINT = 'https://api.neus.network/api/v1/auth/profile-key';
|
|
34
|
+
const SUPPORTED_CLIENTS = ['claude', 'codex', 'cursor', 'vscode'];
|
|
35
|
+
const PROJECT_CLIENTS = ['claude', 'cursor', 'vscode'];
|
|
36
|
+
const CODEX_OAUTH_SCOPES = 'neus:core,neus:profile,neus:secrets,offline_access';
|
|
37
|
+
const IMPORT_SCHEMA = 'neus.portable-agent.v1';
|
|
38
|
+
const SUPPORTED_IMPORT_SOURCES = [
|
|
39
|
+
'auto',
|
|
40
|
+
'cursor',
|
|
41
|
+
'claude-code',
|
|
42
|
+
'claude-desktop'
|
|
43
|
+
];
|
|
44
|
+
const SUPPORTED_EXPORT_FORMATS = ['manifest', 'json'];
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// OAuth token store (~/.neus/mcp-tokens.json — gitignored user-scope cache)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Holds the refresh token returned alongside the short-lived OAuth access
|
|
50
|
+
// token. Powers the `neus refresh` escape hatch: when an IDE MCP client's
|
|
51
|
+
// own OAuth refresh has a bug, `neus refresh` rotates the access token in one
|
|
52
|
+
// command instead of a full browser re-auth. The primary refresh path is the
|
|
53
|
+
// IDE's native OAuth client; a URL-only mcp.json config lets the host run
|
|
54
|
+
// discovery, PKCE, and silent refresh itself.
|
|
55
|
+
//
|
|
56
|
+
// Never committed (lives under ~/.neus/). Never written into mcp.json. Refresh
|
|
57
|
+
// tokens rotate on each use; `neus refresh` is a user-run fallback, not a
|
|
58
|
+
// background daemon.
|
|
59
|
+
const NEUS_HOME_DIR = path.join(os.homedir(), '.neus');
|
|
60
|
+
const NEUS_TOKEN_STORE_PATH = path.join(NEUS_HOME_DIR, 'mcp-tokens.json');
|
|
61
|
+
const NEUS_OAUTH_CLIENT_ID = 'neus-cli';
|
|
62
|
+
const NEUS_MCP_RESOURCE = 'https://mcp.neus.network/mcp';
|
|
63
|
+
|
|
64
|
+
function readTokenStore() {
|
|
65
|
+
try {
|
|
66
|
+
const raw = fs.readFileSync(NEUS_TOKEN_STORE_PATH, 'utf8').trim();
|
|
67
|
+
if (!raw) return null;
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeTokenStore(store) {
|
|
76
|
+
if (!store || typeof store !== 'object') return;
|
|
77
|
+
try {
|
|
78
|
+
fs.mkdirSync(NEUS_HOME_DIR, { recursive: true });
|
|
79
|
+
fs.writeFileSync(NEUS_TOKEN_STORE_PATH, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
|
|
80
|
+
} catch {
|
|
81
|
+
// Non-blocking: refresh fallback is insurance, not the primary auth path.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function clearTokenStore() {
|
|
86
|
+
try {
|
|
87
|
+
fs.unlinkSync(NEUS_TOKEN_STORE_PATH);
|
|
88
|
+
} catch {
|
|
89
|
+
// Non-blocking: file may not exist.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function tokenExpiresAt(expiresIn) {
|
|
94
|
+
const seconds = Number(expiresIn);
|
|
95
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return null;
|
|
96
|
+
return Date.now() + seconds * 1000;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isTokenExpired(store) {
|
|
100
|
+
if (!store?.expiresAt) return true;
|
|
101
|
+
return Date.now() >= (store.expiresAt - 60_000);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function persistOAuthTokens(tokenJson, clientId, resource) {
|
|
105
|
+
const refreshToken = String(tokenJson?.refresh_token || '').trim();
|
|
106
|
+
if (!refreshToken) return;
|
|
107
|
+
writeTokenStore({
|
|
108
|
+
accessToken: String(tokenJson?.access_token || '').trim(),
|
|
109
|
+
refreshToken,
|
|
110
|
+
expiresAt: tokenExpiresAt(tokenJson?.expires_in) || (Date.now() + 3600_000),
|
|
111
|
+
clientId: clientId || NEUS_OAUTH_CLIENT_ID,
|
|
112
|
+
resource: resource || NEUS_MCP_RESOURCE,
|
|
113
|
+
scope: String(tokenJson?.scope || '').trim(),
|
|
114
|
+
updatedAt: Date.now()
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function refreshOAuthToken() {
|
|
119
|
+
const store = readTokenStore();
|
|
120
|
+
if (!store?.refreshToken) {
|
|
121
|
+
throw new Error('No stored OAuth refresh token. Run `neus auth --oauth` first.');
|
|
122
|
+
}
|
|
123
|
+
const params = new URLSearchParams();
|
|
124
|
+
params.set('grant_type', 'refresh_token');
|
|
125
|
+
params.set('refresh_token', store.refreshToken);
|
|
126
|
+
params.set('client_id', store.clientId || NEUS_OAUTH_CLIENT_ID);
|
|
127
|
+
params.set('resource', store.resource || NEUS_MCP_RESOURCE);
|
|
128
|
+
const resp = await fetch(NEUS_TOKEN_ENDPOINT, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
131
|
+
body: params.toString(),
|
|
132
|
+
signal: AbortSignal.timeout(15_000)
|
|
133
|
+
});
|
|
134
|
+
const tokenJson = await resp.json();
|
|
135
|
+
if (!tokenJson.access_token) {
|
|
136
|
+
if (tokenJson.error === 'invalid_grant') clearTokenStore();
|
|
137
|
+
throw new Error(tokenJson.error_description || tokenJson.error || 'Token refresh failed');
|
|
138
|
+
}
|
|
139
|
+
persistOAuthTokens(tokenJson, store.clientId, store.resource);
|
|
140
|
+
return {
|
|
141
|
+
accessToken: String(tokenJson.access_token).trim(),
|
|
142
|
+
expiresAt: tokenExpiresAt(tokenJson.expires_in) || (Date.now() + 3600_000)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ansi = {
|
|
147
|
+
reset: '\x1b[0m',
|
|
148
|
+
dim: '\x1b[2m',
|
|
149
|
+
cyan: '\x1b[36m',
|
|
150
|
+
green: '\x1b[32m',
|
|
151
|
+
yellow: '\x1b[33m',
|
|
152
|
+
red: '\x1b[31m',
|
|
153
|
+
bold: '\x1b[1m'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
function isTruthyEnv(value) {
|
|
157
|
+
const normalized = String(value || '')
|
|
158
|
+
.trim()
|
|
159
|
+
.toLowerCase();
|
|
160
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function resolveColorEnabled() {
|
|
164
|
+
if (isTruthyEnv(process.env.NO_COLOR)) return false;
|
|
165
|
+
if (process.env.TERM === 'dumb') return false;
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function paint(value, color) {
|
|
170
|
+
if (!resolveColorEnabled()) return String(value);
|
|
171
|
+
return `${ansi[color] || ''}${value}${ansi.reset}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function terminalColumns() {
|
|
175
|
+
const cols = Number(process.stderr.columns || process.stdout.columns || 0);
|
|
176
|
+
if (Number.isFinite(cols) && cols >= 40) return cols;
|
|
177
|
+
return 80;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function truncateDetail(text) {
|
|
181
|
+
const raw = String(text || '');
|
|
182
|
+
const max = Math.max(24, terminalColumns() - 18);
|
|
183
|
+
if (raw.length <= max) return raw;
|
|
184
|
+
return `${raw.slice(0, Math.max(0, max - 3))}...`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function cliSymbols() {
|
|
188
|
+
return { ok: 'ok', warn: '!', next: '>', skip: '-' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function writeCliLine(line) {
|
|
192
|
+
process.stderr.write(`${line}\n`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let cliBannerEmitted = false;
|
|
196
|
+
|
|
197
|
+
function readCliVersion() {
|
|
198
|
+
try {
|
|
199
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf8'));
|
|
200
|
+
return String(pkg.version || '0.0.0').trim();
|
|
201
|
+
} catch {
|
|
202
|
+
return '0.0.0';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function shouldEmitCliBanner(cliOptions = {}) {
|
|
207
|
+
if (cliBannerEmitted) return false;
|
|
208
|
+
if (cliOptions.json) return false;
|
|
209
|
+
if (!process.stderr.isTTY) return false;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function emitCliBanner(cliOptions = {}) {
|
|
214
|
+
if (!shouldEmitCliBanner(cliOptions)) return;
|
|
215
|
+
const version = readCliVersion();
|
|
216
|
+
const title = paint('NEUS', 'green');
|
|
217
|
+
const meta = `${paint(`v${version}`, 'dim')}${paint(' | trust that travels', 'dim')}`;
|
|
218
|
+
writeCliLine('');
|
|
219
|
+
writeCliLine(` ${title} ${meta}`);
|
|
220
|
+
writeCliLine('');
|
|
221
|
+
cliBannerEmitted = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function logStep(kind, label, detail = '') {
|
|
225
|
+
const symbols = cliSymbols();
|
|
226
|
+
const iconKey = kind === 'ok' ? 'ok' : kind === 'warn' ? 'warn' : kind === 'next' ? 'next' : 'skip';
|
|
227
|
+
const iconColor = kind === 'ok' ? 'green' : kind === 'warn' ? 'yellow' : kind === 'next' ? 'cyan' : 'dim';
|
|
228
|
+
const iconCell = symbols[iconKey].padEnd(2);
|
|
229
|
+
const icon = paint(iconCell, iconColor);
|
|
230
|
+
const name = paint(String(label).padEnd(10), 'cyan');
|
|
231
|
+
const suffix = detail ? ` ${paint(truncateDetail(detail), 'dim')}` : '';
|
|
232
|
+
writeCliLine(` ${icon} ${name}${suffix}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function writeGuidanceLine(text) {
|
|
236
|
+
writeCliLine(` ${paint('-', 'dim')} ${text}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function describeClientResult(command, result) {
|
|
240
|
+
if (result.dryRun && result.changed) {
|
|
241
|
+
if (result.client === 'codex') {
|
|
242
|
+
return `would update ${result.targetPath || '~/.codex/config.toml'}`;
|
|
243
|
+
}
|
|
244
|
+
return 'would update';
|
|
245
|
+
}
|
|
246
|
+
if (result.client === 'codex' && result.configured) {
|
|
247
|
+
if (command === 'auth') {
|
|
248
|
+
return result.authConfigured ? 'Codex OAuth complete' : 'Codex MCP config ready';
|
|
249
|
+
}
|
|
250
|
+
return `Codex MCP config: ${result.targetPath || '~/.codex/config.toml'}`;
|
|
251
|
+
}
|
|
252
|
+
if (result.changed) return 'updated';
|
|
253
|
+
if (result.authConfigured) return 'signed in';
|
|
254
|
+
if (result.configured) return 'ready';
|
|
255
|
+
return 'ready';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function printBuilderGuidance(command, results) {
|
|
259
|
+
if (!['setup', 'auth', 'check'].includes(command)) return;
|
|
260
|
+
const hasCodex = results.some(result => result.client === 'codex');
|
|
261
|
+
writeCliLine('');
|
|
262
|
+
writeCliLine(paint('Next steps', 'cyan'));
|
|
263
|
+
writeGuidanceLine('Run `npx -y -p @neus/sdk neus examples` for assistant prompts.');
|
|
264
|
+
if (hasCodex) {
|
|
265
|
+
writeGuidanceLine('Codex OAuth: `neus auth --client codex` or `codex mcp login neus`.');
|
|
266
|
+
}
|
|
267
|
+
writeGuidanceLine('Ask your assistant: "Use NEUS Verify before taking sensitive actions."');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function selectedClientNames(results) {
|
|
271
|
+
return results.map(result => result.client).filter(Boolean);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function preferredSetupCommand(results) {
|
|
275
|
+
const clients = selectedClientNames(results);
|
|
276
|
+
const suffix = clients.length === 1 ? ` --client ${clients[0]}` : '';
|
|
277
|
+
return `npx -y -p @neus/sdk neus setup${suffix}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function preferredAuthCommand(results) {
|
|
281
|
+
const clients = selectedClientNames(results);
|
|
282
|
+
if (clients.length === 1 && clients[0] === 'codex') {
|
|
283
|
+
return 'npx -y -p @neus/sdk neus auth --client codex';
|
|
284
|
+
}
|
|
285
|
+
return 'npx -y -p @neus/sdk neus auth';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function printStatusGuidance(results) {
|
|
289
|
+
writeCliLine('');
|
|
290
|
+
writeCliLine(paint('MCP endpoint', 'cyan'));
|
|
291
|
+
writeGuidanceLine(NEUS_MCP_URL);
|
|
292
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
293
|
+
if (results.some(result => result.configured)) {
|
|
294
|
+
writeGuidanceLine('Saved config found. Run `npx -y -p @neus/sdk neus check` to confirm live connection.');
|
|
295
|
+
} else {
|
|
296
|
+
writeGuidanceLine(`No selected MCP host is configured yet. Run \`${preferredSetupCommand(results)}\`.`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function printHostAuthIntro(host, cliOptions = {}) {
|
|
301
|
+
if (cliOptions.json) return;
|
|
302
|
+
emitCliBanner(cliOptions);
|
|
303
|
+
writeCliLine(paint('auth', 'green'));
|
|
304
|
+
if (host === 'codex') {
|
|
305
|
+
logStep('next', 'codex', 'starting Codex-owned MCP OAuth');
|
|
306
|
+
logStep('next', 'command', 'codex mcp login neus');
|
|
307
|
+
writeCliLine('');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function printFlowSummary(command, scope, results, { nextStep = '', cliOptions = {} } = {}) {
|
|
312
|
+
emitCliBanner(cliOptions);
|
|
313
|
+
writeCliLine(paint(String(command), 'green'));
|
|
314
|
+
|
|
315
|
+
for (const result of results) {
|
|
316
|
+
const client = result.client;
|
|
317
|
+
if (result.error) {
|
|
318
|
+
logStep('warn', client, result.error);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (result.configured) {
|
|
322
|
+
const detail = describeClientResult(command, result);
|
|
323
|
+
logStep('ok', client, detail);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (result.authConfigured === null) {
|
|
327
|
+
logStep('skip', client, 'not installed');
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
logStep('skip', client, 'not configured');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (nextStep) {
|
|
334
|
+
writeCliLine('');
|
|
335
|
+
logStep('next', 'next', nextStep);
|
|
336
|
+
}
|
|
337
|
+
if (command === 'status') {
|
|
338
|
+
printStatusGuidance(results);
|
|
339
|
+
}
|
|
340
|
+
printBuilderGuidance(command, results);
|
|
341
|
+
writeCliLine('');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function printAuthBrowserIntro(authUrl, cliOptions = {}) {
|
|
345
|
+
emitCliBanner(cliOptions);
|
|
346
|
+
writeCliLine(paint('auth', 'green'));
|
|
347
|
+
logStep('next', 'sign-in', 'opens in your browser');
|
|
348
|
+
writeCliLine('');
|
|
349
|
+
writeCliLine(` ${paint(truncateDetail(authUrl), 'dim')}`);
|
|
350
|
+
writeCliLine('');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function parseBearerHeader(value) {
|
|
354
|
+
const raw = String(value || '').trim();
|
|
355
|
+
if (!raw.toLowerCase().startsWith('bearer ')) return '';
|
|
356
|
+
return raw.slice(7).trim();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function readCursorBearer(scope, cwd) {
|
|
360
|
+
const targetPath = cursorConfigPath(scope, cwd);
|
|
361
|
+
if (!fileExists(targetPath)) return '';
|
|
362
|
+
const doc = readJsonFile(targetPath, {});
|
|
363
|
+
return parseBearerHeader(doc.mcpServers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function readVsCodeBearer(scope, cwd) {
|
|
367
|
+
const targetPath = vscodeConfigPath(scope, cwd);
|
|
368
|
+
if (!fileExists(targetPath)) return '';
|
|
369
|
+
const doc = readJsonFile(targetPath, {});
|
|
370
|
+
return parseBearerHeader(doc.servers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function readClaudeBearer(scope, cwd) {
|
|
374
|
+
if (scope === 'project') {
|
|
375
|
+
const targetPath = claudeProjectConfigPath(cwd);
|
|
376
|
+
if (!fileExists(targetPath)) return '';
|
|
377
|
+
const doc = readJsonFile(targetPath, {});
|
|
378
|
+
return parseBearerHeader(doc.mcpServers?.[NEUS_MCP_SERVER_NAME]?.headers?.Authorization);
|
|
379
|
+
}
|
|
380
|
+
if (!commandExists('claude')) return '';
|
|
381
|
+
const result = spawnSync('claude', ['mcp', 'list'], {
|
|
382
|
+
encoding: 'utf8',
|
|
383
|
+
env: process.env
|
|
384
|
+
});
|
|
385
|
+
if (result.status !== 0) return '';
|
|
386
|
+
const lines = String(result.stdout || '').split(/\r?\n/);
|
|
387
|
+
if (!lines.includes(NEUS_MCP_SERVER_NAME)) return '';
|
|
388
|
+
const statePath = process.env.NEUS_TEST_CLAUDE_STATE;
|
|
389
|
+
if (statePath && fileExists(statePath)) {
|
|
390
|
+
const state = readJsonFile(statePath, { servers: {} });
|
|
391
|
+
const headers = state.servers?.[NEUS_MCP_SERVER_NAME]?.headers || [];
|
|
392
|
+
const authLine = headers.find(line => String(line).toLowerCase().startsWith('authorization:'));
|
|
393
|
+
if (authLine) {
|
|
394
|
+
return parseBearerHeader(authLine.replace(/^authorization:\s*/i, ''));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return '';
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function readInstalledAccessKey(scope, cwd) {
|
|
401
|
+
for (const reader of [readCursorBearer, readVsCodeBearer, readClaudeBearer]) {
|
|
402
|
+
const token = reader(scope, cwd);
|
|
403
|
+
if (token) return token;
|
|
404
|
+
}
|
|
405
|
+
return '';
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function envAccessKey() {
|
|
409
|
+
return String(process.env.NEUS_ACCESS_KEY || '').trim();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** --access-key flag, else NEUS_ACCESS_KEY from the environment, else browser sign-in. */
|
|
413
|
+
function resolveAccessKey(options) {
|
|
414
|
+
if (options?.oauth) return '';
|
|
415
|
+
const explicit = String(options.accessKey || '').trim();
|
|
416
|
+
if (explicit) return explicit;
|
|
417
|
+
return envAccessKey();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** --access-key, IDE MCP config, then NEUS_ACCESS_KEY from the environment. */
|
|
421
|
+
function resolveLiveAccessKey(options, scope, cwd) {
|
|
422
|
+
const explicit = String(options.accessKey || '').trim();
|
|
423
|
+
if (explicit) return explicit;
|
|
424
|
+
const installed = readInstalledAccessKey(scope, cwd);
|
|
425
|
+
if (installed) return installed;
|
|
426
|
+
if (options?.oauth) return '';
|
|
427
|
+
return envAccessKey();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function resolveAuthMethod(options, accessKey) {
|
|
431
|
+
if (!accessKey) return 'browser';
|
|
432
|
+
if (String(options.accessKey || '').trim()) return 'access-key';
|
|
433
|
+
return 'env-key';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function fileExists(targetPath) {
|
|
437
|
+
try {
|
|
438
|
+
fs.accessSync(targetPath);
|
|
439
|
+
return true;
|
|
440
|
+
} catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function jsonStringify(value) {
|
|
446
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function readJsonFile(targetPath, fallback) {
|
|
450
|
+
if (!fileExists(targetPath)) return fallback;
|
|
451
|
+
const raw = fs.readFileSync(targetPath, 'utf8').trim();
|
|
452
|
+
if (!raw) return fallback;
|
|
453
|
+
try {
|
|
454
|
+
const parsed = JSON.parse(raw);
|
|
455
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (error instanceof SyntaxError) {
|
|
458
|
+
throw new Error(`Invalid JSON in ${targetPath}`);
|
|
459
|
+
}
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function writeJsonFile(targetPath, nextValue, dryRun) {
|
|
465
|
+
const serialized = jsonStringify(nextValue);
|
|
466
|
+
const hadExistingFile = fileExists(targetPath);
|
|
467
|
+
const previous = hadExistingFile ? fs.readFileSync(targetPath, 'utf8') : null;
|
|
468
|
+
const changed = previous !== serialized;
|
|
469
|
+
const backupPath = hadExistingFile && changed ? `${targetPath}.bak` : null;
|
|
470
|
+
|
|
471
|
+
if (!dryRun && changed) {
|
|
472
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
473
|
+
if (backupPath) {
|
|
474
|
+
fs.copyFileSync(targetPath, backupPath);
|
|
475
|
+
}
|
|
476
|
+
fs.writeFileSync(targetPath, serialized, 'utf8');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
changed,
|
|
481
|
+
targetPath,
|
|
482
|
+
backupPath,
|
|
483
|
+
dryRun
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function readTextFile(targetPath) {
|
|
488
|
+
if (!fileExists(targetPath)) return '';
|
|
489
|
+
return fs.readFileSync(targetPath, 'utf8');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function sha256(value) {
|
|
493
|
+
return createHash('sha256').update(value).digest('hex');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function statBytes(targetPath) {
|
|
497
|
+
try {
|
|
498
|
+
return fs.statSync(targetPath).size;
|
|
499
|
+
} catch {
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function listDirectoryNames(targetPath) {
|
|
505
|
+
if (!fileExists(targetPath)) return [];
|
|
506
|
+
try {
|
|
507
|
+
return fs
|
|
508
|
+
.readdirSync(targetPath, { withFileTypes: true })
|
|
509
|
+
.filter(entry => entry.isDirectory())
|
|
510
|
+
.map(entry => entry.name)
|
|
511
|
+
.sort((a, b) => a.localeCompare(b));
|
|
512
|
+
} catch {
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function listFileNames(targetPath, extensions) {
|
|
518
|
+
if (!fileExists(targetPath)) return [];
|
|
519
|
+
try {
|
|
520
|
+
return fs
|
|
521
|
+
.readdirSync(targetPath, { withFileTypes: true })
|
|
522
|
+
.filter(entry => entry.isFile())
|
|
523
|
+
.map(entry => entry.name)
|
|
524
|
+
.filter(name => extensions.some(extension => name.toLowerCase().endsWith(extension)))
|
|
525
|
+
.sort((a, b) => a.localeCompare(b));
|
|
526
|
+
} catch {
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function safeReadJson(targetPath, warnings) {
|
|
532
|
+
if (!fileExists(targetPath)) return null;
|
|
533
|
+
try {
|
|
534
|
+
return readJsonFile(targetPath, null);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
warnings.push(`Skipped malformed JSON at ${targetPath}: ${errorMessage(error)}`);
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function portablePath(targetPath) {
|
|
542
|
+
const homeDir = os.homedir();
|
|
543
|
+
const cwd = process.cwd();
|
|
544
|
+
const normalized = path.resolve(targetPath);
|
|
545
|
+
const homeRelative = path.relative(homeDir, normalized);
|
|
546
|
+
if (homeRelative && !homeRelative.startsWith('..') && !path.isAbsolute(homeRelative)) {
|
|
547
|
+
return `~/${homeRelative.replaceAll(path.sep, '/')}`;
|
|
548
|
+
}
|
|
549
|
+
const cwdRelative = path.relative(cwd, normalized);
|
|
550
|
+
if (cwdRelative && !cwdRelative.startsWith('..') && !path.isAbsolute(cwdRelative)) {
|
|
551
|
+
return cwdRelative.replaceAll(path.sep, '/');
|
|
552
|
+
}
|
|
553
|
+
return normalized.replaceAll(path.sep, '/');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function instructionEntry(targetPath, name) {
|
|
557
|
+
const raw = readTextFile(targetPath);
|
|
558
|
+
if (!raw) return null;
|
|
559
|
+
return {
|
|
560
|
+
name,
|
|
561
|
+
path: portablePath(targetPath),
|
|
562
|
+
bytes: statBytes(targetPath),
|
|
563
|
+
sha256: sha256(raw)
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function readMcpServers(targetPath, source, warnings) {
|
|
568
|
+
const doc = safeReadJson(targetPath, warnings);
|
|
569
|
+
if (!doc) return [];
|
|
570
|
+
const mcpSection = doc.mcp && typeof doc.mcp === 'object' && !Array.isArray(doc.mcp) ? doc.mcp : null;
|
|
571
|
+
const servers =
|
|
572
|
+
doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
573
|
+
? doc.mcpServers
|
|
574
|
+
: mcpSection?.servers &&
|
|
575
|
+
typeof mcpSection.servers === 'object' &&
|
|
576
|
+
!Array.isArray(mcpSection.servers)
|
|
577
|
+
? mcpSection.servers
|
|
578
|
+
: doc.servers && typeof doc.servers === 'object' && !Array.isArray(doc.servers)
|
|
579
|
+
? doc.servers
|
|
580
|
+
: {};
|
|
581
|
+
return Object.keys(servers)
|
|
582
|
+
.sort((a, b) => a.localeCompare(b))
|
|
583
|
+
.map(name => ({
|
|
584
|
+
name,
|
|
585
|
+
source,
|
|
586
|
+
path: portablePath(targetPath),
|
|
587
|
+
type:
|
|
588
|
+
servers[name]?.type ||
|
|
589
|
+
(servers[name]?.url ? 'http' : servers[name]?.command ? 'stdio' : 'unknown'),
|
|
590
|
+
url:
|
|
591
|
+
typeof servers[name]?.url === 'string' && !servers[name].headers
|
|
592
|
+
? servers[name].url
|
|
593
|
+
: undefined
|
|
594
|
+
}));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function resolveCommand(command) {
|
|
598
|
+
const checker = process.platform === 'win32' ? 'where' : 'which';
|
|
599
|
+
const result = spawnSync(checker, [command], {
|
|
600
|
+
encoding: 'utf8',
|
|
601
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
602
|
+
});
|
|
603
|
+
if (result.status !== 0) return null;
|
|
604
|
+
const firstMatch = result.stdout
|
|
605
|
+
.split(/\r?\n/)
|
|
606
|
+
.map(line => line.trim())
|
|
607
|
+
.find(Boolean);
|
|
608
|
+
return firstMatch || null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function runCommand(command, args, cwd, tolerateFailure = false) {
|
|
612
|
+
const resolvedCommand = resolveCommand(command) || command;
|
|
613
|
+
const isWindowsScript = process.platform === 'win32' && /\.(cmd|bat)$/i.test(resolvedCommand);
|
|
614
|
+
const result = isWindowsScript
|
|
615
|
+
? spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', resolvedCommand, ...args], {
|
|
616
|
+
cwd,
|
|
617
|
+
encoding: 'utf8',
|
|
618
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
619
|
+
})
|
|
620
|
+
: spawnSync(resolvedCommand, args, {
|
|
621
|
+
cwd,
|
|
622
|
+
encoding: 'utf8',
|
|
623
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
if (result.error && !tolerateFailure) {
|
|
627
|
+
throw result.error;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (result.status !== 0 && !tolerateFailure) {
|
|
631
|
+
const detail =
|
|
632
|
+
[result.stderr, result.stdout].find(value => typeof value === 'string' && value.trim()) || '';
|
|
633
|
+
throw new Error(detail.trim() || `Command failed: ${command} ${args.join(' ')}`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function commandExists(command) {
|
|
640
|
+
return Boolean(resolveCommand(command));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function cursorInstalled() {
|
|
644
|
+
const homeDir = os.homedir();
|
|
645
|
+
const appData = process.env.APPDATA || '';
|
|
646
|
+
const localAppData = process.env.LOCALAPPDATA || '';
|
|
647
|
+
return [
|
|
648
|
+
path.join(homeDir, '.cursor'),
|
|
649
|
+
path.join(appData, 'Cursor'),
|
|
650
|
+
path.join(localAppData, 'Programs', 'Cursor', 'Cursor.exe')
|
|
651
|
+
].some(fileExists);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function defaultUserClients() {
|
|
655
|
+
const detected = [];
|
|
656
|
+
if (commandExists('claude')) detected.push('claude');
|
|
657
|
+
if (commandExists('codex')) detected.push('codex');
|
|
658
|
+
if (cursorInstalled()) detected.push('cursor');
|
|
659
|
+
if (commandExists('code') || fileExists(path.join(process.env.APPDATA || '', 'Code')))
|
|
660
|
+
detected.push('vscode');
|
|
661
|
+
return detected;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function parseClientOption(raw) {
|
|
665
|
+
return String(raw || '')
|
|
666
|
+
.split(',')
|
|
667
|
+
.map(value => value.trim().toLowerCase())
|
|
668
|
+
.filter(Boolean);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function parseArgs(argv) {
|
|
672
|
+
if (argv.length === 0) {
|
|
673
|
+
return {
|
|
674
|
+
command: 'help',
|
|
675
|
+
options: {
|
|
676
|
+
accessKey: '',
|
|
677
|
+
clients: [],
|
|
678
|
+
source: 'auto',
|
|
679
|
+
format: 'manifest',
|
|
680
|
+
output: '',
|
|
681
|
+
live: false,
|
|
682
|
+
json: false,
|
|
683
|
+
dryRun: false,
|
|
684
|
+
project: false
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const command = argv[0];
|
|
690
|
+
const options = {
|
|
691
|
+
accessKey: '',
|
|
692
|
+
clients: [],
|
|
693
|
+
source: 'auto',
|
|
694
|
+
format: 'manifest',
|
|
695
|
+
output: '',
|
|
696
|
+
live: false,
|
|
697
|
+
json: false,
|
|
698
|
+
dryRun: false,
|
|
699
|
+
project: false,
|
|
700
|
+
oauth: false,
|
|
701
|
+
agent: '',
|
|
702
|
+
apply: '',
|
|
703
|
+
agentTarget: ''
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
707
|
+
const token = argv[index];
|
|
708
|
+
if (token === '--json') {
|
|
709
|
+
options.json = true;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (token === '--dry-run') {
|
|
713
|
+
options.dryRun = true;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (token === '--live') {
|
|
717
|
+
options.live = true;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (token === '--project') {
|
|
721
|
+
options.project = true;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (token === '--from') {
|
|
725
|
+
const value = argv[index + 1];
|
|
726
|
+
if (!value) throw new Error('--from requires a value');
|
|
727
|
+
options.source = value.trim().toLowerCase();
|
|
728
|
+
index += 1;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (token === '--to') {
|
|
732
|
+
const value = argv[index + 1];
|
|
733
|
+
if (!value) throw new Error('--to requires a value');
|
|
734
|
+
options.format = value.trim().toLowerCase();
|
|
735
|
+
index += 1;
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (token === '--output') {
|
|
739
|
+
const value = argv[index + 1];
|
|
740
|
+
if (!value) throw new Error('--output requires a value');
|
|
741
|
+
options.output = value;
|
|
742
|
+
index += 1;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (token === '--client') {
|
|
746
|
+
const value = argv[index + 1];
|
|
747
|
+
if (!value) throw new Error('--client requires a value');
|
|
748
|
+
options.clients.push(...parseClientOption(value));
|
|
749
|
+
index += 1;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (token === '--access-key') {
|
|
753
|
+
const value = argv[index + 1];
|
|
754
|
+
if (!value) throw new Error('--access-key requires a value');
|
|
755
|
+
options.accessKey = value;
|
|
756
|
+
index += 1;
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (token === '--oauth') {
|
|
760
|
+
options.oauth = true;
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (token === '--agent') {
|
|
764
|
+
const value = argv[index + 1];
|
|
765
|
+
if (!value) throw new Error('--agent requires a value');
|
|
766
|
+
options.agent = value.trim();
|
|
767
|
+
index += 1;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (token === '--apply') {
|
|
771
|
+
const value = argv[index + 1];
|
|
772
|
+
if (!value) throw new Error('--apply requires a value (cursor, claude, or codex)');
|
|
773
|
+
options.apply = value.trim().toLowerCase();
|
|
774
|
+
index += 1;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
if (command === 'mount' && !token.startsWith('-') && !options.agentTarget) {
|
|
778
|
+
options.agentTarget = token;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (token === '--help' || token === '-h') {
|
|
782
|
+
return { command: 'help', options };
|
|
783
|
+
}
|
|
784
|
+
throw new Error(`Unknown option: ${token}`);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
options.accessKey = String(options.accessKey || '').trim();
|
|
788
|
+
options.clients = [...new Set(options.clients)];
|
|
789
|
+
|
|
790
|
+
return { command, options };
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function printUsage(exitCode = 0) {
|
|
794
|
+
const lines = [
|
|
795
|
+
'Usage: neus <command> [options]',
|
|
796
|
+
'',
|
|
797
|
+
'Commands:',
|
|
798
|
+
' setup Configure hosted NEUS MCP for supported clients',
|
|
799
|
+
' init Configure supported MCP clients automatically',
|
|
800
|
+
' auth Sign in (browser, or NEUS_ACCESS_KEY / --access-key when set)',
|
|
801
|
+
' refresh Rotate the stored OAuth token using the saved refresh token',
|
|
802
|
+
' disconnect Disconnect NEUS MCP (revoke the stored OAuth token or access key)',
|
|
803
|
+
' status Show current NEUS MCP setup',
|
|
804
|
+
' check Confirm setup and live NEUS connection (alias for doctor --live)',
|
|
805
|
+
' examples Show assistant prompts to try after install',
|
|
806
|
+
' doctor Deep check: config status, profile connection, and live MCP context',
|
|
807
|
+
' mount Mount proof-backed agent context for any runtime',
|
|
808
|
+
' import Detect and package supported assistant context for NEUS portability',
|
|
809
|
+
' export Export the latest local NEUS portable agent manifest',
|
|
810
|
+
' help Show this message',
|
|
811
|
+
'',
|
|
812
|
+
'Options:',
|
|
813
|
+
' --client <name[,name]> Limit setup to claude, codex, cursor, or vscode',
|
|
814
|
+
' --project Write shared project config instead of user config',
|
|
815
|
+
' --access-key <npk_...> Override profile access key (else uses NEUS_ACCESS_KEY if set)',
|
|
816
|
+
' --oauth Force browser OAuth (ignore NEUS_ACCESS_KEY in the environment)',
|
|
817
|
+
' --from <source> Import source: auto, cursor, claude-code, or claude-desktop',
|
|
818
|
+
' --to <format> Export format: manifest or json',
|
|
819
|
+
' --output <path> Write exported manifest to a specific path',
|
|
820
|
+
' --live Run live MCP checks (uses IDE credential or --access-key)',
|
|
821
|
+
' --agent <agentId> Agent id for mount (also: neus mount <agentId>)',
|
|
822
|
+
' --apply <cursor|claude|codex> Write mounted agent rules to the current project',
|
|
823
|
+
' --json Print JSON output',
|
|
824
|
+
' --dry-run Preview changes without writing files'
|
|
825
|
+
];
|
|
826
|
+
const stream = exitCode === 0 ? process.stdout : process.stderr;
|
|
827
|
+
stream.write(`${lines.join('\n')}\n`);
|
|
828
|
+
process.exit(exitCode);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function assertValidClients(clients) {
|
|
832
|
+
for (const client of clients) {
|
|
833
|
+
if (!SUPPORTED_CLIENTS.includes(client)) {
|
|
834
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function resolveScope(options) {
|
|
840
|
+
return options.project ? 'project' : 'user';
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function resolveClients(scope, requestedClients) {
|
|
844
|
+
assertValidClients(requestedClients);
|
|
845
|
+
if (requestedClients.length > 0) return requestedClients;
|
|
846
|
+
if (scope === 'project') return [...PROJECT_CLIENTS];
|
|
847
|
+
return defaultUserClients();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function ensureClientSelection(scope, clients) {
|
|
851
|
+
if (clients.length > 0) return;
|
|
852
|
+
if (scope === 'project') return;
|
|
853
|
+
throw new Error(
|
|
854
|
+
'No supported clients detected. Re-run with --project or use --client to target a specific client.'
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function ensureSafeAuth(command, scope, accessKey) {
|
|
859
|
+
if ((command === 'auth' || command === 'setup') && scope !== 'user') {
|
|
860
|
+
throw new Error(
|
|
861
|
+
'`neus ${command}` only supports user scope so access keys never land in shared project config.'
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
if (scope === 'project' && accessKey) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
'Access keys are only supported in user scope. Remove --project or omit --access-key.'
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function buildCursorServer(accessKey) {
|
|
872
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function buildVsCodeServer(accessKey) {
|
|
876
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function buildClaudeServer(accessKey) {
|
|
880
|
+
return buildNeusMcpHttpConfig(accessKey);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function cursorConfigPath(scope, cwd) {
|
|
884
|
+
return scope === 'user'
|
|
885
|
+
? path.join(os.homedir(), '.cursor', 'mcp.json')
|
|
886
|
+
: path.join(cwd, '.cursor', 'mcp.json');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function vscodeConfigPath(scope, cwd) {
|
|
890
|
+
if (scope !== 'user') {
|
|
891
|
+
return path.join(cwd, '.vscode', 'mcp.json');
|
|
892
|
+
}
|
|
893
|
+
if (process.platform === 'darwin') {
|
|
894
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
|
|
895
|
+
}
|
|
896
|
+
if (process.platform === 'win32') {
|
|
897
|
+
return path.join(
|
|
898
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
899
|
+
'Code',
|
|
900
|
+
'User',
|
|
901
|
+
'mcp.json'
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
return path.join(os.homedir(), '.config', 'Code', 'User', 'mcp.json');
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function claudeProjectConfigPath(cwd) {
|
|
908
|
+
return path.join(cwd, '.mcp.json');
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function codexConfigPath() {
|
|
912
|
+
return path.join(os.homedir(), '.codex', 'config.toml');
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function installCursor(scope, accessKey, dryRun, cwd) {
|
|
916
|
+
const targetPath = cursorConfigPath(scope, cwd);
|
|
917
|
+
const doc = readJsonFile(targetPath, { mcpServers: {} });
|
|
918
|
+
const serverConfig = buildCursorServer(accessKey);
|
|
919
|
+
const next = {
|
|
920
|
+
...doc,
|
|
921
|
+
mcpServers: {
|
|
922
|
+
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
923
|
+
? doc.mcpServers
|
|
924
|
+
: {}),
|
|
925
|
+
[NEUS_MCP_SERVER_NAME]: serverConfig
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
929
|
+
return {
|
|
930
|
+
client: 'cursor',
|
|
931
|
+
scope,
|
|
932
|
+
configured: true,
|
|
933
|
+
authConfigured: Boolean(serverConfig.headers),
|
|
934
|
+
changed: writeResult.changed,
|
|
935
|
+
targetPath,
|
|
936
|
+
backupPath: writeResult.backupPath,
|
|
937
|
+
dryRun,
|
|
938
|
+
error: null
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function installVsCode(scope, accessKey, dryRun, cwd) {
|
|
943
|
+
const targetPath = vscodeConfigPath(scope, cwd);
|
|
944
|
+
const doc = readJsonFile(targetPath, { servers: {} });
|
|
945
|
+
const serverConfig = buildVsCodeServer(accessKey);
|
|
946
|
+
const next = {
|
|
947
|
+
...doc,
|
|
948
|
+
servers: {
|
|
949
|
+
...(doc.servers && typeof doc.servers === 'object' && !Array.isArray(doc.servers)
|
|
950
|
+
? doc.servers
|
|
951
|
+
: {}),
|
|
952
|
+
[NEUS_MCP_SERVER_NAME]: serverConfig
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
956
|
+
return {
|
|
957
|
+
client: 'vscode',
|
|
958
|
+
scope,
|
|
959
|
+
configured: true,
|
|
960
|
+
authConfigured: Boolean(serverConfig.headers),
|
|
961
|
+
changed: writeResult.changed,
|
|
962
|
+
targetPath,
|
|
963
|
+
backupPath: writeResult.backupPath,
|
|
964
|
+
dryRun,
|
|
965
|
+
error: null
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function installClaudeProject(scope, accessKey, dryRun, cwd) {
|
|
970
|
+
const targetPath = claudeProjectConfigPath(cwd);
|
|
971
|
+
const doc = readJsonFile(targetPath, { mcpServers: {} });
|
|
972
|
+
const serverConfig = buildClaudeServer(accessKey);
|
|
973
|
+
const next = {
|
|
974
|
+
...doc,
|
|
975
|
+
mcpServers: {
|
|
976
|
+
...(doc.mcpServers && typeof doc.mcpServers === 'object' && !Array.isArray(doc.mcpServers)
|
|
977
|
+
? doc.mcpServers
|
|
978
|
+
: {}),
|
|
979
|
+
[NEUS_MCP_SERVER_NAME]: serverConfig
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
const writeResult = writeJsonFile(targetPath, next, dryRun);
|
|
983
|
+
return {
|
|
984
|
+
client: 'claude',
|
|
985
|
+
scope,
|
|
986
|
+
configured: true,
|
|
987
|
+
authConfigured: Boolean(serverConfig.headers),
|
|
988
|
+
changed: writeResult.changed,
|
|
989
|
+
targetPath,
|
|
990
|
+
backupPath: writeResult.backupPath,
|
|
991
|
+
dryRun,
|
|
992
|
+
error: null
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function installClaudeUser(scope, accessKey, dryRun, cwd) {
|
|
997
|
+
if (!commandExists('claude')) {
|
|
998
|
+
throw new Error('Claude Code CLI is not installed or not on PATH.');
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (!dryRun) {
|
|
1002
|
+
runCommand('claude', ['mcp', 'remove', '--scope', 'user', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
1003
|
+
const addArgs = [
|
|
1004
|
+
'mcp',
|
|
1005
|
+
'add',
|
|
1006
|
+
'--transport',
|
|
1007
|
+
'http',
|
|
1008
|
+
'--scope',
|
|
1009
|
+
'user',
|
|
1010
|
+
NEUS_MCP_SERVER_NAME,
|
|
1011
|
+
NEUS_MCP_URL
|
|
1012
|
+
];
|
|
1013
|
+
if (accessKey) {
|
|
1014
|
+
addArgs.push('--header', `Authorization: Bearer ${accessKey}`);
|
|
1015
|
+
}
|
|
1016
|
+
runCommand('claude', addArgs, cwd);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return {
|
|
1020
|
+
client: 'claude',
|
|
1021
|
+
scope,
|
|
1022
|
+
configured: true,
|
|
1023
|
+
authConfigured: Boolean(accessKey),
|
|
1024
|
+
changed: true,
|
|
1025
|
+
targetPath: '~/.claude.json',
|
|
1026
|
+
backupPath: null,
|
|
1027
|
+
dryRun,
|
|
1028
|
+
error: null
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function installClaude(scope, accessKey, dryRun, cwd) {
|
|
1033
|
+
if (scope === 'project') {
|
|
1034
|
+
return installClaudeProject(scope, accessKey, dryRun, cwd);
|
|
1035
|
+
}
|
|
1036
|
+
return installClaudeUser(scope, accessKey, dryRun, cwd);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function installCodex(scope, accessKey, dryRun, cwd) {
|
|
1040
|
+
if (scope !== 'user') {
|
|
1041
|
+
throw new Error('Codex MCP setup is user-scoped through ~/.codex/config.toml.');
|
|
1042
|
+
}
|
|
1043
|
+
if (!commandExists('codex')) {
|
|
1044
|
+
throw new Error('Codex CLI is not installed or not on PATH.');
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const bearerTokenEnvVar = envAccessKey() ? 'NEUS_ACCESS_KEY' : '';
|
|
1048
|
+
|
|
1049
|
+
if (!dryRun) {
|
|
1050
|
+
runCommand('codex', ['mcp', 'remove', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
1051
|
+
const addArgs = [
|
|
1052
|
+
'mcp',
|
|
1053
|
+
'add',
|
|
1054
|
+
NEUS_MCP_SERVER_NAME,
|
|
1055
|
+
'--url',
|
|
1056
|
+
NEUS_MCP_URL,
|
|
1057
|
+
'--oauth-client-id',
|
|
1058
|
+
NEUS_OAUTH_CLIENT_ID,
|
|
1059
|
+
'--oauth-resource',
|
|
1060
|
+
NEUS_MCP_RESOURCE
|
|
1061
|
+
];
|
|
1062
|
+
if (bearerTokenEnvVar) {
|
|
1063
|
+
addArgs.push('--bearer-token-env-var', bearerTokenEnvVar);
|
|
1064
|
+
}
|
|
1065
|
+
runCommand('codex', addArgs, cwd);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
return {
|
|
1069
|
+
client: 'codex',
|
|
1070
|
+
scope,
|
|
1071
|
+
configured: true,
|
|
1072
|
+
authConfigured: bearerTokenEnvVar ? true : null,
|
|
1073
|
+
changed: true,
|
|
1074
|
+
targetPath: portablePath(codexConfigPath()),
|
|
1075
|
+
backupPath: null,
|
|
1076
|
+
dryRun,
|
|
1077
|
+
error: null
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function authCodex(scope, dryRun, cwd, cliOptions = {}) {
|
|
1082
|
+
const setupResult = installCodex(scope, '', dryRun, cwd);
|
|
1083
|
+
if (!dryRun) {
|
|
1084
|
+
printHostAuthIntro('codex', cliOptions);
|
|
1085
|
+
runCommand('codex', ['mcp', 'login', NEUS_MCP_SERVER_NAME, '--scopes', CODEX_OAUTH_SCOPES], cwd);
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
...setupResult,
|
|
1089
|
+
authConfigured: !dryRun,
|
|
1090
|
+
changed: true
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function installClient(client, scope, accessKey, dryRun, cwd) {
|
|
1095
|
+
if (client === 'cursor') return installCursor(scope, accessKey, dryRun, cwd);
|
|
1096
|
+
if (client === 'vscode') return installVsCode(scope, accessKey, dryRun, cwd);
|
|
1097
|
+
if (client === 'claude') return installClaude(scope, accessKey, dryRun, cwd);
|
|
1098
|
+
if (client === 'codex') return installCodex(scope, accessKey, dryRun, cwd);
|
|
1099
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function inspectCursor(scope, cwd) {
|
|
1103
|
+
const targetPath = cursorConfigPath(scope, cwd);
|
|
1104
|
+
if (!fileExists(targetPath)) {
|
|
1105
|
+
return {
|
|
1106
|
+
client: 'cursor',
|
|
1107
|
+
scope,
|
|
1108
|
+
configured: false,
|
|
1109
|
+
authConfigured: false,
|
|
1110
|
+
targetPath,
|
|
1111
|
+
error: null
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
const doc = readJsonFile(targetPath, {});
|
|
1115
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
1116
|
+
return {
|
|
1117
|
+
client: 'cursor',
|
|
1118
|
+
scope,
|
|
1119
|
+
configured: Boolean(server && server.url === NEUS_MCP_URL),
|
|
1120
|
+
authConfigured: Boolean(server?.headers?.Authorization),
|
|
1121
|
+
targetPath,
|
|
1122
|
+
error: null
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function inspectVsCode(scope, cwd) {
|
|
1127
|
+
const targetPath = vscodeConfigPath(scope, cwd);
|
|
1128
|
+
if (!fileExists(targetPath)) {
|
|
1129
|
+
return {
|
|
1130
|
+
client: 'vscode',
|
|
1131
|
+
scope,
|
|
1132
|
+
configured: false,
|
|
1133
|
+
authConfigured: false,
|
|
1134
|
+
targetPath,
|
|
1135
|
+
error: null
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
const doc = readJsonFile(targetPath, {});
|
|
1139
|
+
const server = doc.servers?.[NEUS_MCP_SERVER_NAME];
|
|
1140
|
+
return {
|
|
1141
|
+
client: 'vscode',
|
|
1142
|
+
scope,
|
|
1143
|
+
configured: Boolean(server && server.url === NEUS_MCP_URL),
|
|
1144
|
+
authConfigured: Boolean(server?.headers?.Authorization),
|
|
1145
|
+
targetPath,
|
|
1146
|
+
error: null
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function inspectClaude(scope, cwd) {
|
|
1151
|
+
if (scope === 'project') {
|
|
1152
|
+
const targetPath = claudeProjectConfigPath(cwd);
|
|
1153
|
+
if (!fileExists(targetPath)) {
|
|
1154
|
+
return {
|
|
1155
|
+
client: 'claude',
|
|
1156
|
+
scope,
|
|
1157
|
+
configured: false,
|
|
1158
|
+
authConfigured: false,
|
|
1159
|
+
targetPath,
|
|
1160
|
+
error: null
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
const doc = readJsonFile(targetPath, {});
|
|
1164
|
+
const server = doc.mcpServers?.[NEUS_MCP_SERVER_NAME];
|
|
1165
|
+
return {
|
|
1166
|
+
client: 'claude',
|
|
1167
|
+
scope,
|
|
1168
|
+
configured: Boolean(server && server.url === NEUS_MCP_URL),
|
|
1169
|
+
authConfigured: Boolean(server?.headers?.Authorization),
|
|
1170
|
+
targetPath,
|
|
1171
|
+
error: null
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (!commandExists('claude')) {
|
|
1176
|
+
return {
|
|
1177
|
+
client: 'claude',
|
|
1178
|
+
scope,
|
|
1179
|
+
configured: false,
|
|
1180
|
+
authConfigured: null,
|
|
1181
|
+
targetPath: '~/.claude.json',
|
|
1182
|
+
error: null
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const result = runCommand('claude', ['mcp', 'list'], cwd, true);
|
|
1187
|
+
const configured =
|
|
1188
|
+
result.status === 0 &&
|
|
1189
|
+
result.stdout.split(/\r?\n/).some(line => line.trim() === NEUS_MCP_SERVER_NAME);
|
|
1190
|
+
return {
|
|
1191
|
+
client: 'claude',
|
|
1192
|
+
scope,
|
|
1193
|
+
configured,
|
|
1194
|
+
authConfigured: configured ? null : false,
|
|
1195
|
+
targetPath: '~/.claude.json',
|
|
1196
|
+
error: null
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function inspectCodex(scope, cwd) {
|
|
1201
|
+
const targetPath = portablePath(codexConfigPath());
|
|
1202
|
+
if (scope !== 'user') {
|
|
1203
|
+
return {
|
|
1204
|
+
client: 'codex',
|
|
1205
|
+
scope,
|
|
1206
|
+
configured: false,
|
|
1207
|
+
authConfigured: null,
|
|
1208
|
+
targetPath,
|
|
1209
|
+
error: 'Codex MCP setup is user-scoped through ~/.codex/config.toml.'
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
if (!commandExists('codex')) {
|
|
1213
|
+
return {
|
|
1214
|
+
client: 'codex',
|
|
1215
|
+
scope,
|
|
1216
|
+
configured: false,
|
|
1217
|
+
authConfigured: null,
|
|
1218
|
+
targetPath,
|
|
1219
|
+
error: null
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const result = runCommand('codex', ['mcp', 'get', NEUS_MCP_SERVER_NAME], cwd, true);
|
|
1224
|
+
const configured =
|
|
1225
|
+
result.status === 0 &&
|
|
1226
|
+
result.stdout.split(/\r?\n/).some(line => line.trim() === `url: ${NEUS_MCP_URL}`);
|
|
1227
|
+
return {
|
|
1228
|
+
client: 'codex',
|
|
1229
|
+
scope,
|
|
1230
|
+
configured,
|
|
1231
|
+
authConfigured: configured ? null : false,
|
|
1232
|
+
targetPath,
|
|
1233
|
+
error: null
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function inspectClient(client, scope, cwd) {
|
|
1238
|
+
if (client === 'cursor') return inspectCursor(scope, cwd);
|
|
1239
|
+
if (client === 'vscode') return inspectVsCode(scope, cwd);
|
|
1240
|
+
if (client === 'claude') return inspectClaude(scope, cwd);
|
|
1241
|
+
if (client === 'codex') return inspectCodex(scope, cwd);
|
|
1242
|
+
throw new Error(`Unsupported client: ${client}`);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function createEmptyManifest(source) {
|
|
1246
|
+
return {
|
|
1247
|
+
schema: IMPORT_SCHEMA,
|
|
1248
|
+
source,
|
|
1249
|
+
generatedAt: new Date().toISOString(),
|
|
1250
|
+
instructions: [],
|
|
1251
|
+
memories: [],
|
|
1252
|
+
rules: [],
|
|
1253
|
+
skills: [],
|
|
1254
|
+
mcpServers: [],
|
|
1255
|
+
secretRefs: [],
|
|
1256
|
+
proofHints: {
|
|
1257
|
+
status: 'not-issued',
|
|
1258
|
+
qHashes: [],
|
|
1259
|
+
next: ['neus setup', 'neus auth', 'neus check']
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function sourceDetected(source) {
|
|
1265
|
+
if (source === 'cursor') {
|
|
1266
|
+
return (
|
|
1267
|
+
fileExists(path.join(process.cwd(), '.cursor', 'rules')) ||
|
|
1268
|
+
fileExists(path.join(process.cwd(), '.cursor', 'mcp.json'))
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
if (source === 'claude-code') {
|
|
1272
|
+
return (
|
|
1273
|
+
fileExists(path.join(os.homedir(), '.claude', 'skills')) ||
|
|
1274
|
+
fileExists(path.join(process.cwd(), '.claude', 'settings.json'))
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
if (source === 'claude-desktop') {
|
|
1278
|
+
return fileExists(path.join(os.homedir(), '.claude.json'));
|
|
1279
|
+
}
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function detectImportSources() {
|
|
1284
|
+
return SUPPORTED_IMPORT_SOURCES.filter(source => source !== 'auto' && sourceDetected(source)).map(
|
|
1285
|
+
source => ({
|
|
1286
|
+
source,
|
|
1287
|
+
detected: true
|
|
1288
|
+
})
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function chooseImportSource(requestedSource, detectedSources) {
|
|
1293
|
+
if (requestedSource && requestedSource !== 'auto') return requestedSource;
|
|
1294
|
+
const preference = ['claude-code', 'cursor', 'claude-desktop'];
|
|
1295
|
+
return (
|
|
1296
|
+
preference.find(source => detectedSources.some(candidate => candidate.source === source)) ||
|
|
1297
|
+
'cursor'
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function mergeManifest(base, next) {
|
|
1302
|
+
return {
|
|
1303
|
+
...base,
|
|
1304
|
+
instructions: [...base.instructions, ...next.instructions],
|
|
1305
|
+
memories: [...base.memories, ...next.memories],
|
|
1306
|
+
rules: [...base.rules, ...next.rules],
|
|
1307
|
+
skills: [...base.skills, ...next.skills],
|
|
1308
|
+
mcpServers: [...base.mcpServers, ...next.mcpServers],
|
|
1309
|
+
secretRefs: [...base.secretRefs, ...next.secretRefs]
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function buildCursorManifest(warnings) {
|
|
1314
|
+
const source = 'cursor';
|
|
1315
|
+
const manifest = createEmptyManifest(source);
|
|
1316
|
+
const rulesDir = path.join(process.cwd(), '.cursor', 'rules');
|
|
1317
|
+
for (const fileName of listFileNames(rulesDir, ['.mdc', '.md'])) {
|
|
1318
|
+
const targetPath = path.join(rulesDir, fileName);
|
|
1319
|
+
manifest.rules.push({
|
|
1320
|
+
name: fileName,
|
|
1321
|
+
source,
|
|
1322
|
+
path: portablePath(targetPath),
|
|
1323
|
+
bytes: statBytes(targetPath),
|
|
1324
|
+
sha256: sha256(readTextFile(targetPath))
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
manifest.mcpServers.push(
|
|
1328
|
+
...readMcpServers(path.join(process.cwd(), '.cursor', 'mcp.json'), source, warnings)
|
|
1329
|
+
);
|
|
1330
|
+
return manifest;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function buildClaudeCodeManifest(warnings) {
|
|
1334
|
+
const source = 'claude-code';
|
|
1335
|
+
const manifest = createEmptyManifest(source);
|
|
1336
|
+
const settings = instructionEntry(
|
|
1337
|
+
path.join(process.cwd(), '.claude', 'settings.json'),
|
|
1338
|
+
'.claude/settings.json'
|
|
1339
|
+
);
|
|
1340
|
+
if (settings) manifest.rules.push({ ...settings, source });
|
|
1341
|
+
for (const skillName of listDirectoryNames(path.join(os.homedir(), '.claude', 'skills'))) {
|
|
1342
|
+
manifest.skills.push({
|
|
1343
|
+
name: skillName,
|
|
1344
|
+
kind: 'skill',
|
|
1345
|
+
source,
|
|
1346
|
+
path: portablePath(path.join(os.homedir(), '.claude', 'skills', skillName)),
|
|
1347
|
+
hasSkillMd: fileExists(path.join(os.homedir(), '.claude', 'skills', skillName, 'SKILL.md'))
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
manifest.mcpServers.push(
|
|
1351
|
+
...readMcpServers(path.join(process.cwd(), '.mcp.json'), source, warnings)
|
|
1352
|
+
);
|
|
1353
|
+
return manifest;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function buildClaudeDesktopManifest(warnings) {
|
|
1357
|
+
const source = 'claude-desktop';
|
|
1358
|
+
const manifest = createEmptyManifest(source);
|
|
1359
|
+
manifest.mcpServers.push(
|
|
1360
|
+
...readMcpServers(path.join(os.homedir(), '.claude.json'), source, warnings)
|
|
1361
|
+
);
|
|
1362
|
+
return manifest;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function buildSourceManifest(source, warnings) {
|
|
1366
|
+
if (source === 'cursor') return buildCursorManifest(warnings);
|
|
1367
|
+
if (source === 'claude-code') return buildClaudeCodeManifest(warnings);
|
|
1368
|
+
if (source === 'claude-desktop') return buildClaudeDesktopManifest(warnings);
|
|
1369
|
+
throw new Error(`Unsupported import source: ${source}`);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function buildPortableManifest(requestedSource) {
|
|
1373
|
+
const warnings = [];
|
|
1374
|
+
const detectedSources = detectImportSources();
|
|
1375
|
+
const selectedSource = chooseImportSource(requestedSource, detectedSources);
|
|
1376
|
+
let manifest = buildSourceManifest(selectedSource, warnings);
|
|
1377
|
+
|
|
1378
|
+
if (requestedSource === 'auto') {
|
|
1379
|
+
for (const candidate of detectedSources) {
|
|
1380
|
+
if (candidate.source === selectedSource) continue;
|
|
1381
|
+
manifest = mergeManifest(manifest, buildSourceManifest(candidate.source, warnings));
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
manifest.generatedAt = new Date().toISOString();
|
|
1386
|
+
return { manifest, detectedSources, warnings, selectedSource };
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function importedManifestPath(source, cwd) {
|
|
1390
|
+
return path.join(cwd, '.neus', 'imported', `${source}.json`);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function latestImportedManifest(cwd) {
|
|
1394
|
+
const dir = path.join(cwd, '.neus', 'imported');
|
|
1395
|
+
if (!fileExists(dir)) return null;
|
|
1396
|
+
const candidates = fs
|
|
1397
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
1398
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.json'))
|
|
1399
|
+
.map(entry => path.join(dir, entry.name))
|
|
1400
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
1401
|
+
return candidates[0] || null;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function printJson(payload) {
|
|
1405
|
+
process.stdout.write(jsonStringify(payload));
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function clientTargetPath(client, scope, cwd) {
|
|
1409
|
+
if (client === 'cursor') return cursorConfigPath(scope, cwd);
|
|
1410
|
+
if (client === 'vscode') return vscodeConfigPath(scope, cwd);
|
|
1411
|
+
if (client === 'claude') {
|
|
1412
|
+
return scope === 'project' ? claudeProjectConfigPath(cwd) : '~/.claude.json';
|
|
1413
|
+
}
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function errorMessage(error) {
|
|
1418
|
+
return error instanceof Error ? error.message : String(error || 'Unknown error');
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function parseSseMessages(text) {
|
|
1422
|
+
const messages = [];
|
|
1423
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
1424
|
+
if (!line.startsWith('data:')) continue;
|
|
1425
|
+
const payload = line.slice(5).trim();
|
|
1426
|
+
if (!payload) continue;
|
|
1427
|
+
try {
|
|
1428
|
+
messages.push(JSON.parse(payload));
|
|
1429
|
+
} catch {
|
|
1430
|
+
// Ignore malformed SSE fragments. The caller will report the raw body preview.
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return messages;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function parseMcpResponse(text) {
|
|
1437
|
+
const trimmed = String(text || '').trim();
|
|
1438
|
+
if (!trimmed) return null;
|
|
1439
|
+
try {
|
|
1440
|
+
return JSON.parse(trimmed);
|
|
1441
|
+
} catch {
|
|
1442
|
+
return parseSseMessages(trimmed)[0] || null;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function firstTextContent(value) {
|
|
1447
|
+
const content = value?.result?.content ?? value?.content;
|
|
1448
|
+
if (!Array.isArray(content)) return '';
|
|
1449
|
+
const first = content.find(item => item?.type === 'text' && typeof item?.text === 'string');
|
|
1450
|
+
return first?.text || '';
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function parseMcpToolPayload(value) {
|
|
1454
|
+
const text = firstTextContent(value);
|
|
1455
|
+
if (text) {
|
|
1456
|
+
try {
|
|
1457
|
+
return JSON.parse(text);
|
|
1458
|
+
} catch {
|
|
1459
|
+
return { text };
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return value?.result ?? value;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
async function postMcpJsonRpc({ id, method, params, accessKey, sessionId, signal }) {
|
|
1466
|
+
const response = await fetch(NEUS_MCP_URL, {
|
|
1467
|
+
method: 'POST',
|
|
1468
|
+
headers: {
|
|
1469
|
+
accept: 'application/json, text/event-stream',
|
|
1470
|
+
'content-type': 'application/json',
|
|
1471
|
+
'mcp-protocol-version': '2025-11-25',
|
|
1472
|
+
...(accessKey ? { authorization: `Bearer ${accessKey}` } : {}),
|
|
1473
|
+
...(sessionId ? { 'mcp-session-id': sessionId } : {})
|
|
1474
|
+
},
|
|
1475
|
+
body: JSON.stringify({
|
|
1476
|
+
jsonrpc: '2.0',
|
|
1477
|
+
id,
|
|
1478
|
+
method,
|
|
1479
|
+
params: params ?? {}
|
|
1480
|
+
}),
|
|
1481
|
+
signal
|
|
1482
|
+
});
|
|
1483
|
+
const body = await response.text();
|
|
1484
|
+
return {
|
|
1485
|
+
response,
|
|
1486
|
+
body,
|
|
1487
|
+
json: parseMcpResponse(body),
|
|
1488
|
+
sessionId: response.headers.get('mcp-session-id') || sessionId || ''
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
async function callMcpTool({ name, args, accessKey, sessionId, signal }) {
|
|
1493
|
+
const result = await postMcpJsonRpc({
|
|
1494
|
+
id: 3,
|
|
1495
|
+
method: 'tools/call',
|
|
1496
|
+
params: { name, arguments: args ?? {} },
|
|
1497
|
+
accessKey,
|
|
1498
|
+
sessionId,
|
|
1499
|
+
signal
|
|
1500
|
+
});
|
|
1501
|
+
if (!result.response.ok || result.json?.error) {
|
|
1502
|
+
return {
|
|
1503
|
+
ok: false,
|
|
1504
|
+
name,
|
|
1505
|
+
status: result.response.status,
|
|
1506
|
+
error: result.json?.error?.message || result.json?.error || result.body.slice(0, 200)
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
return {
|
|
1510
|
+
ok: true,
|
|
1511
|
+
name,
|
|
1512
|
+
payload: parseMcpToolPayload(result.json)
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
async function initializeMcpSession(accessKey, signal) {
|
|
1517
|
+
const init = await postMcpJsonRpc({
|
|
1518
|
+
id: 1,
|
|
1519
|
+
method: 'initialize',
|
|
1520
|
+
params: {
|
|
1521
|
+
protocolVersion: '2025-11-25',
|
|
1522
|
+
capabilities: {},
|
|
1523
|
+
clientInfo: { name: 'neus-cli', version: CLI_PACKAGE_VERSION }
|
|
1524
|
+
},
|
|
1525
|
+
accessKey,
|
|
1526
|
+
signal
|
|
1527
|
+
});
|
|
1528
|
+
if (!init.response.ok || init.json?.error) {
|
|
1529
|
+
throw new Error(init.json?.error?.message || 'MCP initialize failed');
|
|
1530
|
+
}
|
|
1531
|
+
return { sessionId: init.sessionId || '' };
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
async function evaluateAgentMountDoctor(accessKey, cwd, signal) {
|
|
1535
|
+
const manifest = readMountManifest(cwd);
|
|
1536
|
+
const fileHealth = evaluateMountFileHealth(manifest);
|
|
1537
|
+
const out = {
|
|
1538
|
+
mountFilePresent: Boolean(manifest),
|
|
1539
|
+
mountFileValid: fileHealth.mountFileValid,
|
|
1540
|
+
mountNeedsRefresh: fileHealth.needsRefresh,
|
|
1541
|
+
mountRefreshReason: fileHealth.reason,
|
|
1542
|
+
missingDelegation: fileHealth.missingDelegation,
|
|
1543
|
+
delegationExpired: fileHealth.delegationExpired,
|
|
1544
|
+
mountAgentId: manifest?.identity?.agentId || null,
|
|
1545
|
+
agentVerified: false,
|
|
1546
|
+
agentLinkStatus: null
|
|
1547
|
+
};
|
|
1548
|
+
if (!accessKey) return out;
|
|
1549
|
+
|
|
1550
|
+
let sessionId = '';
|
|
1551
|
+
try {
|
|
1552
|
+
const init = await initializeMcpSession(accessKey, signal);
|
|
1553
|
+
sessionId = init.sessionId;
|
|
1554
|
+
} catch {
|
|
1555
|
+
return out;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
const agentId = out.mountAgentId || manifest?.identity?.agentId;
|
|
1559
|
+
const agentWallet = manifest?.identity?.agentWallet;
|
|
1560
|
+
if (agentWallet) {
|
|
1561
|
+
const link = await callMcpTool({
|
|
1562
|
+
name: 'neus_agent_link',
|
|
1563
|
+
args: { agentWallet },
|
|
1564
|
+
accessKey,
|
|
1565
|
+
sessionId,
|
|
1566
|
+
signal
|
|
1567
|
+
});
|
|
1568
|
+
if (link.ok) {
|
|
1569
|
+
out.agentLinkStatus = link.payload?.status || (link.payload?.linked ? 'ok' : 'link_required');
|
|
1570
|
+
out.agentVerified = Boolean(link.payload?.linked);
|
|
1571
|
+
}
|
|
1572
|
+
} else if (agentId) {
|
|
1573
|
+
try {
|
|
1574
|
+
const bundle = await resolveRuntimeBundleFromMcp({
|
|
1575
|
+
callMcpTool: args => callMcpTool({ ...args, accessKey, sessionId, signal }),
|
|
1576
|
+
accessKey,
|
|
1577
|
+
agentId,
|
|
1578
|
+
signal
|
|
1579
|
+
});
|
|
1580
|
+
out.agentVerified = Boolean(bundle?.trust?.identityQHash && bundle?.delegation);
|
|
1581
|
+
out.mountAgentId = bundle.identity?.agentId || agentId;
|
|
1582
|
+
} catch {
|
|
1583
|
+
out.agentVerified = false;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return out;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
async function runMount(options) {
|
|
1590
|
+
const cwd = process.cwd();
|
|
1591
|
+
const scope = resolveScope(options);
|
|
1592
|
+
const accessKey = resolveLiveAccessKey(options, scope, cwd);
|
|
1593
|
+
const agentTarget = String(options.agentTarget || options.agent || '').trim();
|
|
1594
|
+
if (!agentTarget) {
|
|
1595
|
+
throw new Error('Usage: neus mount <agentId> [--apply cursor|claude|codex]');
|
|
1596
|
+
}
|
|
1597
|
+
if (!accessKey) {
|
|
1598
|
+
throw new Error('Credential required. Run `neus auth` or pass --access-key.');
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
const controller = new AbortController();
|
|
1602
|
+
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
1603
|
+
try {
|
|
1604
|
+
const bundle = await resolveRuntimeBundleFromMcp({
|
|
1605
|
+
callMcpTool: args => callMcpTool({ ...args, accessKey, signal: controller.signal }),
|
|
1606
|
+
initializeMcp: () => initializeMcpSession(accessKey, controller.signal),
|
|
1607
|
+
accessKey,
|
|
1608
|
+
agentId: agentTarget,
|
|
1609
|
+
signal: controller.signal
|
|
1610
|
+
});
|
|
1611
|
+
|
|
1612
|
+
const applyFlavor = String(options.apply || '').trim().toLowerCase();
|
|
1613
|
+
let applyResult = null;
|
|
1614
|
+
if (applyFlavor) {
|
|
1615
|
+
if (!['cursor', 'claude', 'codex'].includes(applyFlavor)) {
|
|
1616
|
+
throw new Error('--apply must be cursor, claude, or codex');
|
|
1617
|
+
}
|
|
1618
|
+
applyResult = applyRuntimeBundle(applyFlavor, bundle, cwd, { dryRun: options.dryRun });
|
|
1619
|
+
} else if (!options.json) {
|
|
1620
|
+
applyRuntimeBundle('cursor', bundle, cwd, { dryRun: options.dryRun });
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
const payload = {
|
|
1624
|
+
command: 'mount',
|
|
1625
|
+
schema: RUNTIME_MOUNT_SCHEMA,
|
|
1626
|
+
agentId: bundle.identity.agentId,
|
|
1627
|
+
bundle,
|
|
1628
|
+
applied: applyResult,
|
|
1629
|
+
dryRun: Boolean(options.dryRun)
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
if (options.json) {
|
|
1633
|
+
printJson(payload);
|
|
1634
|
+
return payload;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
emitCliBanner(options);
|
|
1638
|
+
writeCliLine(paint('mount', 'green'));
|
|
1639
|
+
logStep('ok', 'agent', bundle.identity.agentLabel || bundle.identity.agentId);
|
|
1640
|
+
writeGuidanceLine(`Identity receipt: ${bundle.trust.identityProofUrl}`);
|
|
1641
|
+
if (bundle.trust.delegationProofUrl) {
|
|
1642
|
+
writeGuidanceLine(`Delegation receipt: ${bundle.trust.delegationProofUrl}`);
|
|
1643
|
+
} else {
|
|
1644
|
+
writeGuidanceLine('Delegation not on file — run agent setup on neus.network before scoped actions.');
|
|
1645
|
+
}
|
|
1646
|
+
if (applyResult) {
|
|
1647
|
+
for (const filePath of applyResult.written) {
|
|
1648
|
+
logStep('ok', 'wrote', filePath);
|
|
1649
|
+
}
|
|
1650
|
+
} else if (!options.dryRun) {
|
|
1651
|
+
logStep('ok', 'wrote', path.join(cwd, '.neus', 'mount.json'));
|
|
1652
|
+
}
|
|
1653
|
+
writeGuidanceLine('Start a new Agent chat so mounted rules load. Use NEUS Verify before sensitive actions.');
|
|
1654
|
+
writeCliLine('');
|
|
1655
|
+
return payload;
|
|
1656
|
+
} finally {
|
|
1657
|
+
clearTimeout(timeout);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
async function runLiveMcpDiagnostics(accessKey) {
|
|
1662
|
+
if (!accessKey) {
|
|
1663
|
+
return {
|
|
1664
|
+
live: false,
|
|
1665
|
+
reachable: false,
|
|
1666
|
+
authenticated: false,
|
|
1667
|
+
toolsCount: 0,
|
|
1668
|
+
tools: [],
|
|
1669
|
+
checks: [{ name: 'access-key', ok: false, status: 'missing' }]
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
const controller = new AbortController();
|
|
1674
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
1675
|
+
try {
|
|
1676
|
+
const init = await postMcpJsonRpc({
|
|
1677
|
+
id: 1,
|
|
1678
|
+
method: 'initialize',
|
|
1679
|
+
params: {
|
|
1680
|
+
protocolVersion: '2025-11-25',
|
|
1681
|
+
capabilities: {},
|
|
1682
|
+
clientInfo: { name: 'neus-cli', version: CLI_PACKAGE_VERSION }
|
|
1683
|
+
},
|
|
1684
|
+
accessKey,
|
|
1685
|
+
signal: controller.signal
|
|
1686
|
+
});
|
|
1687
|
+
if (!init.response.ok || init.json?.error) {
|
|
1688
|
+
return {
|
|
1689
|
+
live: true,
|
|
1690
|
+
reachable: false,
|
|
1691
|
+
authenticated: false,
|
|
1692
|
+
toolsCount: 0,
|
|
1693
|
+
tools: [],
|
|
1694
|
+
checks: [
|
|
1695
|
+
{
|
|
1696
|
+
name: 'initialize',
|
|
1697
|
+
ok: false,
|
|
1698
|
+
status: init.response.status,
|
|
1699
|
+
error: init.json?.error?.message || init.body.slice(0, 200)
|
|
1700
|
+
}
|
|
1701
|
+
]
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
const list = await postMcpJsonRpc({
|
|
1706
|
+
id: 2,
|
|
1707
|
+
method: 'tools/list',
|
|
1708
|
+
params: {},
|
|
1709
|
+
accessKey,
|
|
1710
|
+
sessionId: init.sessionId,
|
|
1711
|
+
signal: controller.signal
|
|
1712
|
+
});
|
|
1713
|
+
const tools = list.json?.result?.tools ?? list.json?.tools ?? [];
|
|
1714
|
+
const toolNames = Array.isArray(tools) ? tools.map(tool => tool.name).filter(Boolean) : [];
|
|
1715
|
+
const context = await callMcpTool({
|
|
1716
|
+
name: 'neus_context',
|
|
1717
|
+
args: {},
|
|
1718
|
+
accessKey,
|
|
1719
|
+
sessionId: init.sessionId,
|
|
1720
|
+
signal: controller.signal
|
|
1721
|
+
});
|
|
1722
|
+
const mode = context.ok ? context.payload?.mode?.current || context.payload?.mode || '' : '';
|
|
1723
|
+
const profileCtx = context.ok ? context.payload?.profileContext : null;
|
|
1724
|
+
const principal = profileCtx?.principal || null;
|
|
1725
|
+
const proofsTotal = profileCtx?.profileSummary?.proofsSummary?.total;
|
|
1726
|
+
return {
|
|
1727
|
+
live: true,
|
|
1728
|
+
reachable: true,
|
|
1729
|
+
authenticated: Boolean(accessKey) && context.ok,
|
|
1730
|
+
toolsCount: toolNames.length,
|
|
1731
|
+
tools: toolNames,
|
|
1732
|
+
contextMode: mode,
|
|
1733
|
+
sessionWallet: context.ok ? context.payload?.sessionWallet || principal?.primaryAccount || null : null,
|
|
1734
|
+
profileHandle: principal?.handle || null,
|
|
1735
|
+
proofsTotal: Number.isFinite(Number(proofsTotal)) ? Number(proofsTotal) : null,
|
|
1736
|
+
checks: [
|
|
1737
|
+
{
|
|
1738
|
+
name: 'initialize',
|
|
1739
|
+
ok: true,
|
|
1740
|
+
protocolVersion: init.json?.result?.protocolVersion || null
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
name: 'tools/list',
|
|
1744
|
+
ok: list.response.ok && !list.json?.error,
|
|
1745
|
+
status: list.response.status,
|
|
1746
|
+
toolsCount: toolNames.length
|
|
1747
|
+
},
|
|
1748
|
+
{ name: 'neus_context', ok: context.ok, mode }
|
|
1749
|
+
]
|
|
1750
|
+
};
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
return {
|
|
1753
|
+
live: true,
|
|
1754
|
+
reachable: false,
|
|
1755
|
+
authenticated: false,
|
|
1756
|
+
toolsCount: 0,
|
|
1757
|
+
tools: [],
|
|
1758
|
+
checks: [{ name: 'network', ok: false, error: errorMessage(error) }]
|
|
1759
|
+
};
|
|
1760
|
+
} finally {
|
|
1761
|
+
clearTimeout(timeout);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
function buildClientFailure(client, scope, cwd, dryRun, error) {
|
|
1766
|
+
return {
|
|
1767
|
+
client,
|
|
1768
|
+
scope,
|
|
1769
|
+
configured: false,
|
|
1770
|
+
authConfigured: false,
|
|
1771
|
+
changed: false,
|
|
1772
|
+
targetPath: clientTargetPath(client, scope, cwd),
|
|
1773
|
+
backupPath: null,
|
|
1774
|
+
dryRun,
|
|
1775
|
+
error: errorMessage(error)
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function runClientOperations(clients, scope, cwd, dryRun, runner) {
|
|
1780
|
+
return clients.map(client => {
|
|
1781
|
+
try {
|
|
1782
|
+
return runner(client);
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
return buildClientFailure(client, scope, cwd, dryRun, error);
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
|
|
1790
|
+
function printImportSummary(payload, cliOptions = {}) {
|
|
1791
|
+
emitCliBanner(cliOptions);
|
|
1792
|
+
const manifest = payload.manifest;
|
|
1793
|
+
writeCliLine(paint('import', 'green'));
|
|
1794
|
+
logStep('ok', 'source', `${manifest.source}${payload.dryRun ? ' (dry run)' : ''}`);
|
|
1795
|
+
logStep('ok', 'skills', String(manifest.skills.length));
|
|
1796
|
+
logStep('ok', 'servers', String(manifest.mcpServers.length));
|
|
1797
|
+
writeCliLine('');
|
|
1798
|
+
logStep('next', 'next', 'neus setup | neus auth');
|
|
1799
|
+
writeCliLine('');
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
function printExportSummary(payload, cliOptions = {}) {
|
|
1803
|
+
emitCliBanner(cliOptions);
|
|
1804
|
+
writeCliLine(paint('export', 'green'));
|
|
1805
|
+
logStep('ok', 'format', payload.format);
|
|
1806
|
+
logStep('ok', 'source', payload.manifest.source);
|
|
1807
|
+
if (payload.outputPath) {
|
|
1808
|
+
logStep('ok', 'output', payload.outputPath);
|
|
1809
|
+
}
|
|
1810
|
+
writeCliLine('');
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
function runInit(options) {
|
|
1814
|
+
const scope = resolveScope(options);
|
|
1815
|
+
const accessKey = resolveAccessKey(options);
|
|
1816
|
+
ensureSafeAuth('init', scope, accessKey);
|
|
1817
|
+
const cwd = process.cwd();
|
|
1818
|
+
|
|
1819
|
+
const clients = resolveClients(scope, options.clients);
|
|
1820
|
+
ensureClientSelection(scope, clients);
|
|
1821
|
+
|
|
1822
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
1823
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
1824
|
+
);
|
|
1825
|
+
const payload = {
|
|
1826
|
+
command: 'init',
|
|
1827
|
+
scope,
|
|
1828
|
+
detectedClients: defaultUserClients(),
|
|
1829
|
+
clients,
|
|
1830
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
1831
|
+
results,
|
|
1832
|
+
hasErrors: results.some(result => result.error)
|
|
1833
|
+
};
|
|
1834
|
+
|
|
1835
|
+
if (options.json) {
|
|
1836
|
+
printJson(payload);
|
|
1837
|
+
} else {
|
|
1838
|
+
printFlowSummary('init', scope, results, {
|
|
1839
|
+
nextStep: accessKey ? '' : 'neus auth',
|
|
1840
|
+
cliOptions: options
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
if (payload.hasErrors) {
|
|
1845
|
+
process.exitCode = 1;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
function base64url(buffer) {
|
|
1850
|
+
return Buffer.from(buffer)
|
|
1851
|
+
.toString('base64')
|
|
1852
|
+
.replace(/\+/g, '-')
|
|
1853
|
+
.replace(/\//g, '_')
|
|
1854
|
+
.replace(/=+$/, '');
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function generateCodeVerifier() {
|
|
1858
|
+
return base64url(randomBytes(32));
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function deriveCodeChallenge(verifier) {
|
|
1862
|
+
return base64url(createHash('sha256').update(verifier).digest());
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
async function runAuthBrowser(options) {
|
|
1866
|
+
const scope = resolveScope(options);
|
|
1867
|
+
if (scope !== 'user') {
|
|
1868
|
+
throw new Error('Browser auth only supports user scope. Remove --project flag.');
|
|
1869
|
+
}
|
|
1870
|
+
const clients = resolveClients(scope, options.clients);
|
|
1871
|
+
ensureClientSelection(scope, clients);
|
|
1872
|
+
const browserManagedClients = clients.filter(client => client !== 'codex');
|
|
1873
|
+
const hostManagedClients = clients.filter(client => client === 'codex');
|
|
1874
|
+
const cwd = process.cwd();
|
|
1875
|
+
|
|
1876
|
+
const { createServer } = await import('node:http');
|
|
1877
|
+
|
|
1878
|
+
const csrfState = randomBytes(16).toString('hex');
|
|
1879
|
+
const codeVerifier = generateCodeVerifier();
|
|
1880
|
+
const codeChallenge = deriveCodeChallenge(codeVerifier);
|
|
1881
|
+
|
|
1882
|
+
return new Promise((resolve, reject) => {
|
|
1883
|
+
let settled = false;
|
|
1884
|
+
function finish(error, value) {
|
|
1885
|
+
if (settled) return;
|
|
1886
|
+
settled = true;
|
|
1887
|
+
server.close();
|
|
1888
|
+
if (error) reject(error);
|
|
1889
|
+
else resolve(value);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
const server = createServer((req, res) => {
|
|
1893
|
+
const url = new URL(req.url, `http://127.0.0.1:${server.address().port}`);
|
|
1894
|
+
|
|
1895
|
+
// Ignore browser noise; keep the server alive for the real callback.
|
|
1896
|
+
if (url.pathname === '/favicon.ico') {
|
|
1897
|
+
res.writeHead(204);
|
|
1898
|
+
res.end();
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
if (url.pathname !== '/callback') {
|
|
1903
|
+
res.writeHead(404);
|
|
1904
|
+
res.end();
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
const returnedState = url.searchParams.get('state');
|
|
1909
|
+
if (!returnedState || returnedState !== csrfState) {
|
|
1910
|
+
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
1911
|
+
res.end('<html><body><h2>Security check failed</h2><p>Invalid request. Try again.</p></body></html>');
|
|
1912
|
+
finish(new Error('CSRF state mismatch'));
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
const code = url.searchParams.get('code');
|
|
1917
|
+
const error = url.searchParams.get('error');
|
|
1918
|
+
|
|
1919
|
+
if (error) {
|
|
1920
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1921
|
+
res.end('<html><body><h2>Authentication failed</h2><p>You can close this tab and try again.</p></body></html>');
|
|
1922
|
+
finish(new Error(`Authentication failed: ${error}`));
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
if (!code) {
|
|
1927
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1928
|
+
res.end('<html><body><h2>Missing auth code</h2><p>You can close this tab and try again.</p></body></html>');
|
|
1929
|
+
finish(new Error('No auth code received from callback'));
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
const redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
|
|
1934
|
+
const params = new URLSearchParams();
|
|
1935
|
+
params.set('grant_type', 'authorization_code');
|
|
1936
|
+
params.set('code', code);
|
|
1937
|
+
params.set('redirect_uri', redirectUri);
|
|
1938
|
+
params.set('client_id', NEUS_OAUTH_CLIENT_ID);
|
|
1939
|
+
params.set('code_verifier', codeVerifier);
|
|
1940
|
+
params.set('resource', NEUS_MCP_RESOURCE);
|
|
1941
|
+
|
|
1942
|
+
fetch(NEUS_TOKEN_ENDPOINT, {
|
|
1943
|
+
method: 'POST',
|
|
1944
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
1945
|
+
body: params.toString(),
|
|
1946
|
+
signal: AbortSignal.timeout(15_000),
|
|
1947
|
+
})
|
|
1948
|
+
.then(tokenResp => tokenResp.json())
|
|
1949
|
+
.then(tokenJson => {
|
|
1950
|
+
if (!tokenJson.access_token) {
|
|
1951
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1952
|
+
res.end('<html><body><h2>Token exchange failed</h2><p>Please try again.</p></body></html>');
|
|
1953
|
+
finish(new Error(tokenJson.error_description || tokenJson.error || 'Token exchange failed'));
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
const accessToken = tokenJson.access_token;
|
|
1958
|
+
persistOAuthTokens(tokenJson, NEUS_OAUTH_CLIENT_ID, NEUS_MCP_RESOURCE);
|
|
1959
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1960
|
+
res.end('<html><body><h2>Authenticated</h2><p>You can close this tab and return to your terminal.</p></body></html>');
|
|
1961
|
+
|
|
1962
|
+
const results = runClientOperations(browserManagedClients, scope, cwd, options.dryRun, client =>
|
|
1963
|
+
installClient(client, scope, accessToken, options.dryRun, cwd)
|
|
1964
|
+
);
|
|
1965
|
+
results.push(
|
|
1966
|
+
...runClientOperations(hostManagedClients, scope, cwd, options.dryRun, () =>
|
|
1967
|
+
authCodex(scope, options.dryRun, cwd, options)
|
|
1968
|
+
)
|
|
1969
|
+
);
|
|
1970
|
+
const payload = {
|
|
1971
|
+
command: 'auth',
|
|
1972
|
+
scope,
|
|
1973
|
+
clients,
|
|
1974
|
+
accessKeyConfigured: true,
|
|
1975
|
+
authMethod: 'browser',
|
|
1976
|
+
results,
|
|
1977
|
+
hasErrors: results.some(result => result.error)
|
|
1978
|
+
};
|
|
1979
|
+
finish(null, payload);
|
|
1980
|
+
})
|
|
1981
|
+
.catch(err => {
|
|
1982
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1983
|
+
res.end('<html><body><h2>Connection error</h2><p>Please try again.</p></body></html>');
|
|
1984
|
+
finish(err);
|
|
1985
|
+
});
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
server.listen(0, '127.0.0.1', () => {
|
|
1989
|
+
const port = server.address().port;
|
|
1990
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
1991
|
+
const authParams = new URLSearchParams({
|
|
1992
|
+
response_type: 'code',
|
|
1993
|
+
client_id: NEUS_OAUTH_CLIENT_ID,
|
|
1994
|
+
redirect_uri: redirectUri,
|
|
1995
|
+
code_challenge: codeChallenge,
|
|
1996
|
+
code_challenge_method: 'S256',
|
|
1997
|
+
state: csrfState,
|
|
1998
|
+
scope: 'neus:core neus:profile neus:secrets offline_access',
|
|
1999
|
+
resource: NEUS_MCP_RESOURCE
|
|
2000
|
+
});
|
|
2001
|
+
const authUrl = `${NEUS_APP_URL}/oauth/authorize?${authParams.toString()}`;
|
|
2002
|
+
|
|
2003
|
+
if (!options.json) {
|
|
2004
|
+
printAuthBrowserIntro(authUrl, options);
|
|
2005
|
+
logStep('next', 'wait', 'finish sign-in in the browser');
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
const openCommand = process.platform === 'win32'
|
|
2009
|
+
? `cmd /c start "" "${authUrl.replace(/"/g, '\\"')}"`
|
|
2010
|
+
: process.platform === 'darwin'
|
|
2011
|
+
? `open "${authUrl.replace(/"/g, '\\"')}"`
|
|
2012
|
+
: `xdg-open "${authUrl.replace(/"/g, '\\"')}"`;
|
|
2013
|
+
exec(openCommand, { shell: true }, err => {
|
|
2014
|
+
if (err && !options.json) {
|
|
2015
|
+
logStep('warn', 'browser', 'open the URL above manually');
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
// Timeout after 5 minutes
|
|
2021
|
+
const timeout = setTimeout(() => {
|
|
2022
|
+
finish(new Error('Authentication timed out after 5 minutes. Try again.'));
|
|
2023
|
+
}, 5 * 60 * 1000);
|
|
2024
|
+
|
|
2025
|
+
server.on('close', () => {
|
|
2026
|
+
clearTimeout(timeout);
|
|
2027
|
+
});
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function runAuth(options) {
|
|
2032
|
+
const scope = resolveScope(options);
|
|
2033
|
+
const accessKey = resolveAccessKey(options);
|
|
2034
|
+
ensureSafeAuth('auth', scope, accessKey);
|
|
2035
|
+
const cwd = process.cwd();
|
|
2036
|
+
const clients = resolveClients(scope, options.clients);
|
|
2037
|
+
ensureClientSelection(scope, clients);
|
|
2038
|
+
|
|
2039
|
+
if (!accessKey) {
|
|
2040
|
+
if (clients.length === 1 && clients[0] === 'codex') {
|
|
2041
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, () =>
|
|
2042
|
+
authCodex(scope, options.dryRun, cwd, options)
|
|
2043
|
+
);
|
|
2044
|
+
return {
|
|
2045
|
+
command: 'auth',
|
|
2046
|
+
scope,
|
|
2047
|
+
clients,
|
|
2048
|
+
accessKeyConfigured: results.some(result => result.authConfigured === true),
|
|
2049
|
+
authMethod: 'host-oauth',
|
|
2050
|
+
results,
|
|
2051
|
+
hasErrors: results.some(result => result.error)
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
return runAuthBrowser(options);
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
2058
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
2059
|
+
);
|
|
2060
|
+
const payload = {
|
|
2061
|
+
command: 'auth',
|
|
2062
|
+
scope,
|
|
2063
|
+
clients,
|
|
2064
|
+
accessKeyConfigured: true,
|
|
2065
|
+
authMethod: resolveAuthMethod(options, accessKey),
|
|
2066
|
+
results,
|
|
2067
|
+
hasErrors: results.some(result => result.error)
|
|
2068
|
+
};
|
|
2069
|
+
|
|
2070
|
+
return payload;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
async function runRefresh(options = {}) {
|
|
2074
|
+
const store = readTokenStore();
|
|
2075
|
+
if (!store?.refreshToken) {
|
|
2076
|
+
const message = 'No stored OAuth refresh token. Run `neus auth --oauth` first.';
|
|
2077
|
+
if (options.json) {
|
|
2078
|
+
printJson({ command: 'refresh', error: message });
|
|
2079
|
+
} else {
|
|
2080
|
+
writeCliLine('');
|
|
2081
|
+
writeCliLine(` ${paint('NEUS', 'green')} ${paint('refresh', 'red')}`);
|
|
2082
|
+
writeCliLine('');
|
|
2083
|
+
logStep('!', 'missing', 'no stored refresh token; run `neus auth --oauth` first');
|
|
2084
|
+
}
|
|
2085
|
+
process.exitCode = 1;
|
|
2086
|
+
return null;
|
|
2087
|
+
}
|
|
2088
|
+
try {
|
|
2089
|
+
const refreshed = await refreshOAuthToken();
|
|
2090
|
+
const expiresAtDate = new Date(refreshed.expiresAt).toLocaleString();
|
|
2091
|
+
if (options.json) {
|
|
2092
|
+
printJson({ command: 'refresh', status: 'ok', expiresAt: refreshed.expiresAt });
|
|
2093
|
+
} else {
|
|
2094
|
+
writeCliLine('');
|
|
2095
|
+
writeCliLine(` ${paint('NEUS', 'green')} ${paint('refresh', 'green')}`);
|
|
2096
|
+
writeCliLine('');
|
|
2097
|
+
logStep('ok', 'token', `rotated; valid until ${expiresAtDate}`);
|
|
2098
|
+
writeCliLine('');
|
|
2099
|
+
writeCliLine(' IDE MCP clients with their own OAuth lifecycle (Cursor with a URL-only');
|
|
2100
|
+
writeCliLine(' config) do not need this command. It is an escape hatch for clients whose');
|
|
2101
|
+
writeCliLine(' own refresh is absent or buggy. The stored access token is now fresh.');
|
|
2102
|
+
}
|
|
2103
|
+
return refreshed;
|
|
2104
|
+
} catch (err) {
|
|
2105
|
+
const message = err?.message || 'refresh failed';
|
|
2106
|
+
if (options.json) {
|
|
2107
|
+
printJson({ command: 'refresh', error: message });
|
|
2108
|
+
} else {
|
|
2109
|
+
writeCliLine('');
|
|
2110
|
+
writeCliLine(` ${paint('NEUS', 'green')} ${paint('refresh', 'red')}`);
|
|
2111
|
+
writeCliLine('');
|
|
2112
|
+
logStep('!', 'failed', message);
|
|
2113
|
+
}
|
|
2114
|
+
process.exitCode = 1;
|
|
2115
|
+
return null;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
function runStatus(options) {
|
|
2120
|
+
const scope = resolveScope(options);
|
|
2121
|
+
const cwd = process.cwd();
|
|
2122
|
+
const clients = resolveClients(scope, options.clients);
|
|
2123
|
+
ensureClientSelection(scope, clients);
|
|
2124
|
+
|
|
2125
|
+
const inspected = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
2126
|
+
inspectClient(client, scope, cwd)
|
|
2127
|
+
);
|
|
2128
|
+
const payload = {
|
|
2129
|
+
command: 'status',
|
|
2130
|
+
scope,
|
|
2131
|
+
clients: inspected,
|
|
2132
|
+
hasErrors: inspected.some(result => result.error)
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2135
|
+
if (options.json) {
|
|
2136
|
+
printJson(payload);
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
printFlowSummary('status', scope, inspected, { cliOptions: options });
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
async function runSetup(options) {
|
|
2143
|
+
const scope = resolveScope(options);
|
|
2144
|
+
const accessKey = resolveAccessKey(options);
|
|
2145
|
+
ensureSafeAuth('setup', scope, accessKey);
|
|
2146
|
+
const cwd = process.cwd();
|
|
2147
|
+
if (options.project && accessKey) {
|
|
2148
|
+
throw new Error(
|
|
2149
|
+
'Access keys are only supported in user scope. Remove --project or omit --access-key.'
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
const clients = resolveClients(scope, options.clients);
|
|
2154
|
+
ensureClientSelection(scope, clients);
|
|
2155
|
+
const initResults = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
2156
|
+
installClient(client, scope, accessKey, options.dryRun, cwd)
|
|
2157
|
+
);
|
|
2158
|
+
|
|
2159
|
+
const payload = {
|
|
2160
|
+
command: 'setup',
|
|
2161
|
+
scope,
|
|
2162
|
+
detectedClients: defaultUserClients(),
|
|
2163
|
+
clients,
|
|
2164
|
+
accessKeyConfigured: Boolean(accessKey),
|
|
2165
|
+
results: initResults,
|
|
2166
|
+
hasErrors: initResults.some(result => result.error)
|
|
2167
|
+
};
|
|
2168
|
+
|
|
2169
|
+
if (payload.hasErrors) {
|
|
2170
|
+
if (options.json) printJson(payload);
|
|
2171
|
+
else printFlowSummary('setup', scope, initResults, { cliOptions: options });
|
|
2172
|
+
process.exitCode = 1;
|
|
2173
|
+
return payload;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
if (options.json) {
|
|
2177
|
+
payload.authRequired = !accessKey && !options.dryRun;
|
|
2178
|
+
if (payload.authRequired) {
|
|
2179
|
+
payload.nextCommand = clients.length === 1 && clients[0] === 'codex'
|
|
2180
|
+
? 'neus auth --client codex'
|
|
2181
|
+
: 'neus auth';
|
|
2182
|
+
}
|
|
2183
|
+
printJson(payload);
|
|
2184
|
+
return payload;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
printFlowSummary('setup', scope, initResults, {
|
|
2188
|
+
nextStep: accessKey ? 'Run `neus examples`, then ask your assistant to use NEUS Verify.' : '',
|
|
2189
|
+
cliOptions: options
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
if (!accessKey && !options.dryRun) {
|
|
2193
|
+
const authResult = await runAuth(options);
|
|
2194
|
+
if (authResult && !authResult.hasErrors) {
|
|
2195
|
+
printFlowSummary('auth', authResult.scope, authResult.results, {
|
|
2196
|
+
nextStep: 'Run `neus examples`, then ask your assistant to use NEUS Verify.',
|
|
2197
|
+
cliOptions: options
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
if (authResult?.hasErrors) {
|
|
2201
|
+
process.exitCode = 1;
|
|
2202
|
+
}
|
|
2203
|
+
return authResult || payload;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
if (options.agent && !options.dryRun) {
|
|
2207
|
+
const mountKey = resolveLiveAccessKey(options, scope, cwd);
|
|
2208
|
+
if (mountKey) {
|
|
2209
|
+
await runMount({
|
|
2210
|
+
...options,
|
|
2211
|
+
agentTarget: options.agent,
|
|
2212
|
+
apply: options.apply || 'cursor',
|
|
2213
|
+
json: false,
|
|
2214
|
+
live: true
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
return payload;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
function runImport(options, { emitOutput = true } = {}) {
|
|
2223
|
+
if (!SUPPORTED_IMPORT_SOURCES.includes(options.source)) {
|
|
2224
|
+
throw new Error(`Unsupported import source: ${options.source}`);
|
|
2225
|
+
}
|
|
2226
|
+
const cwd = process.cwd();
|
|
2227
|
+
const { manifest, detectedSources, warnings } = buildPortableManifest(options.source);
|
|
2228
|
+
const targetPath = importedManifestPath(manifest.source, cwd);
|
|
2229
|
+
const writeResult = writeJsonFile(targetPath, manifest, options.dryRun);
|
|
2230
|
+
const payload = {
|
|
2231
|
+
command: 'import',
|
|
2232
|
+
source: options.source,
|
|
2233
|
+
selectedSource: manifest.source,
|
|
2234
|
+
dryRun: options.dryRun,
|
|
2235
|
+
detectedSources,
|
|
2236
|
+
manifest,
|
|
2237
|
+
targetPath,
|
|
2238
|
+
changed: writeResult.changed,
|
|
2239
|
+
warnings,
|
|
2240
|
+
hasErrors:
|
|
2241
|
+
manifest.instructions.length === 0 &&
|
|
2242
|
+
manifest.skills.length === 0 &&
|
|
2243
|
+
manifest.rules.length === 0 &&
|
|
2244
|
+
manifest.mcpServers.length === 0
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
if (emitOutput) {
|
|
2248
|
+
if (options.json) {
|
|
2249
|
+
printJson(payload);
|
|
2250
|
+
} else {
|
|
2251
|
+
printImportSummary(payload, options);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
if (emitOutput && payload.hasErrors) {
|
|
2256
|
+
process.exitCode = 1;
|
|
2257
|
+
}
|
|
2258
|
+
return payload;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
function runExport(options) {
|
|
2262
|
+
if (!SUPPORTED_EXPORT_FORMATS.includes(options.format)) {
|
|
2263
|
+
throw new Error(`Unsupported export format: ${options.format}`);
|
|
2264
|
+
}
|
|
2265
|
+
const cwd = process.cwd();
|
|
2266
|
+
const sourcePath = latestImportedManifest(cwd);
|
|
2267
|
+
if (!sourcePath) {
|
|
2268
|
+
throw new Error(
|
|
2269
|
+
'No local NEUS portable agent manifest found. Run `neus import --dry-run` first, then `neus import` to write one.'
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
const manifest = readJsonFile(sourcePath, null);
|
|
2273
|
+
if (!manifest || manifest.schema !== IMPORT_SCHEMA) {
|
|
2274
|
+
throw new Error(`Invalid NEUS portable agent manifest at ${sourcePath}`);
|
|
2275
|
+
}
|
|
2276
|
+
const outputPath = options.output ? path.resolve(cwd, options.output) : '';
|
|
2277
|
+
if (outputPath && !options.dryRun) {
|
|
2278
|
+
writeJsonFile(outputPath, manifest, false);
|
|
2279
|
+
}
|
|
2280
|
+
const payload = {
|
|
2281
|
+
command: 'export',
|
|
2282
|
+
format: options.format,
|
|
2283
|
+
sourcePath,
|
|
2284
|
+
outputPath,
|
|
2285
|
+
dryRun: options.dryRun,
|
|
2286
|
+
manifest
|
|
2287
|
+
};
|
|
2288
|
+
|
|
2289
|
+
if (options.json) {
|
|
2290
|
+
printJson(payload);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
printExportSummary(payload, options);
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
const ASSISTANT_EXAMPLE_PROMPTS = [
|
|
2297
|
+
'Use NEUS Verify before taking sensitive actions.',
|
|
2298
|
+
'Check whether I already have the required trust receipt.',
|
|
2299
|
+
'Verify this agent is trusted before it runs tools.',
|
|
2300
|
+
'Mount my NEUS agent context with neus_agent_mount, then follow its scoped policy.',
|
|
2301
|
+
'Use NEUS Vault before storing or using secrets.',
|
|
2302
|
+
'Show the receipt for this verification.'
|
|
2303
|
+
];
|
|
2304
|
+
|
|
2305
|
+
function runExamples(options) {
|
|
2306
|
+
const payload = {
|
|
2307
|
+
command: 'examples',
|
|
2308
|
+
intro: 'Try this in your assistant:',
|
|
2309
|
+
prompts: ASSISTANT_EXAMPLE_PROMPTS
|
|
2310
|
+
};
|
|
2311
|
+
|
|
2312
|
+
if (options.json) {
|
|
2313
|
+
printJson(payload);
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
emitCliBanner(options);
|
|
2318
|
+
writeCliLine(paint('examples', 'green'));
|
|
2319
|
+
writeCliLine('');
|
|
2320
|
+
writeCliLine(` ${paint(payload.intro, 'dim')}`);
|
|
2321
|
+
writeCliLine('');
|
|
2322
|
+
ASSISTANT_EXAMPLE_PROMPTS.forEach((prompt, index) => {
|
|
2323
|
+
writeCliLine(` ${paint(String(index + 1) + '.', 'cyan')} ${prompt}`);
|
|
2324
|
+
});
|
|
2325
|
+
writeCliLine('');
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
async function runDoctor(options) {
|
|
2329
|
+
const displayCommand = options.displayCommand || 'doctor';
|
|
2330
|
+
const scope = resolveScope(options);
|
|
2331
|
+
const cwd = process.cwd();
|
|
2332
|
+
const clients = resolveClients(scope, options.clients);
|
|
2333
|
+
ensureClientSelection(scope, clients);
|
|
2334
|
+
|
|
2335
|
+
const inspected = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
2336
|
+
inspectClient(client, scope, cwd)
|
|
2337
|
+
);
|
|
2338
|
+
const configuredClients = inspected.filter(r => r.configured);
|
|
2339
|
+
const liveAccessKey = resolveLiveAccessKey(options, scope, cwd);
|
|
2340
|
+
const payload = {
|
|
2341
|
+
command: displayCommand,
|
|
2342
|
+
scope,
|
|
2343
|
+
clients: inspected,
|
|
2344
|
+
configuredCount: configuredClients.length,
|
|
2345
|
+
accessKeyPresent: Boolean(liveAccessKey),
|
|
2346
|
+
profileConnectable: false,
|
|
2347
|
+
agentVerified: false,
|
|
2348
|
+
live: options.live,
|
|
2349
|
+
mcp: null,
|
|
2350
|
+
summary: '',
|
|
2351
|
+
hasErrors: inspected.some(result => result.error)
|
|
2352
|
+
};
|
|
2353
|
+
|
|
2354
|
+
if (options.live) {
|
|
2355
|
+
payload.mcp = await runLiveMcpDiagnostics(liveAccessKey);
|
|
2356
|
+
if (liveAccessKey) {
|
|
2357
|
+
payload.profileConnectable = Boolean(payload.mcp.authenticated);
|
|
2358
|
+
payload.hasErrors =
|
|
2359
|
+
payload.hasErrors || !payload.mcp.reachable || !payload.mcp.authenticated;
|
|
2360
|
+
try {
|
|
2361
|
+
const agentDoctor = await evaluateAgentMountDoctor(
|
|
2362
|
+
liveAccessKey,
|
|
2363
|
+
cwd,
|
|
2364
|
+
AbortSignal.timeout(20000)
|
|
2365
|
+
);
|
|
2366
|
+
payload.agentVerified = agentDoctor.agentVerified;
|
|
2367
|
+
payload.mountFilePresent = agentDoctor.mountFilePresent;
|
|
2368
|
+
payload.mountFileValid = agentDoctor.mountFileValid;
|
|
2369
|
+
payload.mountNeedsRefresh = agentDoctor.mountNeedsRefresh;
|
|
2370
|
+
payload.mountRefreshReason = agentDoctor.mountRefreshReason;
|
|
2371
|
+
payload.mountAgentId = agentDoctor.mountAgentId;
|
|
2372
|
+
payload.agentLinkStatus = agentDoctor.agentLinkStatus;
|
|
2373
|
+
payload.delegationExpired = agentDoctor.delegationExpired;
|
|
2374
|
+
payload.missingDelegation = agentDoctor.missingDelegation;
|
|
2375
|
+
} catch {
|
|
2376
|
+
payload.agentVerified = false;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
} else {
|
|
2380
|
+
const manifest = readMountManifest(cwd);
|
|
2381
|
+
payload.mountFilePresent = Boolean(manifest);
|
|
2382
|
+
payload.mountAgentId = manifest?.identity?.agentId || null;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
if (options.json) {
|
|
2386
|
+
printJson(payload);
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
if (configuredClients.length === 0) {
|
|
2391
|
+
emitCliBanner(options);
|
|
2392
|
+
writeCliLine(paint(displayCommand, 'green'));
|
|
2393
|
+
for (const result of inspected) {
|
|
2394
|
+
if (result.error) {
|
|
2395
|
+
logStep('warn', result.client, result.error);
|
|
2396
|
+
} else if (result.authConfigured === null) {
|
|
2397
|
+
logStep('skip', result.client, 'not installed');
|
|
2398
|
+
} else {
|
|
2399
|
+
logStep('skip', result.client, 'not configured');
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
writeCliLine('');
|
|
2403
|
+
writeCliLine(paint('MCP endpoint', 'cyan'));
|
|
2404
|
+
writeGuidanceLine(NEUS_MCP_URL);
|
|
2405
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
2406
|
+
writeGuidanceLine(`No selected MCP host is configured yet. Run \`${preferredSetupCommand(inspected)}\`.`);
|
|
2407
|
+
writeGuidanceLine(`Then run \`${preferredAuthCommand(inspected)}\` and re-check with \`npx -y -p @neus/sdk neus check\`.`);
|
|
2408
|
+
writeCliLine('');
|
|
2409
|
+
process.exitCode = 1;
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
printFlowSummary(displayCommand, scope, inspected, { cliOptions: options });
|
|
2414
|
+
const hasCodex = inspected.some(result => result.client === 'codex');
|
|
2415
|
+
writeCliLine(paint('Profile connection', 'cyan'));
|
|
2416
|
+
if (options.live && payload.mcp) {
|
|
2417
|
+
if (!liveAccessKey) {
|
|
2418
|
+
writeGuidanceLine(
|
|
2419
|
+
hasCodex
|
|
2420
|
+
? 'Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.'
|
|
2421
|
+
: 'No account credential found for the configured MCP clients. Run `neus auth`.'
|
|
2422
|
+
);
|
|
2423
|
+
} else {
|
|
2424
|
+
if (payload.mcp.authenticated) {
|
|
2425
|
+
const handle = payload.mcp.profileHandle ? ` as ${payload.mcp.profileHandle}` : '';
|
|
2426
|
+
const receipts =
|
|
2427
|
+
payload.mcp.proofsTotal != null ? ` · ${payload.mcp.proofsTotal} trust receipts on file` : '';
|
|
2428
|
+
logStep('ok', 'profile', `connected${handle}${receipts}`);
|
|
2429
|
+
writeGuidanceLine('NEUS Verify is ready. Ask your assistant to verify trust before sensitive actions.');
|
|
2430
|
+
writeGuidanceLine('Run `npx -y -p @neus/sdk neus examples` for starter prompts.');
|
|
2431
|
+
if (payload.mountFilePresent) {
|
|
2432
|
+
logStep('ok', 'mount', payload.mountAgentId ? `project mount: ${payload.mountAgentId}` : 'project mount on file');
|
|
2433
|
+
}
|
|
2434
|
+
if (payload.mountNeedsRefresh) {
|
|
2435
|
+
const reason =
|
|
2436
|
+
payload.delegationExpired
|
|
2437
|
+
? 'delegation expired'
|
|
2438
|
+
: payload.missingDelegation
|
|
2439
|
+
? 'delegation missing on file'
|
|
2440
|
+
: 'mount stale';
|
|
2441
|
+
logStep('warn', 'mount', `${reason} — run \`neus mount ${payload.mountAgentId || '<agentId>'} --apply cursor\``);
|
|
2442
|
+
payload.hasErrors = true;
|
|
2443
|
+
} else if (payload.agentVerified) {
|
|
2444
|
+
logStep('ok', 'agent', 'identity and delegation on file');
|
|
2445
|
+
} else if (payload.mountAgentId || payload.mountFilePresent) {
|
|
2446
|
+
writeGuidanceLine(
|
|
2447
|
+
`Mounted agent is not fully linked yet. Run \`neus mount ${payload.mountAgentId || '<agentId>'} --apply cursor\` after auth.`
|
|
2448
|
+
);
|
|
2449
|
+
payload.hasErrors = true;
|
|
2450
|
+
}
|
|
2451
|
+
} else {
|
|
2452
|
+
logStep('warn', 'profile', 'live connection was not confirmed — run `neus auth`');
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
} else if (liveAccessKey) {
|
|
2456
|
+
writeGuidanceLine('Saved credential found. Run `neus check` to confirm live connection.');
|
|
2457
|
+
} else {
|
|
2458
|
+
writeGuidanceLine(
|
|
2459
|
+
hasCodex
|
|
2460
|
+
? 'Codex owns OAuth: run `neus auth --client codex` or `codex mcp login neus`.'
|
|
2461
|
+
: 'No account credential found. Run `neus auth` for browser sign-in.'
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
writeCliLine('');
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
async function runDisconnect(options) {
|
|
2468
|
+
const scope = resolveScope(options);
|
|
2469
|
+
if (scope !== 'user') {
|
|
2470
|
+
throw new Error('Disconnect only supports user scope. Remove --project flag.');
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
const cwd = process.cwd();
|
|
2474
|
+
const token = resolveLiveAccessKey(options, scope, cwd);
|
|
2475
|
+
if (!token) {
|
|
2476
|
+
throw new Error(
|
|
2477
|
+
'Credential required. Run `neus disconnect --access-key <token>` or sign in first (`neus auth`).'
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
try {
|
|
2482
|
+
const isProfileKey = token.startsWith('npk_');
|
|
2483
|
+
const resp = isProfileKey
|
|
2484
|
+
? await fetch(NEUS_PROFILE_KEY_ENDPOINT, {
|
|
2485
|
+
method: 'DELETE',
|
|
2486
|
+
headers: {
|
|
2487
|
+
Accept: 'application/json',
|
|
2488
|
+
Authorization: `Bearer ${token}`
|
|
2489
|
+
},
|
|
2490
|
+
signal: AbortSignal.timeout(10_000),
|
|
2491
|
+
})
|
|
2492
|
+
: await fetch(NEUS_DISCONNECT_ENDPOINT, {
|
|
2493
|
+
method: 'POST',
|
|
2494
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
2495
|
+
body: new URLSearchParams({
|
|
2496
|
+
token,
|
|
2497
|
+
token_type_hint: 'access_token',
|
|
2498
|
+
client_id: NEUS_OAUTH_CLIENT_ID
|
|
2499
|
+
}).toString(),
|
|
2500
|
+
signal: AbortSignal.timeout(10_000),
|
|
2501
|
+
});
|
|
2502
|
+
|
|
2503
|
+
if (!resp.ok) {
|
|
2504
|
+
const body = await resp.json().catch(() => ({}));
|
|
2505
|
+
throw new Error(body?.error?.message || `Disconnect failed with status ${resp.status}`);
|
|
2506
|
+
}
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
if (error.message && !error.message.includes('Disconnect failed')) {
|
|
2509
|
+
throw new Error(`Disconnect request failed: ${error.message}`);
|
|
2510
|
+
}
|
|
2511
|
+
throw error;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
const clients = resolveClients(scope, options.clients);
|
|
2515
|
+
ensureClientSelection(scope, clients);
|
|
2516
|
+
|
|
2517
|
+
const results = runClientOperations(clients, scope, cwd, options.dryRun, client =>
|
|
2518
|
+
installClient(client, scope, '', options.dryRun, cwd)
|
|
2519
|
+
);
|
|
2520
|
+
|
|
2521
|
+
const payload = {
|
|
2522
|
+
command: 'disconnect',
|
|
2523
|
+
scope,
|
|
2524
|
+
clients,
|
|
2525
|
+
disconnected: true,
|
|
2526
|
+
results,
|
|
2527
|
+
hasErrors: results.some(result => result.error)
|
|
2528
|
+
};
|
|
2529
|
+
|
|
2530
|
+
if (options.json) {
|
|
2531
|
+
printJson(payload);
|
|
2532
|
+
} else {
|
|
2533
|
+
emitCliBanner(options);
|
|
2534
|
+
writeCliLine(paint('disconnect', 'green'));
|
|
2535
|
+
logStep('ok', 'signed-out', 'MCP configs updated');
|
|
2536
|
+
logStep('next', 'next', 'neus auth');
|
|
2537
|
+
writeCliLine('');
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
async function main() {
|
|
2542
|
+
try {
|
|
2543
|
+
const { command, options } = parseArgs(process.argv.slice(2));
|
|
2544
|
+
|
|
2545
|
+
if (command === 'help') {
|
|
2546
|
+
printUsage(0);
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
if (command === 'init') {
|
|
2550
|
+
runInit(options);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (command === 'auth') {
|
|
2554
|
+
const result = await runAuth(options);
|
|
2555
|
+
if (result) {
|
|
2556
|
+
if (options.json) {
|
|
2557
|
+
printJson(result);
|
|
2558
|
+
} else if (result.authMethod !== 'browser') {
|
|
2559
|
+
printFlowSummary('auth', result.scope, result.results, {
|
|
2560
|
+
nextStep: 'Run `neus examples`, then ask your assistant to use NEUS Verify.',
|
|
2561
|
+
cliOptions: options
|
|
2562
|
+
});
|
|
2563
|
+
} else {
|
|
2564
|
+
printFlowSummary('auth', result.scope, result.results, {
|
|
2565
|
+
nextStep: 'Run `neus examples`, then ask your assistant to use NEUS Verify.',
|
|
2566
|
+
cliOptions: options
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
if (result.hasErrors) {
|
|
2570
|
+
process.exitCode = 1;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
if (command === 'refresh') {
|
|
2576
|
+
await runRefresh(options);
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
if (command === 'refresh') {
|
|
2580
|
+
await runRefresh(options);
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2583
|
+
if (command === 'refresh') {
|
|
2584
|
+
await runRefresh(options);
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
if (command === 'status') {
|
|
2588
|
+
runStatus(options);
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
if (command === 'setup') {
|
|
2592
|
+
const setupResult = await runSetup(options);
|
|
2593
|
+
if (setupResult?.hasErrors) {
|
|
2594
|
+
process.exitCode = 1;
|
|
2595
|
+
}
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
if (command === 'check') {
|
|
2599
|
+
await runDoctor({ ...options, live: true, displayCommand: 'check' });
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
if (command === 'doctor') {
|
|
2603
|
+
await runDoctor(options);
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
if (command === 'mount') {
|
|
2607
|
+
await runMount(options);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
if (command === 'examples') {
|
|
2611
|
+
runExamples(options);
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
if (command === 'import') {
|
|
2615
|
+
runImport(options);
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
if (command === 'export') {
|
|
2619
|
+
runExport(options);
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
if (command === 'disconnect' || command === 'revoke') {
|
|
2623
|
+
await runDisconnect(options);
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
process.stderr.write(`Unknown subcommand: ${command}\n`);
|
|
2628
|
+
printUsage(1);
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
process.stderr.write(`${error?.message || 'Unknown error'}\n`);
|
|
2631
|
+
process.exit(1);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
main();
|