baiduwangpan-cli 1.1.5 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +504 -375
- package/bin/bdp.js +1647 -781
- package/lib/browser-login.js +427 -427
- package/lib/config.js +213 -123
- package/lib/console.js +99 -0
- package/lib/file-type.js +109 -0
- package/lib/group.js +929 -936
- package/lib/http.js +130 -122
- package/lib/index.js +7 -5
- package/lib/mcp.js +287 -0
- package/lib/name-match.js +67 -0
- package/lib/pan.js +1034 -204
- package/lib/partial.js +53 -53
- package/lib/pcs-extra.js +327 -0
- package/lib/table.js +173 -0
- package/package.json +48 -48
- package/scripts/postinstall.js +340 -340
package/bin/bdp.js
CHANGED
|
@@ -1,782 +1,1648 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* bdp — 百度网盘统一 CLI 工具
|
|
5
|
-
*
|
|
6
|
-
* 网盘文件操作:
|
|
7
|
-
* bdp ls [path] 列出目录
|
|
8
|
-
* bdp search <keyword> [-p dir] 全盘搜索
|
|
9
|
-
* bdp cat <path> 读取文件内容(免下载)
|
|
10
|
-
* bdp head [-n N] <path> 读取前 N 行
|
|
11
|
-
* bdp tail [-n N] <path> 读取后 N 行
|
|
12
|
-
* bdp grep <pattern> <path> 搜索文件内容
|
|
13
|
-
* bdp peek <path> 预览文件信息
|
|
14
|
-
* bdp get <path> [-o dir] 下载文件
|
|
15
|
-
* bdp put <local> <remote> 上传文件
|
|
16
|
-
* bdp mkdir <path> 创建目录
|
|
17
|
-
* bdp rm <path> 删除文件
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* bdp
|
|
21
|
-
* bdp
|
|
22
|
-
* bdp
|
|
23
|
-
* bdp
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* bdp
|
|
28
|
-
* bdp
|
|
29
|
-
* bdp
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
opts.flags.
|
|
97
|
-
} else if (arg === "--
|
|
98
|
-
opts.flags.
|
|
99
|
-
} else if (arg === "-
|
|
100
|
-
opts.flags.
|
|
101
|
-
} else if (arg === "
|
|
102
|
-
opts.flags.
|
|
103
|
-
} else if (arg === "
|
|
104
|
-
opts.flags.
|
|
105
|
-
} else if (arg === "
|
|
106
|
-
opts.flags.
|
|
107
|
-
} else if (arg === "--
|
|
108
|
-
opts.flags.
|
|
109
|
-
} else if (arg === "
|
|
110
|
-
opts.flags.
|
|
111
|
-
} else if (arg === "--
|
|
112
|
-
opts.flags.
|
|
113
|
-
} else if (arg === "--
|
|
114
|
-
opts.flags.
|
|
115
|
-
} else if (arg === "--
|
|
116
|
-
opts.flags.
|
|
117
|
-
} else if (arg === "--
|
|
118
|
-
opts.flags.
|
|
119
|
-
} else if (arg === "--
|
|
120
|
-
opts.flags.
|
|
121
|
-
} else if (arg === "--
|
|
122
|
-
opts.flags.
|
|
123
|
-
} else if (arg === "--
|
|
124
|
-
opts.flags.
|
|
125
|
-
} else if (arg === "--
|
|
126
|
-
opts.flags.
|
|
127
|
-
} else if (arg === "--
|
|
128
|
-
opts.flags.
|
|
129
|
-
} else if (arg === "
|
|
130
|
-
opts.flags.
|
|
131
|
-
} else if (arg === "--
|
|
132
|
-
opts.flags.
|
|
133
|
-
} else if (arg === "--
|
|
134
|
-
opts.flags.
|
|
135
|
-
} else if (arg === "--
|
|
136
|
-
opts.flags.
|
|
137
|
-
} else if (arg === "--
|
|
138
|
-
opts.flags.
|
|
139
|
-
} else if (arg
|
|
140
|
-
opts.flags
|
|
141
|
-
} else {
|
|
142
|
-
opts.
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
console.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
console.log(
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
console.log(
|
|
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
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
cmds.
|
|
398
|
-
const
|
|
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
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (args.flags.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
//
|
|
579
|
-
if (
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
cmds.
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
if (!
|
|
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
|
-
} else {
|
|
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
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
bdp
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const
|
|
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
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* bdp — 百度网盘统一 CLI 工具
|
|
5
|
+
*
|
|
6
|
+
* 网盘文件操作:
|
|
7
|
+
* bdp ls [path] 列出目录
|
|
8
|
+
* bdp search <keyword> [-p dir] 全盘搜索 (支持 --regex/--glob/--any-word/--exact)
|
|
9
|
+
* bdp cat <path> 读取文件内容(免下载)
|
|
10
|
+
* bdp head [-n N] <path> 读取前 N 行
|
|
11
|
+
* bdp tail [-n N] <path> 读取后 N 行
|
|
12
|
+
* bdp grep <pattern> <path> 搜索文件内容
|
|
13
|
+
* bdp peek <path> 预览文件信息
|
|
14
|
+
* bdp get <path> [-o dir] 下载文件 (size/md5 校验 + 断点续传; <gid>:<fs_id> 走群文件直下)
|
|
15
|
+
* bdp put <local> <remote> 上传文件
|
|
16
|
+
* bdp mkdir <path> 创建目录
|
|
17
|
+
* bdp rm <path> 删除文件
|
|
18
|
+
* bdp mv <src> <dst> 移动/重命名
|
|
19
|
+
* bdp cp <src> <dst> 拷贝
|
|
20
|
+
* bdp quota 网盘配额
|
|
21
|
+
* bdp offline add <url> / list 离线下载
|
|
22
|
+
* bdp share set/cancel/list 分享管理
|
|
23
|
+
* bdp profile list/use/unset 多账号 profile 切换
|
|
24
|
+
*
|
|
25
|
+
* 群聊文件操作:
|
|
26
|
+
* bdp groups 列出所有群组
|
|
27
|
+
* bdp gshares <gid> 列出群内分享库
|
|
28
|
+
* bdp gls <gid> <fs_id> 浏览分享库内容
|
|
29
|
+
* bdp gsearch <gid> <keyword> 搜索群文件
|
|
30
|
+
* bdp gdownload <gid> <fs_id> 直接下载群文件到本地(免转存)
|
|
31
|
+
*
|
|
32
|
+
* 配置:
|
|
33
|
+
* bdp login --bduss X --stoken Y 设置认证
|
|
34
|
+
* bdp whoami 查看当前状态
|
|
35
|
+
* bdp config 查看配置
|
|
36
|
+
*
|
|
37
|
+
* 全局选项:
|
|
38
|
+
* --json JSON 输出(Agent 友好)
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const { config } = require("../lib");
|
|
42
|
+
const pan = require("../lib/pan");
|
|
43
|
+
const group = require("../lib/group");
|
|
44
|
+
const partial = require("../lib/partial");
|
|
45
|
+
const pcsExtra = require("../lib/pcs-extra");
|
|
46
|
+
const { matchesName } = require("../lib/name-match");
|
|
47
|
+
const { ensureUtf8Console } = require("../lib/console");
|
|
48
|
+
|
|
49
|
+
// ── Utils ──────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function formatSize(bytes) {
|
|
52
|
+
if (!bytes || bytes === 0) return "-";
|
|
53
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
54
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
55
|
+
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + units[i];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatSpeed(bps) {
|
|
59
|
+
if (!bps) return "-";
|
|
60
|
+
const units = ["B/s", "KB/s", "MB/s", "GB/s"];
|
|
61
|
+
const i = Math.floor(Math.log(bps) / Math.log(1024));
|
|
62
|
+
return (bps / Math.pow(1024, i)).toFixed(1) + " " + units[i];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function output(data, jsonFlag, formatter) {
|
|
66
|
+
if (jsonFlag) {
|
|
67
|
+
console.log(JSON.stringify(data, null, 2));
|
|
68
|
+
} else if (formatter) {
|
|
69
|
+
formatter(data);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function emitJson(data, args) {
|
|
74
|
+
const json = JSON.stringify(data, null, 2);
|
|
75
|
+
if (args.flags.jsonFile) {
|
|
76
|
+
require("fs").writeFileSync(args.flags.jsonFile, json, "utf-8");
|
|
77
|
+
console.log(args.flags.jsonFile);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(json);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function verboseLog(args, msg) {
|
|
84
|
+
if (args.flags.verbose) process.stderr.write(`[verbose] ${msg}\n`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseArgs(argv) {
|
|
88
|
+
const opts = { _: [], flags: {} };
|
|
89
|
+
// "--" 之后一律按位置参数处理(如 bdp error -- -3)
|
|
90
|
+
let positionalOnly = false;
|
|
91
|
+
for (let i = 0; i < argv.length; i++) {
|
|
92
|
+
const arg = argv[i];
|
|
93
|
+
if (positionalOnly) { opts._.push(arg); continue; }
|
|
94
|
+
if (arg === "--") { positionalOnly = true; continue; }
|
|
95
|
+
if (arg === "--json") {
|
|
96
|
+
opts.flags.json = true;
|
|
97
|
+
} else if (arg === "--legacy-json") {
|
|
98
|
+
opts.flags.legacyJson = true;
|
|
99
|
+
} else if (arg === "--json-file") {
|
|
100
|
+
opts.flags.jsonFile = argv[++i];
|
|
101
|
+
} else if (arg === "--verbose") {
|
|
102
|
+
opts.flags.verbose = true;
|
|
103
|
+
} else if (arg === "--unique") {
|
|
104
|
+
opts.flags.unique = true;
|
|
105
|
+
} else if (arg === "--no-unique") {
|
|
106
|
+
opts.flags.unique = false;
|
|
107
|
+
} else if (arg === "--all" || arg === "--all-results") {
|
|
108
|
+
opts.flags.all = true;
|
|
109
|
+
} else if (arg === "--timeout") {
|
|
110
|
+
opts.flags.timeout = parseInt(argv[++i], 10) || 0;
|
|
111
|
+
} else if (arg === "--timeout-ms") {
|
|
112
|
+
opts.flags.timeoutMs = parseInt(argv[++i], 10) || 0;
|
|
113
|
+
} else if (arg === "--save-partial") {
|
|
114
|
+
opts.flags.savePartial = true;
|
|
115
|
+
} else if (arg === "--any-word") {
|
|
116
|
+
opts.flags.anyWord = true;
|
|
117
|
+
} else if (arg === "--exact") {
|
|
118
|
+
opts.flags.exact = true;
|
|
119
|
+
} else if (arg === "-n" || arg === "--lines") {
|
|
120
|
+
opts.flags.n = parseInt(argv[++i], 10) || 20;
|
|
121
|
+
} else if (arg === "-p" || arg === "--path") {
|
|
122
|
+
opts.flags.p = argv[++i];
|
|
123
|
+
} else if (arg === "-k" || arg === "--keyword") {
|
|
124
|
+
opts.flags.k = argv[++i];
|
|
125
|
+
} else if (arg === "-i" || arg === "--ignore-case") {
|
|
126
|
+
opts.flags.i = true;
|
|
127
|
+
} else if (arg === "--number" || arg === "-N") {
|
|
128
|
+
opts.flags.number = true;
|
|
129
|
+
} else if (arg === "-o" || arg === "--output") {
|
|
130
|
+
opts.flags.o = argv[++i];
|
|
131
|
+
} else if (arg === "--bduss") {
|
|
132
|
+
opts.flags.bduss = argv[++i];
|
|
133
|
+
} else if (arg === "--stoken") {
|
|
134
|
+
opts.flags.stoken = argv[++i];
|
|
135
|
+
} else if (arg === "--page") {
|
|
136
|
+
opts.flags.page = parseInt(argv[++i], 10) || 1;
|
|
137
|
+
} else if (arg === "--page-size") {
|
|
138
|
+
opts.flags.pageSize = parseInt(argv[++i], 10) || 50;
|
|
139
|
+
} else if (arg === "--top") {
|
|
140
|
+
opts.flags.top = parseInt(argv[++i], 10) || 15;
|
|
141
|
+
} else if (arg === "--limit") {
|
|
142
|
+
opts.flags.limit = parseInt(argv[++i], 10) || 50;
|
|
143
|
+
} else if (arg === "--max-results") {
|
|
144
|
+
opts.flags.maxResults = parseInt(argv[++i], 10) || 0;
|
|
145
|
+
} else if (arg === "--fields") {
|
|
146
|
+
opts.flags.fields = String(argv[++i] || "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
147
|
+
} else if (arg === "--concurrency") {
|
|
148
|
+
opts.flags.concurrency = parseInt(argv[++i], 10) || 4;
|
|
149
|
+
} else if (arg === "--depth") {
|
|
150
|
+
opts.flags.depth = parseInt(argv[++i], 10) || 2;
|
|
151
|
+
} else if (arg === "--max-nodes") {
|
|
152
|
+
opts.flags.maxNodes = parseInt(argv[++i], 10) || 2000;
|
|
153
|
+
} else if (arg === "--max-pages") {
|
|
154
|
+
opts.flags.maxPages = parseInt(argv[++i], 10) || 50;
|
|
155
|
+
} else if (arg === "--max-requests") {
|
|
156
|
+
opts.flags.maxRequests = parseInt(argv[++i], 10) || 400;
|
|
157
|
+
} else if (arg === "--no-cache") {
|
|
158
|
+
opts.flags.noCache = true;
|
|
159
|
+
} else if (arg === "--resume") {
|
|
160
|
+
opts.flags.resume = true;
|
|
161
|
+
} else if (arg === "--force") {
|
|
162
|
+
opts.flags.force = true;
|
|
163
|
+
} else if (arg === "--progress") {
|
|
164
|
+
opts.flags.progress = true;
|
|
165
|
+
} else if (arg === "--no-verify-size") {
|
|
166
|
+
opts.flags.noVerifySize = true;
|
|
167
|
+
} else if (arg === "--regex") {
|
|
168
|
+
opts.flags.regex = true;
|
|
169
|
+
} else if (arg === "--glob") {
|
|
170
|
+
opts.flags.glob = true;
|
|
171
|
+
} else if (arg === "--pwd" || arg === "--password") {
|
|
172
|
+
opts.flags.pwd = argv[++i];
|
|
173
|
+
} else if (arg === "--combined") {
|
|
174
|
+
opts.flags.combined = true;
|
|
175
|
+
} else if (arg === "--dry-run") {
|
|
176
|
+
opts.flags.dryRun = true;
|
|
177
|
+
} else if (arg === "--yes" || arg === "-y") {
|
|
178
|
+
opts.flags.yes = true;
|
|
179
|
+
} else if (arg === "--show-secrets") {
|
|
180
|
+
opts.flags.showSecrets = true;
|
|
181
|
+
} else if (arg === "--profile") {
|
|
182
|
+
opts.flags.profile = argv[++i];
|
|
183
|
+
} else if (arg === "--from-uk") {
|
|
184
|
+
opts.flags.fromUk = argv[++i];
|
|
185
|
+
} else if (arg === "--msg-id") {
|
|
186
|
+
opts.flags.msgId = argv[++i];
|
|
187
|
+
} else if (arg === "--parent-fs-id") {
|
|
188
|
+
opts.flags.parentFsId = argv[++i];
|
|
189
|
+
} else if (/^-\d/.test(arg)) {
|
|
190
|
+
// 负数按位置参数处理(如 bdp error -3)
|
|
191
|
+
opts._.push(arg);
|
|
192
|
+
} else if (arg.startsWith("-")) {
|
|
193
|
+
opts.flags[arg.replace(/^-+/, "")] = true;
|
|
194
|
+
} else {
|
|
195
|
+
opts._.push(arg);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return opts;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Commands ───────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
const cmds = {};
|
|
204
|
+
|
|
205
|
+
cmds.login = async (args, json) => {
|
|
206
|
+
// 方式 1: 手动传入凭证
|
|
207
|
+
if (args.flags.bduss || args.flags.stoken) {
|
|
208
|
+
if (!args.flags.bduss || !args.flags.stoken) {
|
|
209
|
+
throw new Error("Manual login requires both --bduss and --stoken");
|
|
210
|
+
}
|
|
211
|
+
config.setAuth({
|
|
212
|
+
bduss: args.flags.bduss,
|
|
213
|
+
stoken: args.flags.stoken,
|
|
214
|
+
webTransport: "curl",
|
|
215
|
+
});
|
|
216
|
+
const cfg = config.get();
|
|
217
|
+
output(
|
|
218
|
+
{ success: true, bduss: cfg.bduss ? "***set***" : "missing", stoken: cfg.stoken ? "***set***" : "missing" },
|
|
219
|
+
json,
|
|
220
|
+
() => console.log("Credentials saved to " + config.CONFIG_FILE)
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 方式 2: 浏览器自动登录(扫码,Agent 代劳)
|
|
226
|
+
const { browserLogin } = require("../lib/browser-login");
|
|
227
|
+
const status = (msg) => {
|
|
228
|
+
if (!json) console.log(msg);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
status("🔐 bdp login: 未提供 --bduss/--stoken,启动浏览器扫码登录...");
|
|
232
|
+
try {
|
|
233
|
+
const previous = config.get();
|
|
234
|
+
const creds = await browserLogin({
|
|
235
|
+
onStatus: status,
|
|
236
|
+
profileDir: previous.browserProfile || undefined,
|
|
237
|
+
port: previous.webTransport === "browser" ? previous.browserPort : undefined,
|
|
238
|
+
});
|
|
239
|
+
config.setAuth({
|
|
240
|
+
bduss: creds.bduss,
|
|
241
|
+
stoken: creds.stoken,
|
|
242
|
+
webTransport: "browser",
|
|
243
|
+
browserProfile: creds.browserProfile,
|
|
244
|
+
browserPort: creds.browserPort,
|
|
245
|
+
});
|
|
246
|
+
status("✅ 登录成功,凭证已保存到 " + config.CONFIG_FILE);
|
|
247
|
+
|
|
248
|
+
// 同步登录 BaiduPCS-Go 引擎(全盘操作依赖)
|
|
249
|
+
try {
|
|
250
|
+
const { spawnSync } = require("child_process");
|
|
251
|
+
const cfg = config.get();
|
|
252
|
+
const loginResult = spawnSync(cfg.pcsPath, ["login", `-bduss=${creds.bduss}`, `-stoken=${creds.stoken}`], {
|
|
253
|
+
encoding: "utf-8",
|
|
254
|
+
timeout: 30000,
|
|
255
|
+
});
|
|
256
|
+
if (loginResult.stdout && loginResult.stdout.includes("登录成功")) {
|
|
257
|
+
status("✅ BaiduPCS-Go 引擎已同步登录");
|
|
258
|
+
} else {
|
|
259
|
+
status("⚠️ BaiduPCS-Go 引擎登录失败: " + (loginResult.stdout || loginResult.stderr || "未知错误").trim().substring(0, 100));
|
|
260
|
+
}
|
|
261
|
+
} catch (e) {
|
|
262
|
+
status("⚠️ BaiduPCS-Go 引擎登录异常: " + e.message);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (json) {
|
|
266
|
+
console.log(JSON.stringify({ success: true, method: "browser-qr", bduss: "***set***", stoken: "***set***" }));
|
|
267
|
+
}
|
|
268
|
+
} catch (e) {
|
|
269
|
+
if (json) {
|
|
270
|
+
console.log(JSON.stringify({ error: e.message }));
|
|
271
|
+
} else {
|
|
272
|
+
console.error(`[ERROR] ${e.message}`);
|
|
273
|
+
console.error(" 提示: 也可手动方式 bdp login --bduss <值> --stoken <值>");
|
|
274
|
+
}
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
cmds.whoami = async (args, json) => {
|
|
280
|
+
const cfg = config.get();
|
|
281
|
+
output(
|
|
282
|
+
{
|
|
283
|
+
loggedIn: !!(cfg.bduss && cfg.stoken),
|
|
284
|
+
bduss: cfg.bduss ? "***set***" : "missing",
|
|
285
|
+
stoken: cfg.stoken ? "***set***" : "missing",
|
|
286
|
+
webTransport: cfg.webTransport,
|
|
287
|
+
pcsPath: cfg.pcsPath,
|
|
288
|
+
configFile: config.CONFIG_FILE,
|
|
289
|
+
profile: config.getProfile() || "default(全局)",
|
|
290
|
+
},
|
|
291
|
+
json,
|
|
292
|
+
(d) => {
|
|
293
|
+
console.log(`Status: ${d.loggedIn ? "✅ Logged in" : "❌ Not logged in"}`);
|
|
294
|
+
console.log(`Profile: ${d.profile}`);
|
|
295
|
+
console.log(`BDUSS: ${d.bduss}`);
|
|
296
|
+
console.log(`STOKEN: ${d.stoken}`);
|
|
297
|
+
console.log(`Web API: ${d.webTransport}`);
|
|
298
|
+
console.log(`PCS Path: ${d.pcsPath}`);
|
|
299
|
+
console.log(`Config: ${d.configFile}`);
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// 凭证脱敏:--json 曾经明文吐出完整 BDUSS/STOKEN,
|
|
305
|
+
// 而 SKILL.md 恰恰要求"Agent 优先用 --json" —— 等于每次调用都把会话密钥写进上下文。
|
|
306
|
+
// 现在统一脱敏;确需原值时显式加 --show-secrets(且该行为会在输出里标注)。
|
|
307
|
+
function maskSecret(s) {
|
|
308
|
+
if (!s) return "";
|
|
309
|
+
const str = String(s);
|
|
310
|
+
if (str.length <= 14) return str.slice(0, 4) + "***";
|
|
311
|
+
return str.slice(0, 10) + "..." + str.slice(-4);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const SECRET_KEYS = ["bduss", "stoken", "cookie"];
|
|
315
|
+
|
|
316
|
+
cmds.config = async (args, json) => {
|
|
317
|
+
const cfg = config.get();
|
|
318
|
+
const showSecrets = args.flags.showSecrets === true;
|
|
319
|
+
const masked = { ...cfg };
|
|
320
|
+
if (showSecrets) {
|
|
321
|
+
masked.redacted = false;
|
|
322
|
+
} else {
|
|
323
|
+
for (const k of SECRET_KEYS) {
|
|
324
|
+
if (masked[k]) masked[k] = maskSecret(masked[k]);
|
|
325
|
+
}
|
|
326
|
+
masked.redacted = true;
|
|
327
|
+
}
|
|
328
|
+
output(masked, json, (d) => {
|
|
329
|
+
console.log("Configuration:");
|
|
330
|
+
console.log(" bduss: " + (d.bduss || "(not set)"));
|
|
331
|
+
console.log(" stoken: " + (d.stoken || "(not set)"));
|
|
332
|
+
console.log(" web API: " + d.webTransport);
|
|
333
|
+
console.log(" pcsPath: " + d.pcsPath);
|
|
334
|
+
console.log(" ua: " + d.ua.substring(0, 40) + "...");
|
|
335
|
+
console.log(" maxBytes: " + d.maxBytes);
|
|
336
|
+
console.log(" file: " + config.CONFIG_FILE);
|
|
337
|
+
if (d.redacted) console.log(" (凭证已脱敏;如需原值加 --show-secrets)");
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// 从 BaiduPCS-Go search 输出行提取路径(格式: 序号 大小 日期 时间 路径)
|
|
342
|
+
function searchPathFromLine(line) {
|
|
343
|
+
const m = line.trim().match(/^\d+\s+\S+\s+\S+\s+\S+\s+(.+)$/);
|
|
344
|
+
return m ? m[1].trim() : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 从 glob/正则模式提取第一个字面片段(≥2 字符优先),供引擎宽词搜索。
|
|
348
|
+
// BaiduPCS-Go 的 search 自己会做 glob 展开("*.pdf" 直接返回 0 结果),
|
|
349
|
+
// 所以模式模式下必须用字面片段搜引擎,再用完整模式在客户端精确过滤。
|
|
350
|
+
function firstLiteralRun(pattern) {
|
|
351
|
+
const runs = String(pattern).split(/[.*+?^${}()|[\]\\*?]/).filter(Boolean);
|
|
352
|
+
return runs.find((r) => r.length >= 2) || runs[0] || null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// 只保留白名单字段,压缩输出体积(Agent 上下文有限)
|
|
356
|
+
function pickFields(entry, fields) {
|
|
357
|
+
const out = {};
|
|
358
|
+
for (const f of fields) if (f in entry) out[f] = entry[f];
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 人类可读的条目表格
|
|
363
|
+
function printEntries(entries) {
|
|
364
|
+
if (!entries.length) {
|
|
365
|
+
console.log("(无结果)");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
console.log(" # SIZE MODIFIED PATH");
|
|
369
|
+
entries.forEach((e) => {
|
|
370
|
+
console.log(
|
|
371
|
+
" " + String(e.index).padEnd(4) +
|
|
372
|
+
String(e.sizeText || "-").padEnd(12) +
|
|
373
|
+
String(e.mtimeText || "-").padEnd(21) +
|
|
374
|
+
e.path
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
cmds.ls = async (args, json) => {
|
|
380
|
+
const dirPath = args._[0] || "/";
|
|
381
|
+
const info = pan.lsEntries(dirPath);
|
|
382
|
+
if (json || args.flags.jsonFile) {
|
|
383
|
+
emitJson({
|
|
384
|
+
path: info.path,
|
|
385
|
+
entries: info.entries,
|
|
386
|
+
total: info.entries.length,
|
|
387
|
+
fileCount: info.fileCount,
|
|
388
|
+
dirCount: info.dirCount,
|
|
389
|
+
totalText: info.totalText,
|
|
390
|
+
warning: info.warning,
|
|
391
|
+
}, args);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
console.log(info.raw);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
cmds.search = async (args, json) => {
|
|
398
|
+
const keyword = args.flags.k || args._[0] || "";
|
|
399
|
+
const searchPath = args.flags.p || "/";
|
|
400
|
+
if (!keyword) {
|
|
401
|
+
console.error("Usage: bdp search <keyword> [-p /path] [--regex|--glob|--any-word|--exact] [--limit N] [--page N]");
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 客户端二次过滤:--regex 正则 / --glob 通配符 / --any-word OR / --exact 整名(缺省=子串,兼容旧行为)
|
|
406
|
+
const mode = args.flags.exact ? "exact" : args.flags.anyWord ? "any" : args.flags.regex ? "regex" : args.flags.glob ? "glob" : null;
|
|
407
|
+
let engineKw = keyword;
|
|
408
|
+
if (mode === "regex" || mode === "glob") {
|
|
409
|
+
engineKw = firstLiteralRun(keyword);
|
|
410
|
+
if (!engineKw) {
|
|
411
|
+
console.error("[ERROR] 模式 " + keyword + " 不含任何普通字符,无法搜索");
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
} else if (mode === "any") {
|
|
415
|
+
engineKw = keyword.split(/\s+/)[0]; // 引擎只搜第一个词,客户端按 OR 过滤
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const info = pan.searchEntries(keyword, searchPath, {
|
|
419
|
+
mode: mode || "substring",
|
|
420
|
+
engineKeyword: engineKw,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// 分页/限流:--limit 单页条数(默认 50),--page 页码,--max-results 硬上限。
|
|
424
|
+
// 全盘 search 可能命中上千条(实测"中医"1842 条 / 515KB),不分页会吃光 Agent 上下文。
|
|
425
|
+
const limit = args.flags.limit > 0 ? args.flags.limit : 50;
|
|
426
|
+
const page = args.flags.page > 0 ? args.flags.page : 1;
|
|
427
|
+
const maxResults = args.flags.maxResults > 0 ? args.flags.maxResults : 0;
|
|
428
|
+
const startIdx = (page - 1) * limit;
|
|
429
|
+
let sliced = info.entries.slice(startIdx, startIdx + limit);
|
|
430
|
+
const totalMatched = info.matched;
|
|
431
|
+
if (maxResults > 0) {
|
|
432
|
+
sliced = sliced.slice(0, Math.max(0, maxResults - startIdx));
|
|
433
|
+
}
|
|
434
|
+
const total = maxResults > 0 ? Math.min(totalMatched, maxResults) : totalMatched;
|
|
435
|
+
const hasMore = startIdx + sliced.length < totalMatched;
|
|
436
|
+
|
|
437
|
+
// --fields 白名单:只返回需要的字段
|
|
438
|
+
const fields = args.flags.fields && args.flags.fields.length ? args.flags.fields : null;
|
|
439
|
+
const entries = fields ? sliced.map((e) => pickFields(e, fields)) : sliced;
|
|
440
|
+
|
|
441
|
+
if (json || args.flags.jsonFile) {
|
|
442
|
+
emitJson({
|
|
443
|
+
keyword: info.keyword,
|
|
444
|
+
engineKeyword: info.engineKeyword,
|
|
445
|
+
path: info.path,
|
|
446
|
+
mode: info.mode,
|
|
447
|
+
total,
|
|
448
|
+
returned: entries.length,
|
|
449
|
+
page,
|
|
450
|
+
pageSize: limit,
|
|
451
|
+
hasMore,
|
|
452
|
+
nextPage: hasMore ? page + 1 : null,
|
|
453
|
+
entries,
|
|
454
|
+
warning: info.warning,
|
|
455
|
+
}, args);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
printEntries(entries);
|
|
459
|
+
console.log("");
|
|
460
|
+
console.log("共 " + total + " 条,返回 " + entries.length + " 条" + (hasMore ? "(还有更多,用 --page " + (page + 1) + " 继续)" : ""));
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
cmds.cat = async (args, json) => {
|
|
464
|
+
const filePath = args._[0];
|
|
465
|
+
if (!filePath) { console.error("Usage: bdp cat <path>"); process.exit(1); }
|
|
466
|
+
const text = pan.cat(filePath);
|
|
467
|
+
if (json) {
|
|
468
|
+
console.log(JSON.stringify({ path: filePath, content: text }));
|
|
469
|
+
} else if (args.flags.number) {
|
|
470
|
+
text.split("\n").forEach((line, i) => {
|
|
471
|
+
console.log(`${String(i + 1).padStart(6)} ${line}`);
|
|
472
|
+
});
|
|
473
|
+
} else {
|
|
474
|
+
process.stdout.write(text);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
cmds.head = async (args, json) => {
|
|
479
|
+
const filePath = args._[0];
|
|
480
|
+
if (!filePath) { console.error("Usage: bdp head [-n N] <path>"); process.exit(1); }
|
|
481
|
+
const n = args.flags.n || 20;
|
|
482
|
+
const lines = pan.head(filePath, n);
|
|
483
|
+
if (json) {
|
|
484
|
+
console.log(JSON.stringify({ path: filePath, lines: n, content: lines }));
|
|
485
|
+
} else {
|
|
486
|
+
lines.forEach((l) => console.log(l));
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
cmds.tail = async (args, json) => {
|
|
491
|
+
const filePath = args._[0];
|
|
492
|
+
if (!filePath) { console.error("Usage: bdp tail [-n N] <path>"); process.exit(1); }
|
|
493
|
+
const n = args.flags.n || 20;
|
|
494
|
+
const lines = pan.tail(filePath, n);
|
|
495
|
+
if (json) {
|
|
496
|
+
console.log(JSON.stringify({ path: filePath, lines: n, content: lines }));
|
|
497
|
+
} else {
|
|
498
|
+
lines.forEach((l) => console.log(l));
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
cmds.grep = async (args, json) => {
|
|
503
|
+
const pattern = args._[0];
|
|
504
|
+
const filePath = args._[1];
|
|
505
|
+
if (!pattern || !filePath) { console.error("Usage: bdp grep <pattern> <path>"); process.exit(1); }
|
|
506
|
+
const matches = pan.grep(pattern, filePath, { ignoreCase: args.flags.i });
|
|
507
|
+
if (json) {
|
|
508
|
+
console.log(JSON.stringify({ path: filePath, pattern, matches }));
|
|
509
|
+
} else {
|
|
510
|
+
matches.forEach((m) => {
|
|
511
|
+
if (args.flags.number) console.log(`${filePath}:${m.line}:${m.content}`);
|
|
512
|
+
else console.log(m.content);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
cmds.peek = async (args, json) => {
|
|
518
|
+
const filePath = args._[0];
|
|
519
|
+
if (!filePath) { console.error("Usage: bdp peek <path>"); process.exit(1); }
|
|
520
|
+
const info = await pan.peek(filePath);
|
|
521
|
+
output(info, json, (d) => {
|
|
522
|
+
console.log(`Path: ${d.path}`);
|
|
523
|
+
console.log(`Size: ${d.size ? formatSize(d.size) : "unknown"}`);
|
|
524
|
+
console.log(`Type: ${d.type || (d.isDir ? "directory" : "unknown")}`);
|
|
525
|
+
console.log(`Dlink: ${d.dlink ? "✅" : "❌"}`);
|
|
526
|
+
if (d.md5) console.log(`MD5: ${d.md5}`);
|
|
527
|
+
if (d.serverCtime) console.log(`Server: ${new Date(d.serverCtime * 1000).toISOString()}`);
|
|
528
|
+
if (d.localCtime) console.log(`Local: ${new Date(d.localCtime * 1000).toISOString()}`);
|
|
529
|
+
if (d.preview) {
|
|
530
|
+
console.log(`\n── Preview ──`);
|
|
531
|
+
d.preview.forEach((l) => console.log(l));
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
cmds.get = async (args, json) => {
|
|
537
|
+
const filePath = args._[0];
|
|
538
|
+
if (!filePath) { console.error("Usage: bdp get <path> [-o dir] [--resume] [--force] [--progress]"); process.exit(1); }
|
|
539
|
+
|
|
540
|
+
// 群文件路由: `bdp get <gid>:<fs_id>` → gdownload 语义(免转存直下)
|
|
541
|
+
const gmatch = filePath.match(/^(\d+):(\d+)$/);
|
|
542
|
+
if (gmatch) {
|
|
543
|
+
const opts = { outDir: args.flags.o };
|
|
544
|
+
if (args.flags.fromUk) opts.fromUk = args.flags.fromUk;
|
|
545
|
+
if (args.flags.msgId) opts.msgId = args.flags.msgId;
|
|
546
|
+
if (args.flags.filename) opts.filename = args.flags.filename;
|
|
547
|
+
if (args.flags.dryRun) {
|
|
548
|
+
const info = { path: filePath, groupDownload: true, dryRun: true, note: "群文件下载(免转存,无 size 校验/断点续传)" };
|
|
549
|
+
emitJson(info, args);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const info = await group.downloadFile(gmatch[1], gmatch[2], opts);
|
|
553
|
+
if (json || args.flags.jsonFile) emitJson({ path: filePath, groupDownload: true, name: info.name, size: info.size, localPath: info.path, fromUk: info.fromUk, msgId: info.msgId }, args);
|
|
554
|
+
else console.log(`✅ 群文件已下载 ${info.size} 字节 → ${info.path}`);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const info = await pan.download(filePath, args.flags.o, {
|
|
559
|
+
resume: args.flags.resume === true,
|
|
560
|
+
force: args.flags.force === true,
|
|
561
|
+
progress: args.flags.progress === true,
|
|
562
|
+
verifySize: args.flags.noVerifySize !== true,
|
|
563
|
+
dryRun: args.flags.dryRun === true,
|
|
564
|
+
concurrency: args.flags.concurrency || undefined,
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
if (json || args.flags.jsonFile) {
|
|
568
|
+
emitJson(info, args);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (info.dryRun) {
|
|
573
|
+
const verb = info.action === "skip" ? "跳过(已存在且大小一致)" : info.action === "resume" ? "续传" : "下载";
|
|
574
|
+
console.log(`[dry-run] ${verb}: ${info.path} (${info.size != null ? formatSize(info.size) : "大小未知"}) → ${info.localPath}`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// BaiduPCS-Go 回退引擎(目录 / 无 dlink):保持旧输出
|
|
579
|
+
if (info.engine === "pcs") {
|
|
580
|
+
console.log(info.raw);
|
|
581
|
+
if (info.warning) console.error(`⚠️ ${info.warning}`);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const head = info.skipped ? "⏭ 已存在且大小一致,跳过" : info.resumed ? "⏸ 断点续传完成" : "✅ 已下载";
|
|
586
|
+
const md5Part =
|
|
587
|
+
info.md5Match === true ? `,md5 一致 (${info.md5})`
|
|
588
|
+
: info.md5Match === false ? ",md5 与远端不一致!"
|
|
589
|
+
: info.md5 ? `,md5 ${info.md5}${info.md5Obfuscated ? "(远端 md5 为百度混淆值,未比对)" : ""}`
|
|
590
|
+
: "";
|
|
591
|
+
console.log(`${head} ${info.size} 字节 → ${info.localPath}${md5Part} ${formatSpeed(info.avgSpeedBps)}`);
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
cmds.put = async (args, json) => {
|
|
595
|
+
const local = args._[0];
|
|
596
|
+
const remote = args._[1];
|
|
597
|
+
if (!local || !remote) { console.error("Usage: bdp put <local> <remote>"); process.exit(1); }
|
|
598
|
+
if (args.flags.dryRun) {
|
|
599
|
+
console.log(`[dry-run] 将上传 ${local} → ${remote}`);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const raw = pan.upload(local, remote);
|
|
603
|
+
output({ local, remote, ok: true, raw }, json, () => console.log(raw));
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
cmds.mkdir = async (args, json) => {
|
|
607
|
+
const dirPath = args._[0];
|
|
608
|
+
if (!dirPath) { console.error("Usage: bdp mkdir <path>"); process.exit(1); }
|
|
609
|
+
const r = pan.mkdir(dirPath);
|
|
610
|
+
if (json || args.flags.jsonFile) {
|
|
611
|
+
emitJson({ path: r.path, created: r.created, exists: r.exists }, args);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
console.log(r.created ? "✅ 目录已创建: " + r.path : "ℹ️ 目录已存在: " + r.path);
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
cmds.rm = async (args, json) => {
|
|
618
|
+
const paths = args._;
|
|
619
|
+
if (paths.length === 0) {
|
|
620
|
+
console.error("Usage: bdp rm <path> [path2 ...] [--yes]");
|
|
621
|
+
process.exit(1);
|
|
622
|
+
}
|
|
623
|
+
if (args.flags.dryRun) {
|
|
624
|
+
if (json || args.flags.jsonFile) {
|
|
625
|
+
emitJson({ dryRun: true, paths }, args);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
paths.forEach((p) => console.log("[dry-run] 将删除 " + p));
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// 高危操作:非交互环境(Agent/管道)必须显式 --yes,避免误删无确认。
|
|
632
|
+
// 交互式终端则弹 y/N 确认。
|
|
633
|
+
if (!args.flags.yes) {
|
|
634
|
+
if (process.stdin.isTTY) {
|
|
635
|
+
const readline = require("readline");
|
|
636
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
637
|
+
const answer = await new Promise((resolve) => rl.question("确认删除 " + paths.length + " 个路径? [y/N] ", resolve));
|
|
638
|
+
rl.close();
|
|
639
|
+
if (!/^y(es)?$/i.test(String(answer).trim())) {
|
|
640
|
+
console.error("[ERROR] 已取消删除");
|
|
641
|
+
return 1;
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
const msg = "删除是高危操作,需要显式确认:请加 --yes(或先 --dry-run 预览)";
|
|
645
|
+
if (json || args.flags.jsonFile) {
|
|
646
|
+
emitJson({ error: msg, requiresConfirmation: true, paths }, args);
|
|
647
|
+
} else {
|
|
648
|
+
console.error("[ERROR] " + msg);
|
|
649
|
+
}
|
|
650
|
+
return 1;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const results = pan.remove(paths);
|
|
654
|
+
const failed = results.filter((r) => !r.ok);
|
|
655
|
+
if (json || args.flags.jsonFile) {
|
|
656
|
+
emitJson({
|
|
657
|
+
results,
|
|
658
|
+
deleted: results.length - failed.length,
|
|
659
|
+
failed: failed.length,
|
|
660
|
+
}, args);
|
|
661
|
+
} else {
|
|
662
|
+
results.forEach((r) => {
|
|
663
|
+
if (r.ok) console.log("✅ 已删除 " + r.path);
|
|
664
|
+
else console.error("❌ 删除失败 " + r.path + ": " + r.error);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
return failed.length > 0 ? 1 : 0;
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// ── Pan 扩展命令(BaiduPCS-Go 引擎封装)───────────────
|
|
671
|
+
|
|
672
|
+
cmds.quota = async (args, json) => {
|
|
673
|
+
const q = pcsExtra.quota();
|
|
674
|
+
output(q, json, (d) => {
|
|
675
|
+
console.log(`总空间: ${formatSize(d.total)}`);
|
|
676
|
+
console.log(`已用: ${formatSize(d.used)} (${d.ratio.toFixed(2)}%)`);
|
|
677
|
+
console.log(`剩余: ${formatSize(d.free)}`);
|
|
678
|
+
if (d.username) console.log(`用户: ${d.username}`);
|
|
679
|
+
});
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
cmds.du = async (args, json) => {
|
|
683
|
+
const dirPath = args._[0] || "/";
|
|
684
|
+
verboseLog(args, "du " + dirPath + " depth=" + (args.flags.depth || 3) + " concurrency=" + (args.flags.concurrency || 8) + " top=" + (args.flags.top || 15));
|
|
685
|
+
const result = await pan.du(dirPath, {
|
|
686
|
+
depth: args.flags.depth,
|
|
687
|
+
concurrency: args.flags.concurrency,
|
|
688
|
+
top: args.flags.top,
|
|
689
|
+
onProgress: args.flags.progress ? (p) => {
|
|
690
|
+
process.stderr.write("[du] 已扫描 " + p.scannedDirs + " 目录 / " + p.files + " 文件 / 队列 " + p.pending + " / " + (p.elapsedMs / 1000).toFixed(1) + "s\n");
|
|
691
|
+
} : undefined,
|
|
692
|
+
});
|
|
693
|
+
if (json || args.flags.jsonFile) {
|
|
694
|
+
emitJson(result, args);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
console.log(formatSize(result.size) + " " + result.path + " (" + result.files + " 文件 / " + result.dirs + " 子目录" +
|
|
698
|
+
(result.truncated ? " / 仅显示前" + (args.flags.top || 15) + "项" : "") +
|
|
699
|
+
(result.failed ? " / " + result.failed + " 目录读取失败" : "") +
|
|
700
|
+
" " + (result.elapsedMs / 1000).toFixed(1) + "s)");
|
|
701
|
+
if (result.partial) {
|
|
702
|
+
console.log("⚠️ 深度封顶(上限 " + result.depthLimit + ")→ size 不完整;实际占用以 bdp quota 为准,可加大 --depth");
|
|
703
|
+
}
|
|
704
|
+
console.log("");
|
|
705
|
+
result.children.forEach((c) => {
|
|
706
|
+
console.log(" " + formatSize(c.size).padStart(10) + " " + c.path +
|
|
707
|
+
(c.capped ? " [深度封顶]" : "") + (c.partial && !c.capped ? " [子树未扫全]" : ""));
|
|
708
|
+
});
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
cmds.recycle = async (args, json) => {
|
|
712
|
+
const action = args._[0] || "list";
|
|
713
|
+
if (action === "list") {
|
|
714
|
+
const r = pcsExtra.recycleList(args.flags.page || 1);
|
|
715
|
+
output(r, json, (d) => {
|
|
716
|
+
if (d.count === 0) { console.log("回收站为空"); return; }
|
|
717
|
+
console.log(`${d.count} 项回收站内容:`);
|
|
718
|
+
d.items.forEach((it) => {
|
|
719
|
+
console.log(` [${it.index}] ${it.path} ${it.size != null ? formatSize(it.size) : "-"} ${it.remainDays != null ? "剩余" + it.remainDays + "天" : ""} fs_id:${it.fsId}`);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
} else if (action === "restore") {
|
|
723
|
+
const ids = args._.slice(1);
|
|
724
|
+
if (ids.length === 0) { console.error("Usage: bdp recycle restore <fs_id>..."); process.exit(1); }
|
|
725
|
+
const r = pcsExtra.recycleRestore(ids);
|
|
726
|
+
if (json || args.flags.jsonFile) emitJson({ restored: ids, ok: true }, args);
|
|
727
|
+
else console.log(`✅ 已还原 ${ids.length} 项: ${ids.join(", ")}`);
|
|
728
|
+
} else if (action === "clean") {
|
|
729
|
+
const r = pcsExtra.recycleClean();
|
|
730
|
+
if (json || args.flags.jsonFile) emitJson({ cleaned: true }, args);
|
|
731
|
+
else console.log("✅ 回收站已清空(不可恢复)");
|
|
732
|
+
} else {
|
|
733
|
+
console.error("Usage: bdp recycle list | restore <fs_id>... | clean");
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
cmds.mv = async (args, json) => {
|
|
740
|
+
const src = args._[0];
|
|
741
|
+
const dst = args._[1];
|
|
742
|
+
if (!src || !dst) { console.error("Usage: bdp mv <src> <dst>"); process.exit(1); }
|
|
743
|
+
const r = pcsExtra.mv(src, dst);
|
|
744
|
+
output(r, json, () => console.log(`✅ ${r.op === "rename" ? "已重命名" : "已移动"}: ${src} → ${dst}`));
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
cmds.cp = async (args, json) => {
|
|
748
|
+
const src = args._[0];
|
|
749
|
+
const dst = args._[1];
|
|
750
|
+
if (!src || !dst) { console.error("Usage: bdp cp <src> <dst>"); process.exit(1); }
|
|
751
|
+
const r = pcsExtra.cp(src, dst);
|
|
752
|
+
output(r, json, () => console.log(`✅ 已拷贝: ${src} → ${dst}`));
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
cmds.offline = async (args, json) => {
|
|
756
|
+
const action = args._[0] || "list";
|
|
757
|
+
if (action === "add") {
|
|
758
|
+
const url = args._[1];
|
|
759
|
+
if (!url) { console.error("Usage: bdp offline add <http_url> [--path /保存目录]"); process.exit(1); }
|
|
760
|
+
const r = pcsExtra.offlineAdd(url, args.flags.path);
|
|
761
|
+
output(r, json, () => console.log(`✅ 离线任务已创建: taskId=${r.taskId} (${url})${r.savePath ? ` → ${r.savePath}` : ""}`));
|
|
762
|
+
} else if (action === "list") {
|
|
763
|
+
const r = pcsExtra.offlineList();
|
|
764
|
+
output(r, json, (d) => {
|
|
765
|
+
console.log(`${d.count} 个离线任务:`);
|
|
766
|
+
d.tasks.forEach((t) => {
|
|
767
|
+
console.log(` [${t.index}] ${t.name} ${formatSize(t.size)} ${t.status} taskId:${t.taskId}`);
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
} else {
|
|
771
|
+
console.error("Usage: bdp offline add <http_url> [--path /保存目录] | bdp offline list");
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
cmds.share = async (args, json) => {
|
|
777
|
+
const action = args._[0] || "list";
|
|
778
|
+
if (action === "set") {
|
|
779
|
+
const p = args._[1];
|
|
780
|
+
if (!p) { console.error("Usage: bdp share set <path> [--pwd 提取码] [--combined]"); process.exit(1); }
|
|
781
|
+
const r = pcsExtra.shareSet(p, args.flags.pwd, { combined: args.flags.combined });
|
|
782
|
+
output(r, json, () => {
|
|
783
|
+
const link = r.combined ? `${r.link}?pwd=${r.pwd}` : r.link;
|
|
784
|
+
console.log(`✅ 分享创建成功: ${link}${!r.combined && r.pwd ? ` 提取码: ${r.pwd}` : ""} shareId:${r.shareId}`);
|
|
785
|
+
});
|
|
786
|
+
} else if (action === "cancel") {
|
|
787
|
+
const ids = args._.slice(1);
|
|
788
|
+
if (ids.length === 0) { console.error("Usage: bdp share cancel <shareId> [shareId2 ...]"); process.exit(1); }
|
|
789
|
+
const results = ids.map((id) => {
|
|
790
|
+
const r = pcsExtra.shareCancel(id);
|
|
791
|
+
return { shareId: id, ok: r.ok };
|
|
792
|
+
});
|
|
793
|
+
if (json || args.flags.jsonFile) emitJson(results, args);
|
|
794
|
+
else results.forEach((r) => console.log(`✅ 已取消分享 ${r.shareId}`));
|
|
795
|
+
} else if (action === "list") {
|
|
796
|
+
const r = await pcsExtra.shareListSafe(args.flags.page || 1);
|
|
797
|
+
output(r, json, (d) => {
|
|
798
|
+
console.log(`${d.count} 个分享(来源: ${d.source || "engine"}${d.total ? " / 共 " + d.total : ""}):`);
|
|
799
|
+
d.shares.forEach((s) => {
|
|
800
|
+
console.log(` [${s.index}] ${s.link}${s.pwd ? ` 提取码: ${s.pwd}` : ""} shareId:${s.shareId} ${s.expireText || ""}`);
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
} else {
|
|
804
|
+
console.error("Usage: bdp share set <path> [--pwd 提取码] | bdp share cancel <shareId> | bdp share list");
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
cmds.profile = async (args, json) => {
|
|
810
|
+
const action = args._[0] || "list";
|
|
811
|
+
if (action === "use") {
|
|
812
|
+
const name = args._[1];
|
|
813
|
+
if (!name) { console.error("Usage: bdp profile use <name> | bdp profile unset | bdp profile list"); process.exit(1); }
|
|
814
|
+
const n = config.useProfile(name);
|
|
815
|
+
output({ profile: n }, json, () => console.log(`✅ 已切换到 profile: ${n}`));
|
|
816
|
+
} else if (action === "unset") {
|
|
817
|
+
config.unsetProfile();
|
|
818
|
+
output({ profile: null }, json, () => console.log("✅ 已切回全局账号"));
|
|
819
|
+
} else {
|
|
820
|
+
const current = config.getProfile() || "default(全局)";
|
|
821
|
+
const list = config.listProfiles();
|
|
822
|
+
output({ current, profiles: list }, json, (d) => {
|
|
823
|
+
console.log(`当前: ${d.current}`);
|
|
824
|
+
console.log(`${d.profiles.length} 个 profile:`);
|
|
825
|
+
d.profiles.forEach((p) => console.log(` ${p.name} ${p.bduss ? "bduss:***set***" : "(空)"}`));
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// ── Group Commands ─────────────────────────────────────
|
|
831
|
+
|
|
832
|
+
cmds.groups = async (args, json) => {
|
|
833
|
+
const groups = await group.listGroups();
|
|
834
|
+
output(groups, json, (list) => {
|
|
835
|
+
console.log(`${list.length} groups:\n`);
|
|
836
|
+
list.forEach((g, i) => {
|
|
837
|
+
console.log(` [${i}] ${g.name} (gid: ${g.gid})`);
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
cmds.gshares = async (args, json) => {
|
|
843
|
+
const gid = args._[0];
|
|
844
|
+
if (!gid) { console.error("Usage: bdp gshares <gid>"); process.exit(1); }
|
|
845
|
+
const shares = await group.listShares(gid, { cache: args.flags.noCache !== true });
|
|
846
|
+
output(shares, json, (list) => {
|
|
847
|
+
console.log(`${list.length} shares in group ${gid}:\n`);
|
|
848
|
+
list.forEach((s, i) => {
|
|
849
|
+
console.log(` [${i}] ${s.name} ${s.isDir ? "[DIR]" : ""} ${formatSize(s.size)} fs_id:${s.fsId}`);
|
|
850
|
+
console.log(` shared by: ${s.fromUser} msg_id:${s.msgId}`);
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
cmds.gls = async (args, json) => {
|
|
856
|
+
const gid = args._[0];
|
|
857
|
+
const fsId = args._[1];
|
|
858
|
+
if (!gid || !fsId) { console.error("Usage: bdp gls <gid> <fs_id> [--page N] [--page-size N]"); process.exit(1); }
|
|
859
|
+
|
|
860
|
+
const opts = {
|
|
861
|
+
page: args.flags.page || 1,
|
|
862
|
+
pageSize: args.flags.pageSize || 50,
|
|
863
|
+
cache: args.flags.noCache !== true,
|
|
864
|
+
};
|
|
865
|
+
if (args.flags.fromUk) opts.fromUk = args.flags.fromUk;
|
|
866
|
+
if (args.flags.msgId) opts.msgId = args.flags.msgId;
|
|
867
|
+
if (args.flags.parentFsId) opts.parentFsId = args.flags.parentFsId;
|
|
868
|
+
|
|
869
|
+
verboseLog(args, `gls gid=${gid} fsId=${fsId} page=${opts.page} pageSize=${opts.pageSize}`);
|
|
870
|
+
const result = await group.listFiles(gid, fsId, opts);
|
|
871
|
+
|
|
872
|
+
if (args.flags.legacyJson) {
|
|
873
|
+
emitJson(result.files, args);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (result.autoResolved) {
|
|
878
|
+
console.error(`[auto] 传入的 fromUk/msgId 与群 ${gid} 不匹配(API errno=2131),已自动改用 gshares 解析的参数 (fromUk=${result.fromUk}, msgId=${result.msgId})`);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (result.fallback) {
|
|
882
|
+
verboseLog(args, `fallback: ${result.fallback.reason} -> ${result.fallback.resolvedFsId} (${result.fallback.level})`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (args.flags.jsonFile || json) {
|
|
886
|
+
emitJson(result, args);
|
|
887
|
+
} else {
|
|
888
|
+
if (result.fallback) {
|
|
889
|
+
console.log(`⚠️ fallback: ${result.fallback.reason} — 已回退到 ${result.fallback.resolvedFsId} (${result.fallback.level})`);
|
|
890
|
+
}
|
|
891
|
+
console.log(`${result.files.length} items${result.hasMore ? " (has more — use --page)" : ""}:\n`);
|
|
892
|
+
result.files.forEach((f) => {
|
|
893
|
+
const dir = f.isDir ? "[DIR] " : " ";
|
|
894
|
+
console.log(` ${dir}${f.name} ${formatSize(f.size)} fs_id:${f.fsId}`);
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
cmds.gtree = async (args, json) => {
|
|
900
|
+
const gid = args._[0];
|
|
901
|
+
if (!gid) { console.error("Usage: bdp gtree <gid> [--depth N] [--concurrency N] [--max-nodes N]"); process.exit(1); }
|
|
902
|
+
|
|
903
|
+
const opts = {
|
|
904
|
+
depth: args.flags.depth || 2,
|
|
905
|
+
concurrency: args.flags.concurrency || 4,
|
|
906
|
+
maxNodes: args.flags.maxNodes || 2000,
|
|
907
|
+
maxPages: args.flags.maxPages || 50,
|
|
908
|
+
maxRequests: args.flags.maxRequests || 400,
|
|
909
|
+
cache: args.flags.noCache !== true,
|
|
910
|
+
};
|
|
911
|
+
verboseLog(args, `gtree gid=${gid} depth=${opts.depth} concurrency=${opts.concurrency} maxNodes=${opts.maxNodes} maxPages=${opts.maxPages} maxRequests=${opts.maxRequests} cache=${opts.cache}`);
|
|
912
|
+
const result = await group.treeFiles(gid, opts);
|
|
913
|
+
|
|
914
|
+
if (args.flags.jsonFile || json) {
|
|
915
|
+
emitJson(result, args);
|
|
916
|
+
} else {
|
|
917
|
+
console.log(`${result.tree.length} nodes (depth ${result.depth}, ${result.failed.length} failed${result.truncated ? ", truncated" : ""}):\n`);
|
|
918
|
+
result.tree.forEach((n) => {
|
|
919
|
+
const dir = n.isDir ? "[DIR] " : " ";
|
|
920
|
+
console.log(` ${dir}${n.path || n.name} ${formatSize(n.size)} fs_id:${n.fsId}`);
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
cmds.gsearch = async (args, json) => {
|
|
926
|
+
const gid = args._[0];
|
|
927
|
+
const keyword = args._[1];
|
|
928
|
+
if (!gid || !keyword) { console.error("Usage: bdp gsearch <gid> <keyword> [--page N] [--limit N]"); process.exit(1); }
|
|
929
|
+
|
|
930
|
+
const opts = {
|
|
931
|
+
page: args.flags.page || 1,
|
|
932
|
+
limit: args.flags.limit || 50,
|
|
933
|
+
concurrency: args.flags.concurrency || 4,
|
|
934
|
+
unique: args.flags.unique !== false,
|
|
935
|
+
all: args.flags.all === true,
|
|
936
|
+
exact: args.flags.exact === true,
|
|
937
|
+
anyWord: args.flags.anyWord === true,
|
|
938
|
+
timeoutMs: args.flags.timeoutMs || (args.flags.timeout ? args.flags.timeout * 1000 : 0),
|
|
939
|
+
depth: args.flags.depth || 1,
|
|
940
|
+
maxPages: args.flags.maxPages || 50,
|
|
941
|
+
maxRequests: args.flags.maxRequests || 400,
|
|
942
|
+
cache: args.flags.noCache !== true,
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
// --save-partial:未完整(超时/限流/预算/失败)时自动把已搜到的结果落盘;
|
|
946
|
+
// 未指定 --json-file 时自动生成路径(bdp-gsearch-<gid>-<kw>-partial-<ts>.json)
|
|
947
|
+
const savePartial = args.flags.savePartial === true;
|
|
948
|
+
const jsonFile = args.flags.jsonFile;
|
|
949
|
+
const partialFile = jsonFile || (savePartial ? partial.defaultPartialFile(gid, keyword) : null);
|
|
950
|
+
const fsMod = require("fs");
|
|
951
|
+
|
|
952
|
+
// 扫描过程中持续写部分结果(--json-file 或 --save-partial 自动文件),
|
|
953
|
+
// 即使被外部超时/杀掉,已搜到的结果也已落盘
|
|
954
|
+
let lastProgressWrite = 0;
|
|
955
|
+
if (partialFile) {
|
|
956
|
+
fsMod.writeFileSync(partialFile, JSON.stringify({ running: true, results: [], complete: false, partial: true }, null, 2), "utf-8");
|
|
957
|
+
}
|
|
958
|
+
opts.onProgress = (snap) => {
|
|
959
|
+
if (!partialFile) return;
|
|
960
|
+
const now = Date.now();
|
|
961
|
+
if (snap.running && now - lastProgressWrite < 400) return; // 节流,避免频繁写盘
|
|
962
|
+
lastProgressWrite = now;
|
|
963
|
+
fsMod.writeFileSync(
|
|
964
|
+
partialFile,
|
|
965
|
+
JSON.stringify(
|
|
966
|
+
{
|
|
967
|
+
running: snap.running,
|
|
968
|
+
results: snap.results,
|
|
969
|
+
page: snap.page,
|
|
970
|
+
pageSize: snap.pageSize,
|
|
971
|
+
complete: snap.complete,
|
|
972
|
+
partial: snap.partial,
|
|
973
|
+
timedOut: snap.timedOut,
|
|
974
|
+
stoppedReason: snap.stoppedReason,
|
|
975
|
+
scannedShares: snap.scannedShares,
|
|
976
|
+
totalShares: snap.totalShares,
|
|
977
|
+
failedShares: snap.failedShares,
|
|
978
|
+
throttledShares: snap.throttledShares,
|
|
979
|
+
cachedDirs: snap.cachedDirs,
|
|
980
|
+
budgetUsed: snap.budgetUsed,
|
|
981
|
+
depth: snap.depth,
|
|
982
|
+
maxPages: snap.maxPages,
|
|
983
|
+
},
|
|
984
|
+
null,
|
|
985
|
+
2
|
|
986
|
+
),
|
|
987
|
+
"utf-8"
|
|
988
|
+
);
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
verboseLog(args, `gsearch gid=${gid} keyword="${keyword}" page=${opts.page} limit=${opts.limit} concurrency=${opts.concurrency} unique=${opts.unique} all=${opts.all} exact=${opts.exact} anyWord=${opts.anyWord} timeoutMs=${opts.timeoutMs} savePartial=${savePartial} depth=${opts.depth} maxPages=${opts.maxPages} maxRequests=${opts.maxRequests} cache=${opts.cache}`);
|
|
992
|
+
const result = await group.searchFiles(gid, keyword, opts);
|
|
993
|
+
|
|
994
|
+
if (result.partial) {
|
|
995
|
+
verboseLog(args, `partial: ${result.failedShares}/${result.totalShares} shares failed`);
|
|
996
|
+
if (result.failedDirs && result.failedDirs.length > 0) {
|
|
997
|
+
verboseLog(args, `failed dirs: ${result.failedDirs.map((f) => `${f.name || f.fsId}(${f.errno})`).slice(0, 10).join(", ")}`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (result.throttled) {
|
|
1001
|
+
verboseLog(args, `throttled: API 限流中断,重跑命令可续扫(磁盘缓存只补缺失目录)`);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (args.flags.legacyJson) {
|
|
1005
|
+
emitJson(result.results, args);
|
|
1006
|
+
} else if (args.flags.jsonFile || json) {
|
|
1007
|
+
emitJson(result, args);
|
|
1008
|
+
} else {
|
|
1009
|
+
console.log(`${result.returned} matches for "${keyword}"${result.hasMore ? " (has more — use --page " + (result.nextPage || "?") + ")" : ""}:\n`);
|
|
1010
|
+
result.results.forEach((r) => {
|
|
1011
|
+
const dir = r.isDir ? "[DIR] " : " ";
|
|
1012
|
+
console.log(` ${dir}${r.path || r.name} ${formatSize(r.size)} fs_id:${r.fsId}`);
|
|
1013
|
+
});
|
|
1014
|
+
if (result.timedOut) {
|
|
1015
|
+
console.error(`⚠️ 搜索超时(${Math.round(opts.timeoutMs / 1000)}s),已返回部分结果(complete:false, partial:true)。重跑或增大 --timeout / 用 --all-results 不限时`);
|
|
1016
|
+
} else if (result.throttled) {
|
|
1017
|
+
console.error(`⚠️ API 限流中断(已扫描 ${result.scannedShares}/${result.totalShares} 分享,失败 ${result.failedShares})。重跑命令可续扫(磁盘缓存加速)`);
|
|
1018
|
+
} else if (result.partial) {
|
|
1019
|
+
console.error(`[verbose] ${result.failedShares} shares failed (partial result)`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// --save-partial:未完整时把已搜到的结果写盘并告知路径(stderr,不污染 JSON stdout)
|
|
1024
|
+
if (savePartial && partialFile && result.partial) {
|
|
1025
|
+
if (!jsonFile) {
|
|
1026
|
+
fsMod.writeFileSync(
|
|
1027
|
+
partialFile,
|
|
1028
|
+
JSON.stringify(partial.buildPartialPayload(result, { gid, keyword }), null, 2),
|
|
1029
|
+
"utf-8"
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
console.error(`💾 部分结果已保存: ${partialFile}`);
|
|
1033
|
+
} else if (savePartial && partialFile && !result.partial && !jsonFile) {
|
|
1034
|
+
// 自动生成的文件且搜索完整 → 清理占位文件,避免留下垃圾
|
|
1035
|
+
try { fsMod.unlinkSync(partialFile); } catch {}
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
cmds.gdownload = async (args, json) => {
|
|
1040
|
+
const gid = args._[0];
|
|
1041
|
+
const fsIds = args._.slice(1);
|
|
1042
|
+
if (!gid || fsIds.length === 0) { console.error("Usage: bdp gdownload <gid> <fs_id> [fs_id2 ...] [-o dir] [--from-uk X] [--msg-id Y] [--filename NAME]"); process.exit(1); }
|
|
1043
|
+
|
|
1044
|
+
const opts = {
|
|
1045
|
+
cache: args.flags.noCache !== true,
|
|
1046
|
+
};
|
|
1047
|
+
if (args.flags.fromUk) opts.fromUk = args.flags.fromUk;
|
|
1048
|
+
if (args.flags.msgId) opts.msgId = args.flags.msgId;
|
|
1049
|
+
if (args.flags.o) opts.outDir = args.flags.o;
|
|
1050
|
+
if (args.flags.filename) opts.filename = args.flags.filename;
|
|
1051
|
+
|
|
1052
|
+
verboseLog(args, `gdownload gid=${gid} fsIds=[${fsIds.join(",")}] outDir=${opts.outDir || process.cwd()} filename=${opts.filename || "auto"} fromUk=${opts.fromUk || "auto"} msgId=${opts.msgId || "auto"}`);
|
|
1053
|
+
|
|
1054
|
+
// 单文件 → 直接下载;多 fs_id(同一分享消息)→ zip 打包下载(逆向 type=batch)
|
|
1055
|
+
const result = fsIds.length === 1
|
|
1056
|
+
? await group.downloadFile(gid, fsIds[0], opts)
|
|
1057
|
+
: await group.downloadFiles(gid, fsIds, opts);
|
|
1058
|
+
|
|
1059
|
+
output(
|
|
1060
|
+
{ path: result.path, name: result.name, size: result.size, fsId: fsIds.length === 1 ? fsIds[0] : undefined, count: result.count, fromUk: result.fromUk, msgId: result.msgId },
|
|
1061
|
+
json,
|
|
1062
|
+
() => console.log(`✅ 已下载 ${result.size} 字节 → ${result.path}${result.count ? `(${result.count} 个文件 zip 打包)` : ""}`)
|
|
1063
|
+
);
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
cmds.cache = async (args, json) => {
|
|
1067
|
+
const action = args._[0] || "info";
|
|
1068
|
+
const { clearGroupCache } = group;
|
|
1069
|
+
if (action === "clear") {
|
|
1070
|
+
clearGroupCache();
|
|
1071
|
+
output({ cleared: true }, json, () => console.log("✅ 群聊会话缓存已清除"));
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
// info: 显示缓存目录位置
|
|
1075
|
+
const path = require("path");
|
|
1076
|
+
const os = require("os");
|
|
1077
|
+
const dir = process.env.BDP_CACHE_DIR || path.join(os.homedir(), ".bdp", "cache");
|
|
1078
|
+
output({ cacheDir: dir, ttl: { dir: "30min", shares: "5min" } }, json, () => {
|
|
1079
|
+
console.log(`Cache dir: ${dir}`);
|
|
1080
|
+
console.log(`Dir TTL: 30 min (gsearch/gtree 目录列表)`);
|
|
1081
|
+
console.log(`Shares TTL: 5 min (gshares 分享列表)`);
|
|
1082
|
+
console.log(`Clear with: bdp cache clear`);
|
|
1083
|
+
});
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
cmds.error = async (args, json) => {
|
|
1087
|
+
const code = args._[0] || "-3";
|
|
1088
|
+
const ERRORS = {
|
|
1089
|
+
"-10": "群文件转存接口已被百度下线(2026-08 实测:/mbox/msg/transfer 对非空目录一律 errno=-10,\n" +
|
|
1090
|
+
"补 logId/channel 参数仍拒绝,新版 UI 已移除'保存'按钮)。\n" +
|
|
1091
|
+
"处理:群文件用 bdp gdownload 直接下载(单文件 ≤~20MB;多文件 zip 打包 ≤~100MB),\n" +
|
|
1092
|
+
" 更大文件用百度官方客户端。",
|
|
1093
|
+
"-6": "接口鉴权失败。通常为 access_token/cookie 失效,请重新执行 bdp login。",
|
|
1094
|
+
"2": "接口不支持或参数缺失(errno=2)。\n" +
|
|
1095
|
+
"实测普通文件接口(/api/download、/api/filemetas)对群文件参数返回 errno=2。\n" +
|
|
1096
|
+
"处理:群文件请走 bdp gdownload / bdp gsearch 的群专用接口。",
|
|
1097
|
+
"-3": "群分享 API 拒绝请求。已观察到大目录、错误的 msgId/fromUk、过期分享、请求过于频繁(限流)都可能触发。\n" +
|
|
1098
|
+
"处理:减小 --page-size(自动重试 50→20→10)或 --concurrency;\n" +
|
|
1099
|
+
" 限流表现为 errno=0 但只返回目录自身(self-echo),工具会自动退避重试(1s/2s);\n" +
|
|
1100
|
+
" 大目录已支持自动全页遍历(每页上限 100,--max-pages 控制页数);\n" +
|
|
1101
|
+
" 若回退到父目录(fallback level=parent),Agent 应从父目录换路径继续遍历。",
|
|
1102
|
+
"2131": "群分享 msg_id 不属于该群(shareinfo 以 msg_id 为查找键,from_uk 不参与校验)。\n" +
|
|
1103
|
+
"常见原因:gid 与 --from-uk/--msg-id/--parent-fs-id 来自不同群/分享库(参数错配)。\n" +
|
|
1104
|
+
"处理:用 bdp gshares <gid> 查看本群分享(或 bdp gsearch <gid> <关键词>),\n" +
|
|
1105
|
+
" 取其中一行的 fsId/fromUk/msgId 重试,保证 gid/fsId/fromUk/msgId 同源;\n" +
|
|
1106
|
+
" fsId 为子目录时保持其父分享行的 fromUk/msgId 不变;\n" +
|
|
1107
|
+
" 或去掉 --from-uk/--msg-id 让 CLI 自动解析(仅顶层分享)。\n" +
|
|
1108
|
+
" 工具已支持:显式参数错配时自动按 fsId 纠正(结果含 autoResolved:true)。",
|
|
1109
|
+
"31062": "文件名非法(file name is invalid)。\n" +
|
|
1110
|
+
"处理:路径含非法字符或格式不符,重命名网盘文件后再试。",
|
|
1111
|
+
"31090": "zip 打包总大小超出上限(package is too large,实测 452MB 即报 31090,10MB 正常)。\n" +
|
|
1112
|
+
"处理:拆包下载,或大文件用百度官方客户端。",
|
|
1113
|
+
"31326": "dlink 被反盗链拦截(HTTP 403,hitcode 104)。\n" +
|
|
1114
|
+
"实测直链只接受 ≤~5MB 的有界 Range 请求,无界请求返回 403。\n" +
|
|
1115
|
+
"处理:CLI 已按 4MB 分块自动规避;如仍报错,重跑 bdp get --resume(换新 dlink)。",
|
|
1116
|
+
};
|
|
1117
|
+
const msg = ERRORS[code] || `未知错误码 ${code},请提交 Issue: https://github.com/NkAntony777/baiduwangpan-cli/issues`;
|
|
1118
|
+
if (args.flags.jsonFile || json) {
|
|
1119
|
+
emitJson({ code, description: msg }, args);
|
|
1120
|
+
} else {
|
|
1121
|
+
console.log(msg);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
// ── Main ───────────────────────────────────────────────
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
// ── 命令注册表(bdp help / 未知命令提示的唯一数据源)──────────────────
|
|
1129
|
+
// 每项: { group: "pan"|"group"|"config", usage, desc, options?, examples?, note? }
|
|
1130
|
+
// 新增命令时必须在此登记,bdp help 才会展示(避免手写 HELP 漂移)
|
|
1131
|
+
const COMMANDS = {
|
|
1132
|
+
ls: {
|
|
1133
|
+
group: "pan",
|
|
1134
|
+
usage: "bdp ls [path]",
|
|
1135
|
+
desc: "列出目录",
|
|
1136
|
+
flags: [],
|
|
1137
|
+
examples: ["bdp ls /", "bdp ls /文档 --json"],
|
|
1138
|
+
},
|
|
1139
|
+
search: {
|
|
1140
|
+
group: "pan",
|
|
1141
|
+
usage: "bdp search <keyword> [-p /path] [--limit N] [--page N]",
|
|
1142
|
+
desc: "按文件名递归搜索,支持模式过滤(--json 返回结构化 entries[])",
|
|
1143
|
+
flags: ["k", "p", "regex", "glob", "anyWord", "exact", "limit", "page", "maxResults", "fields"],
|
|
1144
|
+
options: [
|
|
1145
|
+
"-p <dir> 搜索目录 (默认 /)",
|
|
1146
|
+
"--regex 正则模式",
|
|
1147
|
+
"--glob shell 通配符 (如 玄空*.pdf)",
|
|
1148
|
+
"--any-word 空格分词命中任意一个 (OR)",
|
|
1149
|
+
"--exact 整名精确匹配",
|
|
1150
|
+
"--limit <N> 单页返回条数 (默认50)",
|
|
1151
|
+
"--page <N> 页码 (默认1)",
|
|
1152
|
+
"--max-results <N> 硬上限,截断总结果数",
|
|
1153
|
+
"--fields <list> 只返回指定字段 (逗号分隔, 如 path,size,mtime)",
|
|
1154
|
+
],
|
|
1155
|
+
examples: [
|
|
1156
|
+
'bdp search "报告"',
|
|
1157
|
+
'bdp search "玄空*.pdf" --glob',
|
|
1158
|
+
'bdp search "玄空.+pdf$" --regex --json',
|
|
1159
|
+
'bdp search "报告" --limit 20 --fields path,size,mtime --json',
|
|
1160
|
+
],
|
|
1161
|
+
},
|
|
1162
|
+
cat: {
|
|
1163
|
+
group: "pan",
|
|
1164
|
+
usage: "bdp cat <path>",
|
|
1165
|
+
desc: "读取文件内容(免下载,限 1MB)",
|
|
1166
|
+
flags: ["number"],
|
|
1167
|
+
options: ["-N 显示行号"],
|
|
1168
|
+
examples: ["bdp cat /文档/data.json"],
|
|
1169
|
+
},
|
|
1170
|
+
head: {
|
|
1171
|
+
group: "pan",
|
|
1172
|
+
usage: "bdp head [-n N] <path>",
|
|
1173
|
+
desc: "读取前 N 行(默认 20)",
|
|
1174
|
+
flags: ["n"],
|
|
1175
|
+
options: ["-n <N> 行数"],
|
|
1176
|
+
examples: ["bdp head -n 50 /文档/日志.txt"],
|
|
1177
|
+
},
|
|
1178
|
+
tail: {
|
|
1179
|
+
group: "pan",
|
|
1180
|
+
usage: "bdp tail [-n N] <path>",
|
|
1181
|
+
desc: "读取后 N 行(默认 20)",
|
|
1182
|
+
flags: ["n"],
|
|
1183
|
+
options: ["-n <N> 行数"],
|
|
1184
|
+
examples: ["bdp tail -n 30 /文档/log.txt"],
|
|
1185
|
+
},
|
|
1186
|
+
grep: {
|
|
1187
|
+
group: "pan",
|
|
1188
|
+
usage: "bdp grep <pattern> <path>",
|
|
1189
|
+
desc: "在文件内容中搜索",
|
|
1190
|
+
flags: ["i", "number"],
|
|
1191
|
+
options: ["-i 忽略大小写", "-N 显示行号"],
|
|
1192
|
+
examples: ["bdp grep 关键词 /文档/file.txt"],
|
|
1193
|
+
},
|
|
1194
|
+
peek: {
|
|
1195
|
+
group: "pan",
|
|
1196
|
+
usage: "bdp peek <path>",
|
|
1197
|
+
desc: "预览文件信息 + 前 10 行(含 md5/ctime)",
|
|
1198
|
+
flags: [],
|
|
1199
|
+
examples: ["bdp peek /文档/report.pdf --json"],
|
|
1200
|
+
},
|
|
1201
|
+
get: {
|
|
1202
|
+
group: "pan",
|
|
1203
|
+
usage: "bdp get <path> [-o dir]",
|
|
1204
|
+
desc: "下载文件(直链分块、size 校验、断点续传)",
|
|
1205
|
+
flags: ["o", "resume", "force", "progress", "noVerifySize", "dryRun", "concurrency", "fromUk", "msgId", "filename"],
|
|
1206
|
+
note: "路径为 <gid>:<fs_id> 时自动走群文件直下(免转存)",
|
|
1207
|
+
options: [
|
|
1208
|
+
"-o <dir> 输出目录(默认当前目录)",
|
|
1209
|
+
"--resume 断点续传(存在部分文件时自动)",
|
|
1210
|
+
"--force 覆盖重下",
|
|
1211
|
+
"--progress 实时进度 + 速度",
|
|
1212
|
+
"--no-verify-size 关闭下载后 size 校验",
|
|
1213
|
+
"--dry-run 模拟,不落盘",
|
|
1214
|
+
"--concurrency <N> 并发分块数 (默认3, 仅大文件; 1=串行)",
|
|
1215
|
+
],
|
|
1216
|
+
examples: [
|
|
1217
|
+
"bdp get /文档/报告.pdf -o ./pdfs --progress",
|
|
1218
|
+
"bdp get /文档/大文件.zip --resume --json",
|
|
1219
|
+
"bdp get 539478953581833690:954615608563687 -o ./g",
|
|
1220
|
+
],
|
|
1221
|
+
},
|
|
1222
|
+
put: {
|
|
1223
|
+
group: "pan",
|
|
1224
|
+
usage: "bdp put <local> <remote>",
|
|
1225
|
+
desc: "上传文件",
|
|
1226
|
+
flags: ["dryRun"],
|
|
1227
|
+
options: ["--dry-run 模拟"],
|
|
1228
|
+
examples: ["bdp put ./report.pdf /文档/"],
|
|
1229
|
+
},
|
|
1230
|
+
mkdir: {
|
|
1231
|
+
group: "pan",
|
|
1232
|
+
usage: "bdp mkdir <path>",
|
|
1233
|
+
desc: "创建目录",
|
|
1234
|
+
flags: [],
|
|
1235
|
+
examples: ["bdp mkdir /新项目"],
|
|
1236
|
+
},
|
|
1237
|
+
rm: {
|
|
1238
|
+
group: "pan",
|
|
1239
|
+
usage: "bdp rm <path> [path2 ...] [--yes]",
|
|
1240
|
+
desc: "删除文件/目录(支持批量)",
|
|
1241
|
+
flags: ["dryRun", "yes"],
|
|
1242
|
+
note: "⚠️ 高危操作。非交互环境(Agent/管道)必须显式 --yes;交互终端会弹 y/N 确认",
|
|
1243
|
+
options: ["--dry-run 模拟", "--yes 跳过确认直接删除(非交互环境必需)"],
|
|
1244
|
+
examples: ["bdp rm /临时文件.txt --yes", "bdp rm /a.txt /b.txt --dry-run"],
|
|
1245
|
+
},
|
|
1246
|
+
mv: {
|
|
1247
|
+
group: "pan",
|
|
1248
|
+
usage: "bdp mv <src> <dst>",
|
|
1249
|
+
desc: "移动/重命名文件/目录",
|
|
1250
|
+
flags: [],
|
|
1251
|
+
examples: ["bdp mv /文档/a.pdf /文档/b.pdf", "bdp mv /文档/a.pdf /备份/"],
|
|
1252
|
+
},
|
|
1253
|
+
cp: {
|
|
1254
|
+
group: "pan",
|
|
1255
|
+
usage: "bdp cp <src> <dst>",
|
|
1256
|
+
desc: "拷贝文件/目录",
|
|
1257
|
+
flags: [],
|
|
1258
|
+
examples: ["bdp cp /文档/a.pdf /备份/"],
|
|
1259
|
+
},
|
|
1260
|
+
quota: {
|
|
1261
|
+
group: "pan",
|
|
1262
|
+
usage: "bdp quota",
|
|
1263
|
+
desc: "网盘配额(总/已用/剩余)",
|
|
1264
|
+
flags: [],
|
|
1265
|
+
examples: ["bdp quota --json"],
|
|
1266
|
+
},
|
|
1267
|
+
du: {
|
|
1268
|
+
group: "pan",
|
|
1269
|
+
usage: "bdp du [path]",
|
|
1270
|
+
desc: "目录容量分析(递归统计,找空间大户)",
|
|
1271
|
+
flags: ["depth", "top", "concurrency", "progress"],
|
|
1272
|
+
note: "深度上限 8;超出时 size 不完整(返回值 partial=true),实际占用以 bdp quota 为准",
|
|
1273
|
+
options: [
|
|
1274
|
+
"--depth <N> 递归深度 (默认3, 上限8)",
|
|
1275
|
+
"--top <N> 显示最大的 N 个子目录 (默认15)",
|
|
1276
|
+
"--concurrency <N> 并发扫描数 (默认8)",
|
|
1277
|
+
"--progress stderr 实时输出扫描进度",
|
|
1278
|
+
],
|
|
1279
|
+
examples: ["bdp du /", "bdp du /玄学 --depth 4 --top 20", "bdp du / --json"],
|
|
1280
|
+
},
|
|
1281
|
+
offline: {
|
|
1282
|
+
group: "pan",
|
|
1283
|
+
usage: "bdp offline add <http_url> [--path /目录] | bdp offline list",
|
|
1284
|
+
desc: "离线下载(百度服务器代下)",
|
|
1285
|
+
flags: ["p"],
|
|
1286
|
+
options: ["--path <dir> 保存目录"],
|
|
1287
|
+
examples: ["bdp offline add http://example.com/big.iso --path /下载", "bdp offline list"],
|
|
1288
|
+
},
|
|
1289
|
+
recycle: {
|
|
1290
|
+
group: "pan",
|
|
1291
|
+
usage: "bdp recycle list | restore <fs_id>... | clean",
|
|
1292
|
+
desc: "回收站管理(列出/还原/清空)",
|
|
1293
|
+
flags: ["page"],
|
|
1294
|
+
note: "clean 清空不可恢复,使用前务必确认;BaiduPCS-Go 不二次确认",
|
|
1295
|
+
options: ["--page <N> 回收站列表页码 (默认1)"],
|
|
1296
|
+
examples: ["bdp recycle list", "bdp recycle restore 378804494923604", "bdp recycle clean"],
|
|
1297
|
+
},
|
|
1298
|
+
share: {
|
|
1299
|
+
group: "pan",
|
|
1300
|
+
usage: "bdp share set <path> [--pwd P] | bdp share cancel <id>... | bdp share list",
|
|
1301
|
+
desc: "分享管理(创建/取消/列出)",
|
|
1302
|
+
flags: ["pwd", "combined", "page"],
|
|
1303
|
+
note: "share list 走网页 API(BaiduPCS-Go v4.0.1 引擎的 share list 有 panic bug,网页 API 正常)",
|
|
1304
|
+
options: ["--pwd <P> 提取码", "--combined 输出带提取码的链接"],
|
|
1305
|
+
examples: ["bdp share set /文档/报告.pdf --pwd 1234 --combined", "bdp share cancel 23220383577"],
|
|
1306
|
+
},
|
|
1307
|
+
groups: {
|
|
1308
|
+
group: "group",
|
|
1309
|
+
usage: "bdp groups",
|
|
1310
|
+
desc: "列出所有群组",
|
|
1311
|
+
flags: [],
|
|
1312
|
+
examples: ["bdp groups --json"],
|
|
1313
|
+
},
|
|
1314
|
+
gshares: {
|
|
1315
|
+
group: "group",
|
|
1316
|
+
usage: "bdp gshares <gid>",
|
|
1317
|
+
desc: "列出群内分享库",
|
|
1318
|
+
flags: ["noCache"],
|
|
1319
|
+
options: ["--no-cache 禁用缓存"],
|
|
1320
|
+
examples: ["bdp gshares 539478953581833690"],
|
|
1321
|
+
},
|
|
1322
|
+
gls: {
|
|
1323
|
+
group: "group",
|
|
1324
|
+
usage: "bdp gls <gid> <fs_id>",
|
|
1325
|
+
desc: "浏览分享库内容(分页)",
|
|
1326
|
+
flags: ["page", "pageSize", "fromUk", "msgId", "parentFsId", "noCache"],
|
|
1327
|
+
options: [
|
|
1328
|
+
"--page <N> 页码 (默认1)",
|
|
1329
|
+
"--page-size <N> 每页条数 (默认50, 最大100)",
|
|
1330
|
+
"--from-uk <X> 分享者 uk",
|
|
1331
|
+
"--msg-id <Y> 消息 id",
|
|
1332
|
+
"--parent-fs-id <Z> 父目录 fsId",
|
|
1333
|
+
"--no-cache 禁用缓存",
|
|
1334
|
+
],
|
|
1335
|
+
examples: ["bdp gls 539478953581833690 742474845517885 --json"],
|
|
1336
|
+
},
|
|
1337
|
+
gtree: {
|
|
1338
|
+
group: "group",
|
|
1339
|
+
usage: "bdp gtree <gid>",
|
|
1340
|
+
desc: "构建群目录树(BFS)",
|
|
1341
|
+
flags: ["depth", "concurrency", "maxNodes", "maxPages", "maxRequests", "noCache"],
|
|
1342
|
+
options: ["--depth <N> 深度 (默认2)", "--concurrency <N> 并发 (默认4)", "--max-nodes <N> 节点上限 (默认2000)"],
|
|
1343
|
+
examples: ["bdp gtree 539478953581833690 --depth 3"],
|
|
1344
|
+
},
|
|
1345
|
+
gsearch: {
|
|
1346
|
+
group: "group",
|
|
1347
|
+
usage: "bdp gsearch <gid> <keyword>",
|
|
1348
|
+
desc: "搜索群文件名(全量遍历,缓存命中秒回)",
|
|
1349
|
+
flags: ["page", "limit", "concurrency", "unique", "noUnique", "all", "timeout", "timeoutMs", "savePartial", "anyWord", "exact", "depth", "maxPages", "maxRequests", "noCache"],
|
|
1350
|
+
note: "默认多关键词 AND(空格分词每词都需出现);--any-word 切 OR",
|
|
1351
|
+
options: [
|
|
1352
|
+
"--page <N> 页码 (默认1)",
|
|
1353
|
+
"--limit <N> 每页条数 (默认50)",
|
|
1354
|
+
"--depth <N> 递归深度 (默认1)",
|
|
1355
|
+
"--concurrency <N> 并发扫描数 (默认4)",
|
|
1356
|
+
"--max-pages <N> 单目录最大页数 (默认50)",
|
|
1357
|
+
"--max-requests <N> 请求预算 (默认400)",
|
|
1358
|
+
"--all|--all-results 忽略分页取全部",
|
|
1359
|
+
"--timeout <N> 超时返回部分结果",
|
|
1360
|
+
"--save-partial 部分结果落盘",
|
|
1361
|
+
"--any-word OR 模式",
|
|
1362
|
+
"--exact 整名匹配",
|
|
1363
|
+
"--no-unique 保留不同 msgId 重复项",
|
|
1364
|
+
"--no-cache 禁用缓存",
|
|
1365
|
+
],
|
|
1366
|
+
examples: [
|
|
1367
|
+
'bdp gsearch 539478953581833690 "倪海厦" --limit 20 --json',
|
|
1368
|
+
'bdp gsearch 539478953581833690 "玄空 飞星" --json',
|
|
1369
|
+
],
|
|
1370
|
+
},
|
|
1371
|
+
gdownload: {
|
|
1372
|
+
group: "group",
|
|
1373
|
+
usage: "bdp gdownload <gid> <fs_id>...",
|
|
1374
|
+
desc: "直接下载群文件(免转存)",
|
|
1375
|
+
flags: ["o", "filename", "fromUk", "msgId", "noCache"],
|
|
1376
|
+
note: "单 fs_id 直下;多个 fs_id(同一分享消息)自动 zip 打包",
|
|
1377
|
+
options: ["-o <dir> 输出目录", "--filename <NAME> zip 包名", "--from-uk <X>", "--msg-id <Y>", "--no-cache"],
|
|
1378
|
+
examples: [
|
|
1379
|
+
"bdp gdownload 539478953581833690 954615608563687 -o ./downloads --from-uk 1101635869133 --msg-id 713945176566573051",
|
|
1380
|
+
"bdp gdownload 539478953581833690 527948256537856 986718405708023 -o ./zips",
|
|
1381
|
+
],
|
|
1382
|
+
},
|
|
1383
|
+
cache: {
|
|
1384
|
+
group: "group",
|
|
1385
|
+
usage: "bdp cache [clear]",
|
|
1386
|
+
desc: "查看/清空群会话缓存",
|
|
1387
|
+
flags: [],
|
|
1388
|
+
examples: ["bdp cache", "bdp cache clear"],
|
|
1389
|
+
},
|
|
1390
|
+
error: {
|
|
1391
|
+
group: "group",
|
|
1392
|
+
usage: "bdp error <code>",
|
|
1393
|
+
desc: "解释错误码",
|
|
1394
|
+
flags: [],
|
|
1395
|
+
examples: ["bdp error -3", "bdp error 31326"],
|
|
1396
|
+
},
|
|
1397
|
+
login: {
|
|
1398
|
+
group: "config",
|
|
1399
|
+
usage: "bdp login [--bduss X --stoken Y]",
|
|
1400
|
+
desc: "登录(浏览器扫码或手动凭证)",
|
|
1401
|
+
flags: ["bduss", "stoken"],
|
|
1402
|
+
options: ["--bduss <X> 手动凭证", "--stoken <Y> 手动凭证"],
|
|
1403
|
+
examples: ["bdp login", "bdp login --bduss abc123 --stoken def456"],
|
|
1404
|
+
},
|
|
1405
|
+
whoami: {
|
|
1406
|
+
group: "config",
|
|
1407
|
+
usage: "bdp whoami",
|
|
1408
|
+
desc: "查看登录状态与当前 profile",
|
|
1409
|
+
flags: [],
|
|
1410
|
+
examples: ["bdp whoami --json"],
|
|
1411
|
+
},
|
|
1412
|
+
config: {
|
|
1413
|
+
group: "config",
|
|
1414
|
+
usage: "bdp config [--show-secrets]",
|
|
1415
|
+
desc: "查看配置(凭证默认脱敏)",
|
|
1416
|
+
flags: ["showSecrets"],
|
|
1417
|
+
note: "BDUSS/STOKEN 默认脱敏显示;--show-secrets 才输出原值",
|
|
1418
|
+
examples: ["bdp config", "bdp config --json"],
|
|
1419
|
+
},
|
|
1420
|
+
profile: {
|
|
1421
|
+
group: "config",
|
|
1422
|
+
usage: "bdp profile list | use <name> | unset",
|
|
1423
|
+
desc: "多账号 profile 管理",
|
|
1424
|
+
flags: [],
|
|
1425
|
+
note: "也可用全局参数 --profile <name> 切换(命令前/后均可)",
|
|
1426
|
+
examples: ["bdp --profile svip login", "bdp profile list"],
|
|
1427
|
+
},
|
|
1428
|
+
serve: {
|
|
1429
|
+
group: "config",
|
|
1430
|
+
usage: "bdp serve --mcp",
|
|
1431
|
+
desc: "启动 MCP 服务器(stdio 传输,工具可被 Agent 客户端自动发现)",
|
|
1432
|
+
flags: ["mcp"],
|
|
1433
|
+
options: [
|
|
1434
|
+
"--mcp 以 MCP 服务器模式启动",
|
|
1435
|
+
],
|
|
1436
|
+
examples: ["bdp serve --mcp"],
|
|
1437
|
+
},
|
|
1438
|
+
help: {
|
|
1439
|
+
group: "config",
|
|
1440
|
+
usage: "bdp help [command]",
|
|
1441
|
+
desc: "查看帮助([command] 查看单命令详情;--json 结构化输出)",
|
|
1442
|
+
flags: [],
|
|
1443
|
+
examples: ["bdp help", "bdp help get", "bdp help --json"],
|
|
1444
|
+
},
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
const GROUP_LABELS = {
|
|
1448
|
+
pan: "PAN FILE OPERATIONS (全盘)",
|
|
1449
|
+
group: "GROUP CHAT OPERATIONS (群聊)",
|
|
1450
|
+
config: "CONFIGURATION",
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
// 全局选项(跨命令通用);命令专属选项见各命令注册项
|
|
1454
|
+
const GLOBAL_OPTIONS = [
|
|
1455
|
+
"--profile <name> 切换账号 profile(命令前/后均可)",
|
|
1456
|
+
"--json JSON 结构化输出 (Agent 友好)",
|
|
1457
|
+
"--json-file <path> JSON 写入文件 (UTF-8, 绕开控制台编码)",
|
|
1458
|
+
"--legacy-json 输出裸数组 (gls/gsearch legacy)",
|
|
1459
|
+
"--verbose 进度/日志只写 stderr",
|
|
1460
|
+
];
|
|
1461
|
+
|
|
1462
|
+
// CJK 等宽字符按 2 列计(与 pcs-extra 的显示宽度规则一致)
|
|
1463
|
+
function padWidth(str, width) {
|
|
1464
|
+
let w = 0;
|
|
1465
|
+
for (const ch of String(str)) w += /[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6]/.test(ch) ? 2 : 1;
|
|
1466
|
+
return String(str) + " ".repeat(Math.max(0, width - w));
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function buildHelp() {
|
|
1470
|
+
const lines = ["bdp — 百度网盘 CLI", "", "USAGE", " bdp <command> [options] [arguments]", " bdp help [command] — 单命令详情 (--json 结构化)", ""];
|
|
1471
|
+
for (const [gkey, label] of Object.entries(GROUP_LABELS)) {
|
|
1472
|
+
lines.push(label);
|
|
1473
|
+
for (const [name, c] of Object.entries(COMMANDS)) {
|
|
1474
|
+
if (c.group !== gkey) continue;
|
|
1475
|
+
// 总览只显示主用法(复合用法取 " | " 前段,完整版见 bdp help <command>)
|
|
1476
|
+
const usageLine = c.usage.includes(" | ") ? c.usage.split(" | ")[0] : c.usage;
|
|
1477
|
+
lines.push(` ${padWidth(usageLine, 42)}${c.desc}`);
|
|
1478
|
+
}
|
|
1479
|
+
lines.push("");
|
|
1480
|
+
}
|
|
1481
|
+
lines.push("OPTIONS (全局)");
|
|
1482
|
+
for (const o of GLOBAL_OPTIONS) lines.push(` ${o}`);
|
|
1483
|
+
lines.push("");
|
|
1484
|
+
lines.push("EXAMPLES(每个命令的典型用法,bdp help <command> 查看更多)");
|
|
1485
|
+
for (const [name, c] of Object.entries(COMMANDS)) {
|
|
1486
|
+
if (c.examples && c.examples[0]) lines.push(` ${c.examples[0]}`);
|
|
1487
|
+
}
|
|
1488
|
+
return lines.join("\n") + "\n";
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function buildCommandHelp(name) {
|
|
1492
|
+
const c = COMMANDS[name];
|
|
1493
|
+
if (!c) return null;
|
|
1494
|
+
const lines = [`bdp ${name} — ${c.desc}`, ""];
|
|
1495
|
+
if (c.note) {
|
|
1496
|
+
lines.push("NOTE");
|
|
1497
|
+
lines.push(` ${c.note}`);
|
|
1498
|
+
lines.push("");
|
|
1499
|
+
}
|
|
1500
|
+
lines.push("USAGE");
|
|
1501
|
+
lines.push(` ${c.usage}`);
|
|
1502
|
+
lines.push("");
|
|
1503
|
+
if (c.options && c.options.length) {
|
|
1504
|
+
lines.push("OPTIONS");
|
|
1505
|
+
for (const o of c.options) lines.push(` ${o}`);
|
|
1506
|
+
lines.push("");
|
|
1507
|
+
}
|
|
1508
|
+
if (c.examples && c.examples.length) {
|
|
1509
|
+
lines.push("EXAMPLES");
|
|
1510
|
+
for (const ex of c.examples) lines.push(` ${ex}`);
|
|
1511
|
+
}
|
|
1512
|
+
return lines.join("\n") + "\n";
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
|
|
1516
|
+
// 全局通用选项(所有命令都接受);命令专属选项见 COMMANDS[name].flags
|
|
1517
|
+
const GLOBAL_FLAGS = ['json', 'jsonFile', 'legacyJson', 'verbose', 'profile'];
|
|
1518
|
+
|
|
1519
|
+
// 防呆:未知选项直接报错(agent 拼错参数立刻发现,而不是静默忽略)
|
|
1520
|
+
function validateFlags(command, flags, cmdMeta) {
|
|
1521
|
+
if (!cmdMeta) return [];
|
|
1522
|
+
const allowed = new Set(GLOBAL_FLAGS.concat(cmdMeta.flags || []));
|
|
1523
|
+
return Object.keys(flags).filter((f) => !allowed.has(f));
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const HELP = buildHelp();
|
|
1527
|
+
|
|
1528
|
+
cmds.help = async (args, json) => {
|
|
1529
|
+
const target = args._[0];
|
|
1530
|
+
if (target) {
|
|
1531
|
+
if (!COMMANDS[target]) {
|
|
1532
|
+
if (json || args.flags.jsonFile) {
|
|
1533
|
+
emitJson({ error: `未知命令: ${target}`, available: Object.keys(COMMANDS) }, args);
|
|
1534
|
+
} else {
|
|
1535
|
+
console.error(`[ERROR] 未知命令: ${target}。用 bdp help 查看全部命令。`);
|
|
1536
|
+
}
|
|
1537
|
+
process.exit(1);
|
|
1538
|
+
}
|
|
1539
|
+
const c = COMMANDS[target];
|
|
1540
|
+
if (json || args.flags.jsonFile) {
|
|
1541
|
+
emitJson({ name: target, ...c }, args);
|
|
1542
|
+
} else {
|
|
1543
|
+
console.log(buildCommandHelp(target));
|
|
1544
|
+
}
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
if (json || args.flags.jsonFile) {
|
|
1548
|
+
emitJson(
|
|
1549
|
+
{
|
|
1550
|
+
tool: "bdp",
|
|
1551
|
+
version: require("../package.json").version,
|
|
1552
|
+
usage: "bdp <command> [options] [arguments]",
|
|
1553
|
+
globalOptions: GLOBAL_OPTIONS,
|
|
1554
|
+
commands: Object.entries(COMMANDS).map(([name, c]) => ({ name, ...c })),
|
|
1555
|
+
},
|
|
1556
|
+
args
|
|
1557
|
+
);
|
|
1558
|
+
} else {
|
|
1559
|
+
console.log(HELP);
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
// ── MCP 服务器 ─────────────────────────────────────────────
|
|
1565
|
+
|
|
1566
|
+
cmds.serve = async (args, json) => {
|
|
1567
|
+
const { startServer, SERVER_NAME, SERVER_VERSION } = require("../lib/mcp");
|
|
1568
|
+
if (json || args.flags.jsonFile) {
|
|
1569
|
+
emitJson({ mode: "mcp", server: SERVER_NAME, version: SERVER_VERSION, transport: "stdio" }, args);
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (!args.flags.mcp) {
|
|
1573
|
+
console.error("Usage: bdp serve --mcp (启动 MCP 服务器,stdio 传输)");
|
|
1574
|
+
process.exit(1);
|
|
1575
|
+
}
|
|
1576
|
+
process.stderr.write("[bdp serve] MCP 服务器已启动(stdio, JSON-RPC 2.0)\n");
|
|
1577
|
+
startServer();
|
|
1578
|
+
// stdio 服务器常驻,直到 stdin 关闭
|
|
1579
|
+
await new Promise(() => {});
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
// ── Main ───────────────────────────────────────────────
|
|
1583
|
+
|
|
1584
|
+
async function main() {
|
|
1585
|
+
// Windows 控制台编码兜底:TTY 且非 UTF-8 代码页时切换 chcp 65001(幂等、静默失败)
|
|
1586
|
+
ensureUtf8Console();
|
|
1587
|
+
|
|
1588
|
+
const argv = process.argv.slice(2);
|
|
1589
|
+
// 全局 --profile:支持 `bdp --profile <name> <cmd>`(命令前)或 `bdp <cmd> --profile <name>`(命令后)
|
|
1590
|
+
let rest = argv;
|
|
1591
|
+
let profileName;
|
|
1592
|
+
if (argv[0] === "--profile") {
|
|
1593
|
+
profileName = argv[1];
|
|
1594
|
+
rest = argv.slice(2);
|
|
1595
|
+
} else if (argv[0] && argv[0].startsWith("--profile=")) {
|
|
1596
|
+
profileName = argv[0].slice("--profile=".length);
|
|
1597
|
+
rest = argv.slice(1);
|
|
1598
|
+
}
|
|
1599
|
+
const command = rest[0];
|
|
1600
|
+
const args = parseArgs(rest.slice(1));
|
|
1601
|
+
const json = args.flags.json || false;
|
|
1602
|
+
|
|
1603
|
+
// 切换账号 profile(须在命令 handler 之前,get()/isLoggedIn() 自动生效)
|
|
1604
|
+
const profile = profileName !== undefined ? profileName : args.flags.profile;
|
|
1605
|
+
if (profile !== undefined) {
|
|
1606
|
+
try {
|
|
1607
|
+
config.useProfile(profile);
|
|
1608
|
+
} catch (e) {
|
|
1609
|
+
console.error("[ERROR] " + e.message);
|
|
1610
|
+
process.exit(1);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const handler = cmds[command];
|
|
1615
|
+
if (!handler) {
|
|
1616
|
+
if (command) console.error("[ERROR] 未知命令: " + command + "(用 bdp help 查看全部命令,bdp help <命令> 查看用法)");
|
|
1617
|
+
console.log(HELP);
|
|
1618
|
+
process.exit(command ? 1 : 0);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// 防呆:未知选项立即报错(避免 agent 拼错参数被静默忽略)
|
|
1622
|
+
const unknownFlags = validateFlags(command, args.flags, COMMANDS[command]);
|
|
1623
|
+
if (unknownFlags.length > 0) {
|
|
1624
|
+
console.error("[ERROR] 命令 " + command + " 不支持选项: --" + unknownFlags.join(", --") + "(bdp help " + command + " 查看合法选项)");
|
|
1625
|
+
process.exit(1);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
let exitCode = 0;
|
|
1629
|
+
try {
|
|
1630
|
+
// handler 可以返回数字作为退出码(用于"部分失败"场景,如 rm 批量删除)
|
|
1631
|
+
const rc = await handler(args, json);
|
|
1632
|
+
if (typeof rc === "number" && rc !== 0) exitCode = rc;
|
|
1633
|
+
} catch (e) {
|
|
1634
|
+
if (json) {
|
|
1635
|
+
console.log(JSON.stringify({ error: e.message, ...(e.errno !== undefined ? { errno: e.errno } : {}) }));
|
|
1636
|
+
} else {
|
|
1637
|
+
console.error("[ERROR] " + e.message);
|
|
1638
|
+
}
|
|
1639
|
+
exitCode = 1;
|
|
1640
|
+
} finally {
|
|
1641
|
+
try {
|
|
1642
|
+
require("../lib/browser-login").disconnectBrowserSession();
|
|
1643
|
+
} catch {}
|
|
1644
|
+
}
|
|
1645
|
+
process.exit(exitCode);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
main();
|