g2log 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.git-user-log-config.json.example +14 -14
- package/CONFIG.md +154 -154
- package/PUBLISH.md +110 -110
- package/README.md +208 -208
- package/git-user-log.js +1940 -1845
- package/install.js +51 -51
- package/package.json +41 -41
package/git-user-log.js
CHANGED
@@ -1,1846 +1,1941 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
|
3
|
-
/**
|
4
|
-
* 获取指定用户和时间范围的Git日志
|
5
|
-
* 使用方法:
|
6
|
-
* - 全局安装: g2log [选项]
|
7
|
-
* - NPX直接运行: npx g2log [选项]
|
8
|
-
*
|
9
|
-
* 常用选项:
|
10
|
-
* [--author="用户名"] [--since="2023-01-01"] [--until="2023-12-31"]
|
11
|
-
* [--repo="alias或路径"] [--format="格式"] [--output="文件路径"] [--stats] [--help]
|
12
|
-
*/
|
13
|
-
|
14
|
-
const { execSync } = require('child_process');
|
15
|
-
const path = require('path');
|
16
|
-
const fs = require('fs');
|
17
|
-
const https = require('https');
|
18
|
-
const os = require('os');
|
19
|
-
const readline = require('readline');
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
const
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
const
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
{
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
//
|
123
|
-
if (userConfig.
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
}
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
return config
|
190
|
-
}
|
191
|
-
|
192
|
-
//
|
193
|
-
function
|
194
|
-
const config = loadConfig();
|
195
|
-
config.
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
config
|
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
|
-
return
|
255
|
-
}
|
256
|
-
|
257
|
-
//
|
258
|
-
function
|
259
|
-
const
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
}
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
}
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
config
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
}
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
//
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
}
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
}
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
}
|
600
|
-
}
|
601
|
-
|
602
|
-
|
603
|
-
}
|
604
|
-
|
605
|
-
|
606
|
-
}
|
607
|
-
|
608
|
-
//
|
609
|
-
function
|
610
|
-
if (!useColor) return
|
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
|
-
const
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
if (spinner) spinner.
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
//
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
//
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
//
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
}
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
}
|
1095
|
-
|
1096
|
-
|
1097
|
-
}
|
1098
|
-
|
1099
|
-
//
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
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
|
-
|
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
|
-
console.log(colorize('\n
|
1361
|
-
console.log(colorize(' (
|
1362
|
-
|
1363
|
-
|
1364
|
-
const
|
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
|
-
const
|
1421
|
-
if (
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
console.
|
1432
|
-
|
1433
|
-
|
1434
|
-
}
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
const
|
1450
|
-
|
1451
|
-
|
1452
|
-
}
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
}
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
}
|
1475
|
-
|
1476
|
-
//
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
if (args
|
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
|
-
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
}
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
}
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
}
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
}
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
}
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
}
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
}
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
//
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
//
|
1805
|
-
|
1806
|
-
|
1807
|
-
//
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1815
|
-
|
1816
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
1844
|
-
|
1845
|
-
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
/**
|
4
|
+
* 获取指定用户和时间范围的Git日志
|
5
|
+
* 使用方法:
|
6
|
+
* - 全局安装: g2log [选项]
|
7
|
+
* - NPX直接运行: npx g2log [选项]
|
8
|
+
*
|
9
|
+
* 常用选项:
|
10
|
+
* [--author="用户名"] [--since="2023-01-01"] [--until="2023-12-31"]
|
11
|
+
* [--repo="alias或路径"] [--format="格式"] [--output="文件路径"] [--stats] [--help]
|
12
|
+
*/
|
13
|
+
|
14
|
+
const { execSync } = require('child_process');
|
15
|
+
const path = require('path');
|
16
|
+
const fs = require('fs');
|
17
|
+
const https = require('https');
|
18
|
+
const os = require('os');
|
19
|
+
const readline = require('readline');
|
20
|
+
// 改用动态导入
|
21
|
+
let ora;
|
22
|
+
import('ora').then(module => {
|
23
|
+
ora = module.default;
|
24
|
+
}).catch(err => {
|
25
|
+
console.error('无法加载ora模块:', err);
|
26
|
+
});
|
27
|
+
|
28
|
+
// 检测是否通过npx运行
|
29
|
+
const isRunningWithNpx = process.env.npm_lifecycle_event === 'npx' ||
|
30
|
+
process.env.npm_execpath?.includes('npx') ||
|
31
|
+
process.env.npm_command === 'exec';
|
32
|
+
|
33
|
+
// 预解析命令行参数,以便在早期决定是否使用颜色
|
34
|
+
const rawArgs = process.argv.slice(2);
|
35
|
+
const forceColor = rawArgs.includes('--color') || rawArgs.includes('--force-color');
|
36
|
+
const disableColor = rawArgs.includes('--no-color');
|
37
|
+
|
38
|
+
// 修改颜色显示逻辑 - 默认就显示颜色,只有pipe时才根据TTY判断,或显式禁用时才不显示
|
39
|
+
const isPiped = !process.stdout.isTTY;
|
40
|
+
const shouldUseColor = (isPiped ? forceColor : true) && !disableColor;
|
41
|
+
|
42
|
+
// ANSI 颜色代码定义
|
43
|
+
const colors = {
|
44
|
+
reset: '\x1b[0m',
|
45
|
+
bright: '\x1b[1m',
|
46
|
+
dim: '\x1b[2m',
|
47
|
+
underscore: '\x1b[4m',
|
48
|
+
blink: '\x1b[5m',
|
49
|
+
reverse: '\x1b[7m',
|
50
|
+
hidden: '\x1b[8m',
|
51
|
+
|
52
|
+
black: '\x1b[30m',
|
53
|
+
red: '\x1b[31m',
|
54
|
+
green: '\x1b[32m',
|
55
|
+
yellow: '\x1b[33m',
|
56
|
+
blue: '\x1b[34m',
|
57
|
+
magenta: '\x1b[35m',
|
58
|
+
cyan: '\x1b[36m',
|
59
|
+
white: '\x1b[37m',
|
60
|
+
|
61
|
+
bgBlack: '\x1b[40m',
|
62
|
+
bgRed: '\x1b[41m',
|
63
|
+
bgGreen: '\x1b[42m',
|
64
|
+
bgYellow: '\x1b[43m',
|
65
|
+
bgBlue: '\x1b[44m',
|
66
|
+
bgMagenta: '\x1b[45m',
|
67
|
+
bgCyan: '\x1b[46m',
|
68
|
+
bgWhite: '\x1b[47m'
|
69
|
+
};
|
70
|
+
|
71
|
+
// 优化的彩色输出函数
|
72
|
+
function colorize(text, color) {
|
73
|
+
// 如果不使用颜色或没有对应颜色代码,直接返回原文本
|
74
|
+
if (!shouldUseColor || !colors[color]) return text;
|
75
|
+
return colors[color] + text + colors.reset;
|
76
|
+
}
|
77
|
+
|
78
|
+
// 配置文件路径
|
79
|
+
const CONFIG_PATH = path.join(os.homedir(), '.git-user-log-config.json');
|
80
|
+
console.log(CONFIG_PATH);
|
81
|
+
// 默认配置
|
82
|
+
const DEFAULT_CONFIG = {
|
83
|
+
api_key: '',
|
84
|
+
default_author: '',
|
85
|
+
default_since: 'today',
|
86
|
+
default_until: 'today',
|
87
|
+
model: 'deepseek-chat', // 默认使用deepseek-chat模型
|
88
|
+
api_base_url: 'https://api.deepseek.com', // 默认使用DeepSeek API
|
89
|
+
api_provider: 'deepseek', // API提供商: deepseek或openai
|
90
|
+
repositories: {},
|
91
|
+
prompt_template: `
|
92
|
+
请根据下面的Git提交记录,用3-5句话简洁地总结一天的工作内容。
|
93
|
+
|
94
|
+
以下是Git提交记录:
|
95
|
+
|
96
|
+
{{GIT_LOGS}}
|
97
|
+
|
98
|
+
要求:
|
99
|
+
1. 按项目和日期组织内容
|
100
|
+
2. 每个项目每天的工作内容用3-5句话概括
|
101
|
+
3. 使用清晰、专业但不晦涩的语言
|
102
|
+
4. 突出重要的功能开发、问题修复和优化改进
|
103
|
+
5. 适合放入工作日报的简洁描述
|
104
|
+
6. 输出格式为:【日期】:
|
105
|
+
【项目名称】- 【工作内容概述】
|
106
|
+
【项目名称】- 【工作内容概述】
|
107
|
+
7. 回复不要出现多余的内容,非必要不要用markdown格式
|
108
|
+
`
|
109
|
+
};
|
110
|
+
|
111
|
+
// 加载配置
|
112
|
+
function loadConfig() {
|
113
|
+
try {
|
114
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
115
|
+
// 读取配置文件
|
116
|
+
const fileContent = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
117
|
+
|
118
|
+
try {
|
119
|
+
// 尝试解析JSON
|
120
|
+
const userConfig = JSON.parse(fileContent);
|
121
|
+
|
122
|
+
// 检查并处理旧版字段
|
123
|
+
if (userConfig.deepseek_api_key && !userConfig.api_key) {
|
124
|
+
userConfig.api_key = userConfig.deepseek_api_key;
|
125
|
+
// 这里不删除旧字段,以保持兼容性,只在fixConfigFile中执行迁移
|
126
|
+
}
|
127
|
+
|
128
|
+
// 检查prompt_template是否完整
|
129
|
+
if (userConfig.prompt_template && typeof userConfig.prompt_template === 'string') {
|
130
|
+
// 检查变量名是否被错误分割
|
131
|
+
if (userConfig.prompt_template.includes('{log_con') &&
|
132
|
+
!userConfig.prompt_template.includes('{log_content}')) {
|
133
|
+
console.log(colorize('警告: 配置文件中的prompt模板格式有误,已修复', 'yellow'));
|
134
|
+
userConfig.prompt_template = userConfig.prompt_template.replace('{log_con\ntent}', '{log_content}');
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
// 移除旧版推理模型相关配置
|
139
|
+
if (userConfig.use_reasoning !== undefined) {
|
140
|
+
delete userConfig.use_reasoning;
|
141
|
+
}
|
142
|
+
|
143
|
+
if (userConfig.show_reasoning !== undefined) {
|
144
|
+
delete userConfig.show_reasoning;
|
145
|
+
}
|
146
|
+
|
147
|
+
if (userConfig.reasoning_prompt_template) {
|
148
|
+
delete userConfig.reasoning_prompt_template;
|
149
|
+
}
|
150
|
+
|
151
|
+
const mergedConfig = {
|
152
|
+
...DEFAULT_CONFIG, // 首先应用默认配置
|
153
|
+
...userConfig // 然后用用户配置覆盖默认值
|
154
|
+
};
|
155
|
+
|
156
|
+
// 确保api_key字段存在,兼容旧版配置
|
157
|
+
if (!mergedConfig.api_key && userConfig.deepseek_api_key) {
|
158
|
+
mergedConfig.api_key = userConfig.deepseek_api_key;
|
159
|
+
}
|
160
|
+
|
161
|
+
return mergedConfig;
|
162
|
+
} catch (parseError) {
|
163
|
+
console.error(colorize(`解析配置文件失败: ${parseError.message},将使用默认配置`, 'red'));
|
164
|
+
return {...DEFAULT_CONFIG};
|
165
|
+
}
|
166
|
+
}
|
167
|
+
return {...DEFAULT_CONFIG}; // 如果配置文件不存在,返回默认配置的副本
|
168
|
+
} catch (error) {
|
169
|
+
console.error(colorize(`加载配置失败: ${error.message}`, 'red'));
|
170
|
+
return {...DEFAULT_CONFIG}; // 如果出错,返回默认配置的副本
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
// 保存配置
|
175
|
+
function saveConfig(config) {
|
176
|
+
try {
|
177
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
178
|
+
return true;
|
179
|
+
} catch (error) {
|
180
|
+
console.error(colorize(`保存配置失败: ${error.message}`, 'red'));
|
181
|
+
return false;
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
// 设置API密钥
|
186
|
+
function setApiKey(key) {
|
187
|
+
const config = loadConfig();
|
188
|
+
config.api_key = key;
|
189
|
+
return saveConfig(config);
|
190
|
+
}
|
191
|
+
|
192
|
+
// 获取API密钥
|
193
|
+
function getApiKey() {
|
194
|
+
const config = loadConfig();
|
195
|
+
return config.api_key;
|
196
|
+
}
|
197
|
+
|
198
|
+
// 设置AI模型
|
199
|
+
function setAIModel(model) {
|
200
|
+
const config = loadConfig();
|
201
|
+
config.model = model;
|
202
|
+
return saveConfig(config);
|
203
|
+
}
|
204
|
+
|
205
|
+
// 设置默认作者
|
206
|
+
function setDefaultAuthor(author) {
|
207
|
+
const config = loadConfig();
|
208
|
+
config.default_author = author;
|
209
|
+
return saveConfig(config);
|
210
|
+
}
|
211
|
+
|
212
|
+
// 设置默认时间范围
|
213
|
+
function setDefaultTimeRange(since, until) {
|
214
|
+
const config = loadConfig();
|
215
|
+
if (since) config.default_since = since;
|
216
|
+
if (until) config.default_until = until;
|
217
|
+
return saveConfig(config);
|
218
|
+
}
|
219
|
+
|
220
|
+
// 添加或更新仓库配置
|
221
|
+
function addRepository(alias, path) {
|
222
|
+
const config = loadConfig();
|
223
|
+
if (!config.repositories) {
|
224
|
+
config.repositories = {};
|
225
|
+
}
|
226
|
+
config.repositories[alias] = path;
|
227
|
+
return saveConfig(config);
|
228
|
+
}
|
229
|
+
|
230
|
+
// 删除仓库配置
|
231
|
+
function removeRepository(alias) {
|
232
|
+
const config = loadConfig();
|
233
|
+
if (config.repositories && config.repositories[alias]) {
|
234
|
+
delete config.repositories[alias];
|
235
|
+
return saveConfig(config);
|
236
|
+
}
|
237
|
+
return false;
|
238
|
+
}
|
239
|
+
|
240
|
+
// 获取仓库路径(支持别名)
|
241
|
+
function getRepositoryPath(repoIdentifier, useLocalRepo) {
|
242
|
+
if (useLocalRepo) {
|
243
|
+
return process.cwd();
|
244
|
+
}
|
245
|
+
|
246
|
+
if (!repoIdentifier) return process.cwd();
|
247
|
+
|
248
|
+
const config = loadConfig();
|
249
|
+
if (config.repositories && config.repositories[repoIdentifier]) {
|
250
|
+
return config.repositories[repoIdentifier];
|
251
|
+
}
|
252
|
+
|
253
|
+
// 如果不是别名,就当作路径处理
|
254
|
+
return repoIdentifier;
|
255
|
+
}
|
256
|
+
|
257
|
+
// 列出所有配置的仓库
|
258
|
+
function listRepositories() {
|
259
|
+
const config = loadConfig();
|
260
|
+
return config.repositories || {};
|
261
|
+
}
|
262
|
+
|
263
|
+
// 创建一个高级spinner
|
264
|
+
function createSpinner() {
|
265
|
+
// 如果ora模块未加载完成或不支持,提供一个简单的替代方案
|
266
|
+
if (!ora) {
|
267
|
+
return {
|
268
|
+
start(text) {
|
269
|
+
console.log(text);
|
270
|
+
return this;
|
271
|
+
},
|
272
|
+
stop(text) {
|
273
|
+
if (text) console.log(text);
|
274
|
+
return this;
|
275
|
+
},
|
276
|
+
fail(text) {
|
277
|
+
console.error(text || '操作失败');
|
278
|
+
return this;
|
279
|
+
},
|
280
|
+
update(text) {
|
281
|
+
console.log(text);
|
282
|
+
return this;
|
283
|
+
}
|
284
|
+
};
|
285
|
+
}
|
286
|
+
|
287
|
+
// 原有的spinner实现
|
288
|
+
const spinner = ora({
|
289
|
+
color: 'cyan',
|
290
|
+
spinner: 'dots',
|
291
|
+
discardStdin: false
|
292
|
+
});
|
293
|
+
|
294
|
+
return {
|
295
|
+
start(text) {
|
296
|
+
if (shouldUseColor) {
|
297
|
+
process.stdout.write(colorize(`⏳ ${text}`, 'cyan'));
|
298
|
+
} else {
|
299
|
+
process.stdout.write(`${text}`);
|
300
|
+
}
|
301
|
+
return this;
|
302
|
+
},
|
303
|
+
stop(text) {
|
304
|
+
process.stdout.clearLine?.(0);
|
305
|
+
process.stdout.cursorTo?.(0);
|
306
|
+
if (shouldUseColor) {
|
307
|
+
console.log(colorize(text, 'green'));
|
308
|
+
} else {
|
309
|
+
console.log(text);
|
310
|
+
}
|
311
|
+
return this;
|
312
|
+
},
|
313
|
+
fail(text) {
|
314
|
+
process.stdout.clearLine?.(0);
|
315
|
+
process.stdout.cursorTo?.(0);
|
316
|
+
if (shouldUseColor) {
|
317
|
+
console.log(colorize(text, 'red'));
|
318
|
+
} else {
|
319
|
+
console.log(text);
|
320
|
+
}
|
321
|
+
return this;
|
322
|
+
},
|
323
|
+
update(text) {
|
324
|
+
process.stdout.clearLine?.(0);
|
325
|
+
process.stdout.cursorTo?.(0);
|
326
|
+
if (shouldUseColor) {
|
327
|
+
process.stdout.write(colorize(`⏳ ${text}`, 'cyan'));
|
328
|
+
} else {
|
329
|
+
process.stdout.write(`${text}`);
|
330
|
+
}
|
331
|
+
return this;
|
332
|
+
}
|
333
|
+
};
|
334
|
+
}
|
335
|
+
|
336
|
+
// 修复配置文件
|
337
|
+
function fixConfigFile() {
|
338
|
+
try {
|
339
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
340
|
+
// 读取当前配置
|
341
|
+
const fileContent = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
342
|
+
let config;
|
343
|
+
|
344
|
+
try {
|
345
|
+
config = JSON.parse(fileContent);
|
346
|
+
|
347
|
+
// 迁移旧字段到新字段
|
348
|
+
if (config.deepseek_api_key && !config.api_key) {
|
349
|
+
config.api_key = config.deepseek_api_key;
|
350
|
+
delete config.deepseek_api_key;
|
351
|
+
console.log(colorize('已将配置中的 deepseek_api_key 迁移到 api_key', 'yellow'));
|
352
|
+
}
|
353
|
+
|
354
|
+
// 确保有API提供商和基础URL配置
|
355
|
+
if (!config.api_provider) {
|
356
|
+
config.api_provider = DEFAULT_CONFIG.api_provider;
|
357
|
+
console.log(colorize('已添加默认API提供商配置', 'yellow'));
|
358
|
+
}
|
359
|
+
|
360
|
+
if (!config.api_base_url) {
|
361
|
+
config.api_base_url = DEFAULT_CONFIG.api_base_url;
|
362
|
+
console.log(colorize('已添加默认API基础URL配置', 'yellow'));
|
363
|
+
}
|
364
|
+
|
365
|
+
// 移除旧版推理模型相关配置
|
366
|
+
if (config.use_reasoning !== undefined) {
|
367
|
+
delete config.use_reasoning;
|
368
|
+
console.log(colorize('已移除旧版推理模式配置', 'yellow'));
|
369
|
+
}
|
370
|
+
|
371
|
+
if (config.show_reasoning !== undefined) {
|
372
|
+
delete config.show_reasoning;
|
373
|
+
console.log(colorize('已移除旧版显示推理过程配置', 'yellow'));
|
374
|
+
}
|
375
|
+
|
376
|
+
if (config.reasoning_prompt_template) {
|
377
|
+
delete config.reasoning_prompt_template;
|
378
|
+
console.log(colorize('已移除旧版推理模板配置', 'yellow'));
|
379
|
+
}
|
380
|
+
|
381
|
+
// 检查prompt_template是否完整
|
382
|
+
if (config.prompt_template && typeof config.prompt_template === 'string') {
|
383
|
+
// 检查变量名是否被错误分割
|
384
|
+
if (config.prompt_template.includes('{log_con') &&
|
385
|
+
!config.prompt_template.includes('{log_content}')) {
|
386
|
+
console.log(colorize('警告: 配置文件中的prompt模板格式有误,已修复', 'yellow'));
|
387
|
+
config.prompt_template = config.prompt_template.replace('{log_con\ntent}', '{log_content}');
|
388
|
+
}
|
389
|
+
}
|
390
|
+
|
391
|
+
} catch (error) {
|
392
|
+
console.error(colorize(`配置文件JSON格式错误,将重新创建配置文件`, 'red'));
|
393
|
+
config = {...DEFAULT_CONFIG};
|
394
|
+
}
|
395
|
+
|
396
|
+
// 重新写入完整的配置文件
|
397
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
398
|
+
return true;
|
399
|
+
} else {
|
400
|
+
console.error(colorize(`配置文件不存在,将创建默认配置`, 'yellow'));
|
401
|
+
return saveConfig({...DEFAULT_CONFIG});
|
402
|
+
}
|
403
|
+
} catch (error) {
|
404
|
+
console.error(colorize(`修复配置文件失败: ${error.message}`, 'red'));
|
405
|
+
return false;
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
409
|
+
// 显示帮助
|
410
|
+
function showHelp() {
|
411
|
+
console.log(`
|
412
|
+
使用方法: g2log [选项]
|
413
|
+
|
414
|
+
时间参数:
|
415
|
+
--since <date> 开始日期 (默认: 7天前)
|
416
|
+
--until <date> 结束日期 (默认: 今天)
|
417
|
+
--days <number> 查询最近n天的记录 (默认: 7)
|
418
|
+
|
419
|
+
显示设置:
|
420
|
+
--no-color 禁用彩色输出
|
421
|
+
--save 保存结果到文件
|
422
|
+
--debug 显示调试信息
|
423
|
+
--show-prompt 显示完整的prompt内容
|
424
|
+
--version 显示当前版本号
|
425
|
+
|
426
|
+
配置管理:
|
427
|
+
--config 启动交互式配置向导
|
428
|
+
--set-api-key 设置API密钥
|
429
|
+
--set-api-provider 设置API提供商 (OpenAI/DeepSeek)
|
430
|
+
--set-api-base-url 设置API基础URL
|
431
|
+
--set-ai-model 设置AI模型
|
432
|
+
--set-default-author 设置默认作者
|
433
|
+
--add-repo 添加仓库配置
|
434
|
+
--remove-repo 移除仓库配置
|
435
|
+
--list-repos 列出所有配置的仓库
|
436
|
+
--uninstall 删除g2log配置文件 (~/.git-user-log-config.json)
|
437
|
+
|
438
|
+
示例:
|
439
|
+
g2log --since "2024-01-01" --until "2024-01-31"
|
440
|
+
g2log --days 30
|
441
|
+
g2log --config
|
442
|
+
g2log --set-api-key "your-api-key"
|
443
|
+
g2log --add-repo "alias" "path/to/repo"
|
444
|
+
g2log --remove-repo "alias"
|
445
|
+
g2log --list-repos
|
446
|
+
g2log --version
|
447
|
+
`);
|
448
|
+
process.exit(0);
|
449
|
+
}
|
450
|
+
|
451
|
+
// 解析命令行参数
|
452
|
+
function parseArgs() {
|
453
|
+
const args = {};
|
454
|
+
const rawArgs = process.argv.slice(2);
|
455
|
+
|
456
|
+
// 将帮助标志和便捷选项放在前面
|
457
|
+
if (rawArgs.includes('-h') || rawArgs.includes('--help')) {
|
458
|
+
args.help = true;
|
459
|
+
return args;
|
460
|
+
}
|
461
|
+
|
462
|
+
// 解析标准参数
|
463
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
464
|
+
const arg = rawArgs[i];
|
465
|
+
|
466
|
+
// 处理格式为 --key=value 的参数
|
467
|
+
if (arg.startsWith('--') && arg.includes('=')) {
|
468
|
+
const parts = arg.substring(2).split('=');
|
469
|
+
const key = parts[0];
|
470
|
+
const value = parts.slice(1).join('=').replace(/^["'](.*)["']$/, '$1'); // 移除可能的引号
|
471
|
+
args[key] = value;
|
472
|
+
continue;
|
473
|
+
}
|
474
|
+
|
475
|
+
// 处理格式为 --key value 的参数
|
476
|
+
if (arg.startsWith('--') && i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith('--')) {
|
477
|
+
const key = arg.substring(2);
|
478
|
+
const value = rawArgs[i + 1];
|
479
|
+
args[key] = value;
|
480
|
+
i++; // 跳过下一个参数,因为它是值
|
481
|
+
continue;
|
482
|
+
}
|
483
|
+
|
484
|
+
// 处理格式为 --flag 的布尔参数
|
485
|
+
if (arg.startsWith('--')) {
|
486
|
+
const key = arg.substring(2);
|
487
|
+
args[key] = true;
|
488
|
+
}
|
489
|
+
}
|
490
|
+
|
491
|
+
// 处理特殊参数
|
492
|
+
if (args.local === undefined) {
|
493
|
+
args.local = false; // 默认使用配置中的仓库
|
494
|
+
}
|
495
|
+
|
496
|
+
// 处理--output和--save参数,它们是同义词
|
497
|
+
if (args.save && !args.output) {
|
498
|
+
args.output = args.save;
|
499
|
+
}
|
500
|
+
|
501
|
+
// 添加--skip-config-check参数支持
|
502
|
+
if (args['skip-config-check'] === undefined) {
|
503
|
+
args['skip-config-check'] = false; // 默认不跳过配置检查
|
504
|
+
}
|
505
|
+
|
506
|
+
// 添加--config参数支持(用于显式启动配置向导)
|
507
|
+
if (args.config === undefined) {
|
508
|
+
args.config = false; // 默认不启动配置向导
|
509
|
+
}
|
510
|
+
|
511
|
+
return args;
|
512
|
+
}
|
513
|
+
|
514
|
+
// 获取提交的详细信息
|
515
|
+
function getCommitDetails(repoPath, commitHash) {
|
516
|
+
try {
|
517
|
+
// 获取完整的提交信息
|
518
|
+
const detailCommand = `git -C "${repoPath}" show --no-patch --pretty=fuller ${commitHash}`;
|
519
|
+
const details = execSync(detailCommand, { encoding: 'utf-8' });
|
520
|
+
|
521
|
+
// 获取提交所属的分支信息
|
522
|
+
let branchInfo = '';
|
523
|
+
try {
|
524
|
+
const branchCommand = `git -C "${repoPath}" branch --contains ${commitHash} | grep -v "detached" | grep -v "no branch"`;
|
525
|
+
branchInfo = execSync(branchCommand, { encoding: 'utf-8' }).trim()
|
526
|
+
.replace(/^\*?\s+/, '') // 移除前导星号和空格
|
527
|
+
.split('\n')
|
528
|
+
.map(branch => branch.trim())
|
529
|
+
.filter(branch => branch)
|
530
|
+
.join(', ');
|
531
|
+
} catch (e) {
|
532
|
+
branchInfo = '无分支信息';
|
533
|
+
}
|
534
|
+
|
535
|
+
// 获取与提交相关的标签
|
536
|
+
let tagInfo = '';
|
537
|
+
try {
|
538
|
+
const tagCommand = `git -C "${repoPath}" tag --contains ${commitHash}`;
|
539
|
+
tagInfo = execSync(tagCommand, { encoding: 'utf-8' }).trim();
|
540
|
+
if (tagInfo) {
|
541
|
+
tagInfo = tagInfo.split('\n').join(', ');
|
542
|
+
} else {
|
543
|
+
tagInfo = '无标签';
|
544
|
+
}
|
545
|
+
} catch (e) {
|
546
|
+
tagInfo = '无标签信息';
|
547
|
+
}
|
548
|
+
|
549
|
+
return {
|
550
|
+
details,
|
551
|
+
branches: branchInfo,
|
552
|
+
tags: tagInfo
|
553
|
+
};
|
554
|
+
} catch (error) {
|
555
|
+
return {
|
556
|
+
details: `无法获取提交详情: ${error.message}`,
|
557
|
+
branches: '无分支信息',
|
558
|
+
tags: '无标签信息'
|
559
|
+
};
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
// 获取提交简洁信息
|
564
|
+
function getCommitSimpleDetails(repoPath, commitHash) {
|
565
|
+
try {
|
566
|
+
// 仅获取提交日期和消息
|
567
|
+
const simpleCommand = `git -C "${repoPath}" show --no-patch --pretty=format:"%ad%n%n%s%n%n%b" --date=format:"%Y-%m-%d %H:%M:%S" ${commitHash}`;
|
568
|
+
return execSync(simpleCommand, { encoding: 'utf-8' });
|
569
|
+
} catch (error) {
|
570
|
+
return `无法获取提交信息: ${error.message}`;
|
571
|
+
}
|
572
|
+
}
|
573
|
+
|
574
|
+
// 获取提交统计信息
|
575
|
+
function getCommitStats(repoPath, commitHash) {
|
576
|
+
try {
|
577
|
+
const statsCommand = `git -C "${repoPath}" show --stat ${commitHash}`;
|
578
|
+
return execSync(statsCommand, { encoding: 'utf-8' }).split('\n').slice(1).join('\n');
|
579
|
+
} catch (error) {
|
580
|
+
return `无法获取统计信息: ${error.message}`;
|
581
|
+
}
|
582
|
+
}
|
583
|
+
|
584
|
+
// 获取提交补丁信息
|
585
|
+
function getCommitPatch(repoPath, commitHash) {
|
586
|
+
try {
|
587
|
+
const patchCommand = `git -C "${repoPath}" show --patch ${commitHash}`;
|
588
|
+
const patchOutput = execSync(patchCommand, { encoding: 'utf-8' });
|
589
|
+
|
590
|
+
// 简单处理补丁输出,去除前几行提交信息 (因为我们已经在别处显示了)
|
591
|
+
const lines = patchOutput.split('\n');
|
592
|
+
let startIndex = 0;
|
593
|
+
|
594
|
+
// 查找补丁开始的位置 (通常是 diff --git 行)
|
595
|
+
for (let i = 0; i < lines.length; i++) {
|
596
|
+
if (lines[i].startsWith('diff --git')) {
|
597
|
+
startIndex = i;
|
598
|
+
break;
|
599
|
+
}
|
600
|
+
}
|
601
|
+
|
602
|
+
return lines.slice(startIndex).join('\n');
|
603
|
+
} catch (error) {
|
604
|
+
return `无法获取补丁信息: ${error.message}`;
|
605
|
+
}
|
606
|
+
}
|
607
|
+
|
608
|
+
// 格式化提交信息 - 添加颜色
|
609
|
+
function formatCommitLine(line, useColor) {
|
610
|
+
if (!useColor) return line;
|
611
|
+
|
612
|
+
try {
|
613
|
+
// 尝试将提交行分成不同部分来应用颜色
|
614
|
+
// 假设格式是 "hash - author (date): message"
|
615
|
+
const hashMatch = line.match(/^([a-f0-9]+)/);
|
616
|
+
if (hashMatch) {
|
617
|
+
const hash = hashMatch[1];
|
618
|
+
const restOfLine = line.substring(hash.length);
|
619
|
+
|
620
|
+
// 查找日期部分
|
621
|
+
const dateMatch = restOfLine.match(/\((.*?)\)/);
|
622
|
+
if (dateMatch) {
|
623
|
+
const beforeDate = restOfLine.substring(0, dateMatch.index);
|
624
|
+
const date = dateMatch[0];
|
625
|
+
const afterDate = restOfLine.substring(dateMatch.index + date.length);
|
626
|
+
|
627
|
+
// 给不同部分添加颜色
|
628
|
+
return colorize(hash, 'yellow') +
|
629
|
+
beforeDate +
|
630
|
+
colorize(date, 'green') +
|
631
|
+
colorize(afterDate, 'cyan');
|
632
|
+
}
|
633
|
+
}
|
634
|
+
} catch (e) {
|
635
|
+
// 如果解析失败,返回原始行
|
636
|
+
}
|
637
|
+
|
638
|
+
return line;
|
639
|
+
}
|
640
|
+
|
641
|
+
// 将补丁内容着色
|
642
|
+
function colorizePatch(patch, useColor) {
|
643
|
+
if (!useColor) return patch;
|
644
|
+
|
645
|
+
return patch.split('\n').map(line => {
|
646
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
647
|
+
return colorize(line, 'green');
|
648
|
+
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
649
|
+
return colorize(line, 'red');
|
650
|
+
} else if (line.startsWith('@@ ')) {
|
651
|
+
return colorize(line, 'cyan');
|
652
|
+
} else if (line.startsWith('diff ') || line.startsWith('index ')) {
|
653
|
+
return colorize(line, 'yellow');
|
654
|
+
} else {
|
655
|
+
return line;
|
656
|
+
}
|
657
|
+
}).join('\n');
|
658
|
+
}
|
659
|
+
|
660
|
+
// 构建完整的API URL
|
661
|
+
function buildApiUrl(baseUrl, endpoint = 'chat/completions') {
|
662
|
+
// 如果baseUrl以斜杠结尾,直接拼接endpoint
|
663
|
+
if (baseUrl.endsWith('/')) {
|
664
|
+
return `${baseUrl}${endpoint}`;
|
665
|
+
}
|
666
|
+
|
667
|
+
// 如果baseUrl不以斜杠结尾,添加斜杠再拼接endpoint
|
668
|
+
return `${baseUrl}/${endpoint}`;
|
669
|
+
}
|
670
|
+
|
671
|
+
// 使用AI进行总结
|
672
|
+
async function summarizeWithAI(gitLogs, author, since, until, spinner = null) {
|
673
|
+
try {
|
674
|
+
// 加载配置
|
675
|
+
const config = loadConfig();
|
676
|
+
const modelName = config.ai_model || 'gpt-4-turbo';
|
677
|
+
const apiKey = config.api_key || '';
|
678
|
+
const apiProvider = config.api_provider || 'openai';
|
679
|
+
const apiBaseURL = config.api_base_url || '';
|
680
|
+
|
681
|
+
let prompt = config.prompt_template || `请根据以下Git提交记录,总结${author}在${since}到${until}期间的主要工作内容。
|
682
|
+
按照类别进行归纳,突出重点任务和成就。
|
683
|
+
用清晰的标题和小标题组织内容,确保总结全面且易于阅读。
|
684
|
+
|
685
|
+
Git提交记录:
|
686
|
+
{{GIT_LOGS}}`;
|
687
|
+
|
688
|
+
// 替换变量 - 支持多种变量格式以兼容用户自定义模板
|
689
|
+
prompt = prompt.replace('{{GIT_LOGS}}', gitLogs)
|
690
|
+
.replace('{log_content}', gitLogs) // 添加对{log_content}格式的支持
|
691
|
+
.replace('{{AUTHOR}}', author)
|
692
|
+
.replace('{author}', author)
|
693
|
+
.replace('{{SINCE}}', since)
|
694
|
+
.replace('{since}', since)
|
695
|
+
.replace('{{UNTIL}}', until)
|
696
|
+
.replace('{until}', until);
|
697
|
+
|
698
|
+
if (spinner) spinner.update('🔄 正在连接API...');
|
699
|
+
|
700
|
+
// 打印完整提示内容(添加--debug参数时显示)
|
701
|
+
if (process.argv.includes('--debug') || process.argv.includes('--show-prompt')) {
|
702
|
+
console.log(colorize('\n📝 完整提示内容:', 'cyan'));
|
703
|
+
console.log(colorize('=' .repeat(50), 'dim'));
|
704
|
+
console.log(prompt);
|
705
|
+
console.log(colorize('=' .repeat(50), 'dim'));
|
706
|
+
}
|
707
|
+
|
708
|
+
// 根据不同的API提供商使用不同的实现
|
709
|
+
let aiResponse = '';
|
710
|
+
const providerLower = apiProvider.toLowerCase();
|
711
|
+
|
712
|
+
// 输出AI总结的标题信息
|
713
|
+
console.log(`\n${colorize('📊 ' + author + ' 的工作总结', 'bright')}`);
|
714
|
+
console.log(`${colorize('📅 时间范围: ' + since + ' 至 ' + until, 'green')}`);
|
715
|
+
console.log(`${colorize('🤖 使用模型: ' + modelName, 'cyan')}`);
|
716
|
+
console.log(`${colorize('=' .repeat(30), 'bright')}\n`);
|
717
|
+
|
718
|
+
// 根据提供商名称选择对应的实现
|
719
|
+
if (providerLower === 'openai') {
|
720
|
+
aiResponse = await getOpenAIResponse(apiKey, prompt, modelName, apiBaseURL, spinner);
|
721
|
+
} else {
|
722
|
+
// 其他提供商默认使用DeepSeek实现
|
723
|
+
aiResponse = await getDeepSeekResponse(apiKey, prompt, modelName, apiBaseURL, spinner);
|
724
|
+
}
|
725
|
+
|
726
|
+
// 停止spinner并显示成功消息
|
727
|
+
if (spinner) spinner.stop('✅ AI总结已生成');
|
728
|
+
|
729
|
+
return aiResponse;
|
730
|
+
} catch (error) {
|
731
|
+
if (spinner) spinner.fail(`❌ AI总结失败: ${error.message}`);
|
732
|
+
throw error;
|
733
|
+
}
|
734
|
+
}
|
735
|
+
|
736
|
+
// 从OpenAI获取响应
|
737
|
+
async function getOpenAIResponse(apiKey, prompt, modelName, apiBaseURL, spinner = null) {
|
738
|
+
// 验证参数
|
739
|
+
if (!apiKey) throw new Error('未设置OpenAI API密钥');
|
740
|
+
|
741
|
+
// 构造请求头和URL
|
742
|
+
const headers = {
|
743
|
+
'Content-Type': 'application/json',
|
744
|
+
'Authorization': `Bearer ${apiKey}`
|
745
|
+
};
|
746
|
+
|
747
|
+
const baseURL = apiBaseURL || 'https://api.openai.com';
|
748
|
+
const url = `${baseURL}/v1/chat/completions`;
|
749
|
+
|
750
|
+
// 构造请求体
|
751
|
+
const data = {
|
752
|
+
model: modelName || 'gpt-4',
|
753
|
+
messages: [
|
754
|
+
{ role: 'system', content: '你是一位专业的工作总结助手,擅长将Git提交记录整理成清晰的工作报告。' },
|
755
|
+
{ role: 'user', content: prompt }
|
756
|
+
],
|
757
|
+
temperature: 0.5,
|
758
|
+
max_tokens: 2048,
|
759
|
+
stream: true // 启用流式传输
|
760
|
+
};
|
761
|
+
|
762
|
+
// 打印请求内容
|
763
|
+
console.log(colorize('\n📨 发送给AI的请求:', 'cyan'));
|
764
|
+
console.log(colorize(`📌 API端点: ${url}`, 'dim'));
|
765
|
+
console.log(colorize(`🤖 使用模型: ${data.model}`, 'dim'));
|
766
|
+
console.log(colorize(`🌡️ 温度: ${data.temperature}`, 'dim'));
|
767
|
+
console.log(colorize(`🔢 最大Token: ${data.max_tokens}`, 'dim'));
|
768
|
+
console.log(colorize('📄 系统角色: ' + data.messages[0].content, 'dim'));
|
769
|
+
console.log(colorize('💬 提示内容预览: ' + data.messages[1].content.substring(0, 150) + '...', 'dim'));
|
770
|
+
|
771
|
+
if (spinner) spinner.update('🔄 正在向AI发送请求...\n');
|
772
|
+
|
773
|
+
return new Promise((resolve, reject) => {
|
774
|
+
try {
|
775
|
+
// 解析URL以获取主机名和路径
|
776
|
+
const urlObj = new URL(url);
|
777
|
+
|
778
|
+
// 准备请求选项
|
779
|
+
const options = {
|
780
|
+
hostname: urlObj.hostname,
|
781
|
+
path: urlObj.pathname,
|
782
|
+
method: 'POST',
|
783
|
+
headers: headers,
|
784
|
+
rejectUnauthorized: false // 在开发环境中可能需要
|
785
|
+
};
|
786
|
+
|
787
|
+
// 确定使用http还是https
|
788
|
+
const protocol = urlObj.protocol === 'https:' ? require('https') : require('http');
|
789
|
+
|
790
|
+
// 创建请求
|
791
|
+
const req = protocol.request(options, (res) => {
|
792
|
+
// 检查状态码
|
793
|
+
if (res.statusCode !== 200) {
|
794
|
+
let errorData = '';
|
795
|
+
res.on('data', chunk => {
|
796
|
+
errorData += chunk.toString();
|
797
|
+
});
|
798
|
+
res.on('end', () => {
|
799
|
+
let errorMessage = `OpenAI API请求失败 (${res.statusCode})`;
|
800
|
+
try {
|
801
|
+
const parsedError = JSON.parse(errorData);
|
802
|
+
errorMessage += `: ${JSON.stringify(parsedError)}`;
|
803
|
+
} catch (e) {
|
804
|
+
errorMessage += `: ${errorData}`;
|
805
|
+
}
|
806
|
+
if (spinner) spinner.fail(`❌ ${errorMessage}`);
|
807
|
+
reject(new Error(errorMessage));
|
808
|
+
});
|
809
|
+
return;
|
810
|
+
}
|
811
|
+
|
812
|
+
let fullContent = '';
|
813
|
+
let buffer = '';
|
814
|
+
|
815
|
+
// 处理数据
|
816
|
+
res.on('data', (chunk) => {
|
817
|
+
// 将新的数据添加到缓冲区
|
818
|
+
const data = chunk.toString();
|
819
|
+
buffer += data;
|
820
|
+
|
821
|
+
// 尝试从缓冲区中提取完整的SSE消息
|
822
|
+
const messages = buffer.split('\n\n');
|
823
|
+
|
824
|
+
// 处理除了最后一个可能不完整的消息之外的所有消息
|
825
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
826
|
+
const message = messages[i].trim();
|
827
|
+
if (!message) continue; // 跳过空消息
|
828
|
+
|
829
|
+
// 处理SSE格式的消息
|
830
|
+
if (message.startsWith('data: ')) {
|
831
|
+
const content = message.substring(6); // 移除 'data: ' 前缀
|
832
|
+
|
833
|
+
// 跳过[DONE]消息
|
834
|
+
if (content === '[DONE]') continue;
|
835
|
+
|
836
|
+
try {
|
837
|
+
const parsed = JSON.parse(content);
|
838
|
+
if (parsed.choices &&
|
839
|
+
parsed.choices[0] &&
|
840
|
+
parsed.choices[0].delta &&
|
841
|
+
parsed.choices[0].delta.content) {
|
842
|
+
const contentPart = parsed.choices[0].delta.content;
|
843
|
+
fullContent += contentPart;
|
844
|
+
|
845
|
+
// 直接输出内容增量到控制台
|
846
|
+
process.stdout.write(contentPart);
|
847
|
+
}
|
848
|
+
} catch (err) {
|
849
|
+
// 忽略解析错误,但在调试模式下输出
|
850
|
+
if (process.argv.includes('--debug')) {
|
851
|
+
console.error(`解析错误: ${err.message} for content: ${content}`);
|
852
|
+
}
|
853
|
+
}
|
854
|
+
}
|
855
|
+
}
|
856
|
+
|
857
|
+
// 保留最后一个可能不完整的消息
|
858
|
+
buffer = messages[messages.length - 1];
|
859
|
+
});
|
860
|
+
|
861
|
+
// 处理响应结束
|
862
|
+
res.on('end', () => {
|
863
|
+
// 处理缓冲区中可能剩余的内容
|
864
|
+
if (buffer.trim()) {
|
865
|
+
const lines = buffer.split('\n');
|
866
|
+
for (const line of lines) {
|
867
|
+
if (line.startsWith('data: ') && line.substring(6) !== '[DONE]') {
|
868
|
+
try {
|
869
|
+
const parsed = JSON.parse(line.substring(6));
|
870
|
+
if (parsed.choices &&
|
871
|
+
parsed.choices[0] &&
|
872
|
+
parsed.choices[0].delta &&
|
873
|
+
parsed.choices[0].delta.content) {
|
874
|
+
const contentPart = parsed.choices[0].delta.content;
|
875
|
+
fullContent += contentPart;
|
876
|
+
|
877
|
+
// 直接输出内容增量到控制台
|
878
|
+
process.stdout.write(contentPart);
|
879
|
+
}
|
880
|
+
} catch (err) {
|
881
|
+
// 忽略解析错误
|
882
|
+
}
|
883
|
+
}
|
884
|
+
}
|
885
|
+
}
|
886
|
+
|
887
|
+
// 确保输出后有换行符
|
888
|
+
console.log(); // 强制添加换行符
|
889
|
+
|
890
|
+
if (spinner) spinner.stop('✅ AI响应已结束');
|
891
|
+
resolve(fullContent);
|
892
|
+
});
|
893
|
+
});
|
894
|
+
|
895
|
+
// 处理请求错误
|
896
|
+
req.on('error', (error) => {
|
897
|
+
if (spinner) spinner.fail(`❌ OpenAI API网络错误: ${error.message}`);
|
898
|
+
reject(error);
|
899
|
+
});
|
900
|
+
|
901
|
+
// 发送请求体
|
902
|
+
req.write(JSON.stringify(data));
|
903
|
+
req.end();
|
904
|
+
} catch (error) {
|
905
|
+
if (spinner) spinner.fail(`❌ OpenAI API错误: ${error.message}`);
|
906
|
+
reject(error);
|
907
|
+
}
|
908
|
+
});
|
909
|
+
}
|
910
|
+
|
911
|
+
// 从DeepSeek获取响应
|
912
|
+
async function getDeepSeekResponse(apiKey, prompt, modelName, apiBaseURL, spinner = null) {
|
913
|
+
// 验证参数
|
914
|
+
if (!apiKey) throw new Error('未设置DeepSeek API密钥');
|
915
|
+
|
916
|
+
// 构造请求头和URL
|
917
|
+
const headers = {
|
918
|
+
'Content-Type': 'application/json',
|
919
|
+
'Authorization': `Bearer ${apiKey}`
|
920
|
+
};
|
921
|
+
|
922
|
+
const baseURL = apiBaseURL || 'https://api.deepseek.com';
|
923
|
+
const url = `${baseURL}/v1/chat/completions`;
|
924
|
+
|
925
|
+
// 构造请求体
|
926
|
+
const data = {
|
927
|
+
model: modelName || 'deepseek-chat',
|
928
|
+
messages: [
|
929
|
+
{ role: 'system', content: '你是一位专业的工作总结助手,擅长将Git提交记录整理成清晰的工作报告。' },
|
930
|
+
{ role: 'user', content: prompt }
|
931
|
+
],
|
932
|
+
temperature: 0.5,
|
933
|
+
max_tokens: 2048,
|
934
|
+
stream: true // 启用流式传输
|
935
|
+
};
|
936
|
+
|
937
|
+
// 打印请求内容
|
938
|
+
console.log(colorize('\n📨 发送给AI的请求:', 'cyan'));
|
939
|
+
console.log(colorize(`📌 API提供商: ${apiBaseURL}`, 'dim'));
|
940
|
+
console.log(colorize(`🤖 使用模型: ${data.model}`, 'dim'));
|
941
|
+
console.log(colorize(`🌡️ 温度: ${data.temperature}`, 'dim'));
|
942
|
+
console.log(colorize(`🔢 最大Token: ${data.max_tokens}`, 'dim'));
|
943
|
+
console.log(colorize('📄 系统角色: ' + data.messages[0].content, 'dim'));
|
944
|
+
console.log(colorize('💬 提示内容预览: ' + data.messages[1].content.substring(0, 150) + '...', 'dim'));
|
945
|
+
|
946
|
+
if (spinner) spinner.update('🔄 正在向AI发送请求...\n');
|
947
|
+
|
948
|
+
return new Promise((resolve, reject) => {
|
949
|
+
try {
|
950
|
+
// 解析URL以获取主机名和路径
|
951
|
+
const urlObj = new URL(url);
|
952
|
+
|
953
|
+
// 准备请求选项
|
954
|
+
const options = {
|
955
|
+
hostname: urlObj.hostname,
|
956
|
+
path: urlObj.pathname,
|
957
|
+
method: 'POST',
|
958
|
+
headers: headers,
|
959
|
+
rejectUnauthorized: false // 在开发环境中可能需要
|
960
|
+
};
|
961
|
+
|
962
|
+
// 确定使用http还是https
|
963
|
+
const protocol = urlObj.protocol === 'https:' ? require('https') : require('http');
|
964
|
+
|
965
|
+
// 创建请求
|
966
|
+
const req = protocol.request(options, (res) => {
|
967
|
+
// 检查状态码
|
968
|
+
if (res.statusCode !== 200) {
|
969
|
+
let errorData = '';
|
970
|
+
res.on('data', chunk => {
|
971
|
+
errorData += chunk.toString();
|
972
|
+
});
|
973
|
+
res.on('end', () => {
|
974
|
+
let errorMessage = `DeepSeek API请求失败 (${res.statusCode})`;
|
975
|
+
try {
|
976
|
+
const parsedError = JSON.parse(errorData);
|
977
|
+
errorMessage += `: ${JSON.stringify(parsedError)}`;
|
978
|
+
} catch (e) {
|
979
|
+
errorMessage += `: ${errorData}`;
|
980
|
+
}
|
981
|
+
if (spinner) spinner.fail(`❌ ${errorMessage}`);
|
982
|
+
reject(new Error(errorMessage));
|
983
|
+
});
|
984
|
+
return;
|
985
|
+
}
|
986
|
+
|
987
|
+
let fullContent = '';
|
988
|
+
let buffer = '';
|
989
|
+
|
990
|
+
// 处理数据
|
991
|
+
res.on('data', (chunk) => {
|
992
|
+
// 将新的数据添加到缓冲区
|
993
|
+
const data = chunk.toString();
|
994
|
+
buffer += data;
|
995
|
+
|
996
|
+
// 尝试从缓冲区中提取完整的SSE消息
|
997
|
+
const messages = buffer.split('\n\n');
|
998
|
+
|
999
|
+
// 处理除了最后一个可能不完整的消息之外的所有消息
|
1000
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
1001
|
+
const message = messages[i].trim();
|
1002
|
+
if (!message) continue; // 跳过空消息
|
1003
|
+
|
1004
|
+
// 处理SSE格式的消息
|
1005
|
+
if (message.startsWith('data: ')) {
|
1006
|
+
const content = message.substring(6); // 移除 'data: ' 前缀
|
1007
|
+
|
1008
|
+
// 跳过[DONE]消息
|
1009
|
+
if (content === '[DONE]') continue;
|
1010
|
+
|
1011
|
+
try {
|
1012
|
+
const parsed = JSON.parse(content);
|
1013
|
+
if (parsed.choices &&
|
1014
|
+
parsed.choices[0] &&
|
1015
|
+
parsed.choices[0].delta &&
|
1016
|
+
parsed.choices[0].delta.content) {
|
1017
|
+
const contentPart = parsed.choices[0].delta.content;
|
1018
|
+
fullContent += contentPart;
|
1019
|
+
|
1020
|
+
// 直接输出内容增量到控制台
|
1021
|
+
process.stdout.write(contentPart);
|
1022
|
+
}
|
1023
|
+
} catch (err) {
|
1024
|
+
// 忽略解析错误,但在调试模式下输出
|
1025
|
+
if (process.argv.includes('--debug')) {
|
1026
|
+
console.error(`解析错误: ${err.message} for content: ${content}`);
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
}
|
1030
|
+
}
|
1031
|
+
|
1032
|
+
// 保留最后一个可能不完整的消息
|
1033
|
+
buffer = messages[messages.length - 1];
|
1034
|
+
});
|
1035
|
+
|
1036
|
+
// 处理响应结束
|
1037
|
+
res.on('end', () => {
|
1038
|
+
// 处理缓冲区中可能剩余的内容
|
1039
|
+
if (buffer.trim()) {
|
1040
|
+
const lines = buffer.split('\n');
|
1041
|
+
for (const line of lines) {
|
1042
|
+
if (line.startsWith('data: ') && line.substring(6) !== '[DONE]') {
|
1043
|
+
try {
|
1044
|
+
const parsed = JSON.parse(line.substring(6));
|
1045
|
+
if (parsed.choices &&
|
1046
|
+
parsed.choices[0] &&
|
1047
|
+
parsed.choices[0].delta &&
|
1048
|
+
parsed.choices[0].delta.content) {
|
1049
|
+
const contentPart = parsed.choices[0].delta.content;
|
1050
|
+
fullContent += contentPart;
|
1051
|
+
|
1052
|
+
// 直接输出内容增量到控制台
|
1053
|
+
process.stdout.write(contentPart);
|
1054
|
+
}
|
1055
|
+
} catch (err) {
|
1056
|
+
// 忽略解析错误
|
1057
|
+
}
|
1058
|
+
}
|
1059
|
+
}
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
// 确保输出后有换行符
|
1063
|
+
console.log(); // 强制添加换行符
|
1064
|
+
|
1065
|
+
if (spinner) spinner.stop('✅ AI响应已结束');
|
1066
|
+
resolve(fullContent);
|
1067
|
+
});
|
1068
|
+
});
|
1069
|
+
|
1070
|
+
// 处理请求错误
|
1071
|
+
req.on('error', (error) => {
|
1072
|
+
if (spinner) spinner.fail(`❌ DeepSeek API网络错误: ${error.message}`);
|
1073
|
+
reject(error);
|
1074
|
+
});
|
1075
|
+
|
1076
|
+
// 发送请求体
|
1077
|
+
req.write(JSON.stringify(data));
|
1078
|
+
req.end();
|
1079
|
+
} catch (error) {
|
1080
|
+
if (spinner) spinner.fail(`❌ DeepSeek API错误: ${error.message}`);
|
1081
|
+
reject(error);
|
1082
|
+
}
|
1083
|
+
});
|
1084
|
+
}
|
1085
|
+
|
1086
|
+
// 从多个仓库获取日志
|
1087
|
+
async function getLogsFromMultipleRepos(author, since, until, options) {
|
1088
|
+
const config = loadConfig();
|
1089
|
+
|
1090
|
+
// 检查是否有配置的仓库
|
1091
|
+
if (!config.repositories || Object.keys(config.repositories).length === 0) {
|
1092
|
+
console.log(colorize('⚠️ 未配置任何仓库,请使用 --add-repo="别名" --path="/仓库路径" 添加仓库', 'yellow'));
|
1093
|
+
return null;
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
const spinner = createSpinner();
|
1097
|
+
spinner.start(`🔍 正在从 ${Object.keys(config.repositories).length} 个仓库获取提交记录...`);
|
1098
|
+
|
1099
|
+
// 用于保存所有仓库的日志
|
1100
|
+
let allLogs = '';
|
1101
|
+
let logCount = 0;
|
1102
|
+
let repos = 0;
|
1103
|
+
|
1104
|
+
// 遍历所有仓库
|
1105
|
+
for (const [alias, repoPath] of Object.entries(config.repositories)) {
|
1106
|
+
try {
|
1107
|
+
// 检查仓库路径是否有效
|
1108
|
+
spinner.update(`🔍 正在检查仓库 ${alias} (${repoPath})...`);
|
1109
|
+
execSync(`git -C "${repoPath}" rev-parse --is-inside-work-tree`, { stdio: 'ignore' });
|
1110
|
+
|
1111
|
+
// 构建Git命令
|
1112
|
+
let command = `git -C "${repoPath}" log --author="${author}" --since="${since}" --until="${until}" --date=format:"%Y-%m-%d %H:%M:%S"`;
|
1113
|
+
|
1114
|
+
// 添加选项
|
1115
|
+
if (options.noMerges) {
|
1116
|
+
command += ' --no-merges';
|
1117
|
+
}
|
1118
|
+
|
1119
|
+
// 添加格式选项
|
1120
|
+
if (options.simpleMode) {
|
1121
|
+
command += ` --pretty=format:"${alias} | %ad | %s%n%b%n"`;
|
1122
|
+
} else {
|
1123
|
+
command += ` --pretty=format:"${alias} | %ad | %h | %s%n%b%n"`;
|
1124
|
+
}
|
1125
|
+
|
1126
|
+
// 执行命令
|
1127
|
+
spinner.update(`🔍 正在获取仓库 ${alias} 的提交记录...`);
|
1128
|
+
const repoLogs = execSync(command, { encoding: 'utf-8' });
|
1129
|
+
|
1130
|
+
// 检查是否有日志,如果有则添加到结果
|
1131
|
+
if (repoLogs.trim()) {
|
1132
|
+
const repoCommitCount = (repoLogs.match(/\n\n/g) || []).length + 1;
|
1133
|
+
logCount += repoCommitCount;
|
1134
|
+
repos++;
|
1135
|
+
|
1136
|
+
if (allLogs) allLogs += '\n\n';
|
1137
|
+
allLogs += repoLogs;
|
1138
|
+
}
|
1139
|
+
} catch (error) {
|
1140
|
+
spinner.update(`⚠️ 处理仓库 ${alias} 时出错: ${error.message}`);
|
1141
|
+
}
|
1142
|
+
}
|
1143
|
+
|
1144
|
+
// 更新spinner显示结果
|
1145
|
+
if (logCount > 0) {
|
1146
|
+
spinner.stop(`✅ 从仓库 ${repos > 1 ? `${repos} 个仓库` : Object.keys(config.repositories)[0]} 获取到 ${logCount} 条提交`);
|
1147
|
+
} else {
|
1148
|
+
spinner.stop(`📭 未找到 ${author} 在 ${since} 至 ${until} 期间的提交记录`);
|
1149
|
+
}
|
1150
|
+
|
1151
|
+
return allLogs;
|
1152
|
+
}
|
1153
|
+
|
1154
|
+
// 设置prompt模板
|
1155
|
+
function setPromptTemplate(template) {
|
1156
|
+
const config = loadConfig();
|
1157
|
+
config.prompt_template = template;
|
1158
|
+
return saveConfig(config);
|
1159
|
+
}
|
1160
|
+
|
1161
|
+
// 重置prompt模板到默认值
|
1162
|
+
function resetPromptTemplate() {
|
1163
|
+
const config = loadConfig();
|
1164
|
+
config.prompt_template = DEFAULT_CONFIG.prompt_template;
|
1165
|
+
const result = saveConfig(config);
|
1166
|
+
|
1167
|
+
if (result) {
|
1168
|
+
// 显示默认模板内容
|
1169
|
+
console.log('\n' + colorize('默认模板内容:', 'green'));
|
1170
|
+
console.log('===========================================');
|
1171
|
+
console.log(DEFAULT_CONFIG.prompt_template);
|
1172
|
+
console.log('===========================================\n');
|
1173
|
+
|
1174
|
+
// 再次读取配置文件,确保保存成功
|
1175
|
+
try {
|
1176
|
+
const savedConfig = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
1177
|
+
if (savedConfig.prompt_template === DEFAULT_CONFIG.prompt_template) {
|
1178
|
+
return true;
|
1179
|
+
} else {
|
1180
|
+
console.error(colorize('警告: 默认模板保存不完整,尝试直接写入...', 'yellow'));
|
1181
|
+
// 直接重写配置文件
|
1182
|
+
const fixedConfig = {...savedConfig, prompt_template: DEFAULT_CONFIG.prompt_template};
|
1183
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(fixedConfig, null, 2), 'utf-8');
|
1184
|
+
}
|
1185
|
+
} catch (error) {
|
1186
|
+
console.error(colorize(`验证保存失败: ${error.message}`, 'red'));
|
1187
|
+
return false;
|
1188
|
+
}
|
1189
|
+
}
|
1190
|
+
|
1191
|
+
return result;
|
1192
|
+
}
|
1193
|
+
|
1194
|
+
// 设置API提供商
|
1195
|
+
function setAPIProvider(provider) {
|
1196
|
+
const config = loadConfig();
|
1197
|
+
config.api_provider = provider;
|
1198
|
+
return saveConfig(config);
|
1199
|
+
}
|
1200
|
+
|
1201
|
+
// 设置API URL
|
1202
|
+
function setAPIBaseURL(url) {
|
1203
|
+
const config = loadConfig();
|
1204
|
+
config.api_base_url = url;
|
1205
|
+
return saveConfig(config);
|
1206
|
+
}
|
1207
|
+
|
1208
|
+
// 检测是否是通过npx临时运行并添加相应提示
|
1209
|
+
function showNpxInfo() {
|
1210
|
+
if (isRunningWithNpx) {
|
1211
|
+
console.log(colorize('\n💡 提示: 您正在通过npx临时运行g2log。', 'cyan'));
|
1212
|
+
console.log(colorize('要全局安装以便更快地使用,请运行:', 'cyan'));
|
1213
|
+
console.log(colorize('npm install -g g2log\n', 'green'));
|
1214
|
+
}
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
// 检查配置文件状态
|
1218
|
+
function checkConfig(silent = false) {
|
1219
|
+
try {
|
1220
|
+
// 检查配置文件是否存在
|
1221
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
1222
|
+
if (!silent) console.log(colorize('⚠️ 检测到配置缺失: 配置文件不存在', 'red'));
|
1223
|
+
return {
|
1224
|
+
needsConfig: true,
|
1225
|
+
missingConfig: ['api_key', 'default_author'],
|
1226
|
+
reason: '配置文件不存在',
|
1227
|
+
currentConfig: null
|
1228
|
+
};
|
1229
|
+
}
|
1230
|
+
|
1231
|
+
// 尝试加载配置
|
1232
|
+
const config = loadConfig();
|
1233
|
+
const missingConfig = [];
|
1234
|
+
|
1235
|
+
// 检查关键配置是否存在
|
1236
|
+
if (!config.api_key) {
|
1237
|
+
missingConfig.push('api_key');
|
1238
|
+
}
|
1239
|
+
|
1240
|
+
if (!config.default_author) {
|
1241
|
+
missingConfig.push('default_author');
|
1242
|
+
}
|
1243
|
+
|
1244
|
+
// 设置默认时间范围(如果不存在)
|
1245
|
+
if (!config.default_since) {
|
1246
|
+
config.default_since = '7 days ago';
|
1247
|
+
}
|
1248
|
+
|
1249
|
+
if (!config.default_until) {
|
1250
|
+
config.default_until = 'today';
|
1251
|
+
}
|
1252
|
+
|
1253
|
+
// 若没有设置仓库配置,添加一个空对象
|
1254
|
+
if (!config.repositories) {
|
1255
|
+
config.repositories = {};
|
1256
|
+
}
|
1257
|
+
|
1258
|
+
// 若有缺失配置,返回需要配置的状态
|
1259
|
+
if (missingConfig.length > 0) {
|
1260
|
+
if (!silent) {
|
1261
|
+
console.log(colorize(`⚠️ 检测到配置缺失: ${missingConfig.join(', ')}`, 'red'));
|
1262
|
+
}
|
1263
|
+
return {
|
1264
|
+
needsConfig: true,
|
1265
|
+
missingConfig,
|
1266
|
+
reason: '必要配置项缺失',
|
1267
|
+
currentConfig: config
|
1268
|
+
};
|
1269
|
+
}
|
1270
|
+
|
1271
|
+
// 所有必要配置都存在
|
1272
|
+
return {
|
1273
|
+
needsConfig: false,
|
1274
|
+
missingConfig: [],
|
1275
|
+
reason: '配置完整',
|
1276
|
+
currentConfig: config
|
1277
|
+
};
|
1278
|
+
} catch (error) {
|
1279
|
+
if (!silent) {
|
1280
|
+
console.error(colorize(`❌ 配置检查错误: ${error.message}`, 'red'));
|
1281
|
+
}
|
1282
|
+
return {
|
1283
|
+
needsConfig: true,
|
1284
|
+
missingConfig: ['api_key', 'default_author'],
|
1285
|
+
reason: `配置文件解析错误: ${error.message}`,
|
1286
|
+
currentConfig: null
|
1287
|
+
};
|
1288
|
+
}
|
1289
|
+
}
|
1290
|
+
|
1291
|
+
// 设置交互式配置向导
|
1292
|
+
async function setupConfigInteractive() {
|
1293
|
+
const spinner = createSpinner();
|
1294
|
+
console.log(colorize('\n🛠️ Git日志工具配置向导', 'bright'));
|
1295
|
+
console.log(colorize('=' .repeat(30), 'bright'));
|
1296
|
+
console.log();
|
1297
|
+
|
1298
|
+
// 创建readline接口
|
1299
|
+
const rl = readline.createInterface({
|
1300
|
+
input: process.stdin,
|
1301
|
+
output: process.stdout
|
1302
|
+
});
|
1303
|
+
|
1304
|
+
// 转换问题为Promise
|
1305
|
+
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
1306
|
+
|
1307
|
+
try {
|
1308
|
+
// 初始化配置
|
1309
|
+
let config = {};
|
1310
|
+
try {
|
1311
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
1312
|
+
config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
1313
|
+
console.log(colorize('ℹ️ 检测到现有配置,将在其基础上进行修改。', 'blue'));
|
1314
|
+
} else {
|
1315
|
+
console.log(colorize('ℹ️ 未检测到配置文件,将创建新配置。', 'blue'));
|
1316
|
+
config = {
|
1317
|
+
repositories: {},
|
1318
|
+
prompt_template: `请根据下面的Git提交记录,用3-5句话简洁地总结一天的工作内容。
|
1319
|
+
|
1320
|
+
以下是Git提交记录:
|
1321
|
+
|
1322
|
+
{log_content}
|
1323
|
+
|
1324
|
+
要求:
|
1325
|
+
1. 按项目和日期组织内容
|
1326
|
+
2. 每个项目每天的工作内容用3-5句话概括
|
1327
|
+
3. 使用清晰、专业但不晦涩的语言
|
1328
|
+
4. 突出重要的功能开发、问题修复和优化改进
|
1329
|
+
5. 适合放入工作日报的简洁描述
|
1330
|
+
6. 输出格式为:【日期】:
|
1331
|
+
【项目名称】- 【工作内容概述】
|
1332
|
+
【项目名称】- 【工作内容概述】
|
1333
|
+
7. 回复不要出现多余的内容,非必要不要用markdown格式`
|
1334
|
+
};
|
1335
|
+
}
|
1336
|
+
} catch (error) {
|
1337
|
+
console.log(colorize('⚠️ 读取配置文件时出错,将创建新配置。', 'yellow'));
|
1338
|
+
config = {
|
1339
|
+
repositories: {},
|
1340
|
+
prompt_template: `请根据下面的Git提交记录,用3-5句话简洁地总结一天的工作内容。
|
1341
|
+
|
1342
|
+
以下是Git提交记录:
|
1343
|
+
|
1344
|
+
{log_content}
|
1345
|
+
|
1346
|
+
要求:
|
1347
|
+
1. 按项目和日期组织内容
|
1348
|
+
2. 每个项目每天的工作内容用3-5句话概括
|
1349
|
+
3. 使用清晰、专业但不晦涩的语言
|
1350
|
+
4. 突出重要的功能开发、问题修复和优化改进
|
1351
|
+
5. 适合放入工作日报的简洁描述
|
1352
|
+
6. 输出格式为:【日期】:
|
1353
|
+
【项目名称】- 【工作内容概述】
|
1354
|
+
【项目名称】- 【工作内容概述】
|
1355
|
+
7. 回复不要出现多余的内容,非必要不要用markdown格式`
|
1356
|
+
};
|
1357
|
+
}
|
1358
|
+
|
1359
|
+
// 步骤1: 设置API提供商
|
1360
|
+
console.log(colorize('\n📡 步骤1: 设置API提供商', 'yellow'));
|
1361
|
+
console.log(colorize(' (仅作为一个备注,示例:openai, deepseek, xxx)', 'cyan'));
|
1362
|
+
let apiProvider = config.api_provider || '';
|
1363
|
+
|
1364
|
+
const providerInput = await question(colorize(` 请选择API提供商 [${apiProvider ? apiProvider : 'openai'}]: `, 'green'));
|
1365
|
+
if (providerInput.trim() !== '') {
|
1366
|
+
apiProvider = providerInput.trim();
|
1367
|
+
} else if (apiProvider === '') {
|
1368
|
+
apiProvider = 'openai'; // 默认值
|
1369
|
+
}
|
1370
|
+
|
1371
|
+
// 保存用户输入的提供商名称,不做验证
|
1372
|
+
config.api_provider = apiProvider.toLowerCase();
|
1373
|
+
console.log(colorize(` ✅ API提供商已设置为: ${config.api_provider}`, 'green'));
|
1374
|
+
|
1375
|
+
// 步骤2: 设置API基础URL
|
1376
|
+
console.log(colorize('\n🔗 步骤2: 设置API基础URL', 'yellow'));
|
1377
|
+
console.log(colorize(' (示例: https://api.openai.com, https://api.deepseek.com 或其他API服务地址)', 'cyan'));
|
1378
|
+
|
1379
|
+
// 根据提供商设置默认值
|
1380
|
+
let defaultBaseURL = config.api_base_url || '';
|
1381
|
+
if (!defaultBaseURL) {
|
1382
|
+
if (config.api_provider === 'openai') {
|
1383
|
+
defaultBaseURL = 'https://api.openai.com';
|
1384
|
+
} else if (config.api_provider === 'deepseek' || config.api_provider === 'ds') {
|
1385
|
+
defaultBaseURL = 'https://api.deepseek.com';
|
1386
|
+
}
|
1387
|
+
}
|
1388
|
+
|
1389
|
+
const baseURLInput = await question(colorize(` 请输入API基础URL [${defaultBaseURL}]: `, 'green'));
|
1390
|
+
config.api_base_url = baseURLInput.trim() || defaultBaseURL;
|
1391
|
+
console.log(colorize(` ✅ API基础URL已设置为: ${config.api_base_url}`, 'green'));
|
1392
|
+
|
1393
|
+
// 步骤3: 设置AI模型
|
1394
|
+
console.log(colorize('\n🤖 步骤3: 设置AI模型', 'yellow'));
|
1395
|
+
|
1396
|
+
// 根据提供商显示不同的模型示例
|
1397
|
+
const modelExamples = config.api_provider === 'openai' ?
|
1398
|
+
'gpt-3.5-turbo, gpt-4, gpt-4-turbo' :
|
1399
|
+
'deepseek-chat, deepseek-coder, deepseek-v3';
|
1400
|
+
console.log(colorize(` (常用模型示例: ${modelExamples})`, 'cyan'));
|
1401
|
+
|
1402
|
+
// 根据提供商设置默认模型
|
1403
|
+
let defaultModel = config.ai_model || '';
|
1404
|
+
if (!defaultModel) {
|
1405
|
+
if (config.api_provider === 'openai') {
|
1406
|
+
defaultModel = 'gpt-3.5-turbo';
|
1407
|
+
} else if (config.api_provider === 'deepseek' || config.api_provider === 'ds') {
|
1408
|
+
defaultModel = 'deepseek-chat';
|
1409
|
+
}
|
1410
|
+
}
|
1411
|
+
|
1412
|
+
const modelInput = await question(colorize(` 请输入AI模型名称 [${defaultModel}]: `, 'green'));
|
1413
|
+
config.ai_model = modelInput.trim() || defaultModel;
|
1414
|
+
console.log(colorize(` ✅ AI模型已设置为: ${config.ai_model}`, 'green'));
|
1415
|
+
|
1416
|
+
// 步骤4: 设置API密钥
|
1417
|
+
console.log(colorize('\n🔑 步骤4: 设置API密钥', 'yellow'));
|
1418
|
+
console.log(colorize(' (格式示例: sk-abcdefg123456789... 密钥会安全存储在本地配置文件中)', 'cyan'));
|
1419
|
+
const existingKey = config.api_key || '';
|
1420
|
+
const keyInput = await question(colorize(` 请输入API密钥${existingKey ? ' [已配置,按Enter保留]' : ''}: `, 'green'));
|
1421
|
+
if (keyInput.trim() !== '') {
|
1422
|
+
config.api_key = keyInput.trim();
|
1423
|
+
console.log(colorize(' ✅ API密钥已更新', 'green'));
|
1424
|
+
} else if (!existingKey) {
|
1425
|
+
console.log(colorize(' ⚠️ 警告: 未设置API密钥,某些功能可能无法使用。', 'yellow'));
|
1426
|
+
} else {
|
1427
|
+
console.log(colorize(' ℹ️ API密钥保持不变', 'blue'));
|
1428
|
+
}
|
1429
|
+
|
1430
|
+
// 步骤5: 设置默认作者
|
1431
|
+
console.log(colorize('\n👤 步骤5: 设置默认作者', 'yellow'));
|
1432
|
+
console.log(colorize(' (示例: 张三, user@example.com, 或Git提交时使用的用户名)', 'cyan'));
|
1433
|
+
const existingAuthor = config.default_author || '';
|
1434
|
+
const authorInput = await question(colorize(` 请输入默认作者名称 [${existingAuthor}]: `, 'green'));
|
1435
|
+
config.default_author = authorInput.trim() || existingAuthor;
|
1436
|
+
console.log(colorize(` ✅ 默认作者已设置为: ${config.default_author}`, 'green'));
|
1437
|
+
|
1438
|
+
// 步骤6: 设置默认时间范围(可选)
|
1439
|
+
console.log(colorize('\n🕒 步骤6: 设置默认时间范围(可选)', 'yellow'));
|
1440
|
+
console.log(colorize(' (支持格式: "7 days ago", "1 week ago", "yesterday", "2023-01-01", "last monday")', 'cyan'));
|
1441
|
+
|
1442
|
+
// 获取当前的默认值
|
1443
|
+
const defaultSince = config.default_since || '7 days ago';
|
1444
|
+
const defaultUntil = config.default_until || 'today';
|
1445
|
+
|
1446
|
+
const sinceInput = await question(colorize(` 请输入默认起始时间 [${defaultSince}]: `, 'green'));
|
1447
|
+
config.default_since = sinceInput.trim() || defaultSince;
|
1448
|
+
|
1449
|
+
const untilInput = await question(colorize(` 请输入默认结束时间 [${defaultUntil}]: `, 'green'));
|
1450
|
+
config.default_until = untilInput.trim() || defaultUntil;
|
1451
|
+
|
1452
|
+
console.log(colorize(` ✅ 默认时间范围已设置为: ${config.default_since} 至 ${config.default_until}`, 'green'));
|
1453
|
+
|
1454
|
+
// 步骤7: 仓库配置(可选)
|
1455
|
+
console.log(colorize('\n📂 步骤7: 仓库配置(可选)', 'yellow'));
|
1456
|
+
console.log(colorize(' (仓库别名示例: frontend, backend, main-project)', 'cyan'));
|
1457
|
+
|
1458
|
+
// 根据操作系统提供路径示例
|
1459
|
+
const repoPathExample = process.platform === 'win32' ?
|
1460
|
+
'C:\\项目\\前端仓库' :
|
1461
|
+
'/Users/用户名/projects/前端仓库';
|
1462
|
+
console.log(colorize(` (仓库路径示例: ${repoPathExample})`, 'cyan'));
|
1463
|
+
|
1464
|
+
// 显示当前配置的仓库
|
1465
|
+
const repos = config.repositories || {};
|
1466
|
+
if (Object.keys(repos).length > 0) {
|
1467
|
+
console.log(colorize(' 当前配置的仓库:', 'cyan'));
|
1468
|
+
let index = 1;
|
1469
|
+
for (const [name, path] of Object.entries(repos)) {
|
1470
|
+
console.log(colorize(` ${index++}. ${name}: ${path}`, 'reset'));
|
1471
|
+
}
|
1472
|
+
} else {
|
1473
|
+
console.log(colorize(' 当前没有配置的仓库', 'cyan'));
|
1474
|
+
}
|
1475
|
+
|
1476
|
+
// 询问是否添加仓库
|
1477
|
+
let addRepo = true;
|
1478
|
+
while (addRepo) {
|
1479
|
+
const addRepoInput = await question(colorize(' 是否添加仓库配置?(y/n): ', 'green'));
|
1480
|
+
if (addRepoInput.toLowerCase() === 'y' || addRepoInput.toLowerCase() === 'yes') {
|
1481
|
+
// 获取仓库别名
|
1482
|
+
const repoName = await question(colorize(' 请输入仓库别名(如 frontend): ', 'green'));
|
1483
|
+
if (!repoName.trim()) {
|
1484
|
+
console.log(colorize(' ❌ 仓库别名不能为空', 'red'));
|
1485
|
+
continue;
|
1486
|
+
}
|
1487
|
+
|
1488
|
+
// 获取仓库路径,添加示例提示
|
1489
|
+
console.log(colorize(` (请输入Git仓库的绝对路径,示例: ${repoPathExample})`, 'cyan'));
|
1490
|
+
const repoPath = await question(colorize(' 请输入仓库路径(绝对路径): ', 'green'));
|
1491
|
+
if (!repoPath.trim()) {
|
1492
|
+
console.log(colorize(' ❌ 仓库路径不能为空', 'red'));
|
1493
|
+
continue;
|
1494
|
+
}
|
1495
|
+
|
1496
|
+
// 验证路径是否为有效的Git仓库
|
1497
|
+
try {
|
1498
|
+
const pathSpinner = spinner.start(' 🔍 正在验证仓库路径...');
|
1499
|
+
execSync(`git -C "${repoPath}" rev-parse --is-inside-work-tree`, { stdio: 'ignore' });
|
1500
|
+
pathSpinner.stop(' ✅ 仓库路径有效');
|
1501
|
+
|
1502
|
+
// 添加仓库配置
|
1503
|
+
if (!config.repositories) config.repositories = {};
|
1504
|
+
config.repositories[repoName.trim()] = repoPath.trim();
|
1505
|
+
console.log(colorize(` ✅ 已添加仓库: ${repoName.trim()} -> ${repoPath.trim()}`, 'green'));
|
1506
|
+
} catch (error) {
|
1507
|
+
console.log(colorize(` ❌ 路径 "${repoPath}" 不是有效的Git仓库`, 'red'));
|
1508
|
+
}
|
1509
|
+
} else {
|
1510
|
+
addRepo = false;
|
1511
|
+
}
|
1512
|
+
}
|
1513
|
+
|
1514
|
+
// 保存配置
|
1515
|
+
const configDir = path.dirname(CONFIG_PATH);
|
1516
|
+
if (!fs.existsSync(configDir)) {
|
1517
|
+
fs.mkdirSync(configDir, { recursive: true });
|
1518
|
+
}
|
1519
|
+
|
1520
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
1521
|
+
console.log(colorize(`\n✅ 配置已保存到: ${CONFIG_PATH}`, 'bright'));
|
1522
|
+
console.log(colorize('现在您可以使用g2log工具了!默认会从当前时间查询过去7天的记录。', 'bright'));
|
1523
|
+
console.log(colorize('使用 --since 和 --until 参数可以指定不同的时间范围。', 'bright'));
|
1524
|
+
|
1525
|
+
} catch (error) {
|
1526
|
+
console.error(colorize(`\n❌ 配置过程出错: ${error.message}`, 'red'));
|
1527
|
+
} finally {
|
1528
|
+
rl.close();
|
1529
|
+
}
|
1530
|
+
}
|
1531
|
+
|
1532
|
+
// 主函数
|
1533
|
+
async function getGitLogs() {
|
1534
|
+
const args = parseArgs();
|
1535
|
+
|
1536
|
+
// 显示帮助信息
|
1537
|
+
if (args['help']) {
|
1538
|
+
showHelp();
|
1539
|
+
return;
|
1540
|
+
}
|
1541
|
+
|
1542
|
+
// 显示版本信息
|
1543
|
+
if (args['version']) {
|
1544
|
+
const packageJson = require('./package.json');
|
1545
|
+
console.log(`g2log version ${packageJson.version}`);
|
1546
|
+
return;
|
1547
|
+
}
|
1548
|
+
|
1549
|
+
// 删除配置文件
|
1550
|
+
if (args['uninstall']) {
|
1551
|
+
const spinner = ora('正在删除配置文件...').start();
|
1552
|
+
const success = removeConfigFile();
|
1553
|
+
if (success) {
|
1554
|
+
spinner.succeed('配置文件已删除,如需完全卸载请运行: npm uninstall -g g2log');
|
1555
|
+
} else {
|
1556
|
+
spinner.fail('配置文件删除失败,可能文件不存在或无权限访问');
|
1557
|
+
}
|
1558
|
+
return;
|
1559
|
+
}
|
1560
|
+
|
1561
|
+
try {
|
1562
|
+
const spinner = createSpinner();
|
1563
|
+
|
1564
|
+
// 检查是否要显示自定义配置向导
|
1565
|
+
if (args.config) {
|
1566
|
+
console.log(colorize('🔧 启动配置向导...', 'cyan'));
|
1567
|
+
await setupConfigInteractive();
|
1568
|
+
return;
|
1569
|
+
}
|
1570
|
+
|
1571
|
+
// 配置检查与向导(仅当不是特定的配置命令时)
|
1572
|
+
if (!args['set-api-key'] && !args['set-default-author'] && !args['add-repo'] &&
|
1573
|
+
!args['fix-config'] && !args['remove-repo'] && !args['list-repos'] &&
|
1574
|
+
!args['set-prompt-template'] && !args['reset-prompt-template'] &&
|
1575
|
+
!args['skip-config-check'] && !args['uninstall']) {
|
1576
|
+
|
1577
|
+
// 检查配置状态
|
1578
|
+
const configStatus = checkConfig();
|
1579
|
+
|
1580
|
+
// 如果配置缺失
|
1581
|
+
if (configStatus.needsConfig) {
|
1582
|
+
if (isRunningWithNpx || !fs.existsSync(CONFIG_PATH)) {
|
1583
|
+
// 对于NPX运行或首次使用(无配置文件),显示提示并询问是否配置
|
1584
|
+
console.log(colorize('\n⚠️ 检测到配置缺失: ' + configStatus.reason, 'yellow'));
|
1585
|
+
if (configStatus.missingConfig.includes('default_author')) {
|
1586
|
+
console.log(colorize('❗ 必须设置默认作者才能使用此工具。', 'red'));
|
1587
|
+
}
|
1588
|
+
|
1589
|
+
// 创建readline接口进行简单询问
|
1590
|
+
const rl = readline.createInterface({
|
1591
|
+
input: process.stdin,
|
1592
|
+
output: process.stdout
|
1593
|
+
});
|
1594
|
+
|
1595
|
+
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
1596
|
+
const answer = await question(colorize('❓ 是否现在进行配置?(y/n): ', 'cyan'));
|
1597
|
+
rl.close();
|
1598
|
+
|
1599
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
1600
|
+
// 启动配置向导
|
1601
|
+
await setupConfigInteractive();
|
1602
|
+
// 配置完成后,重新加载配置
|
1603
|
+
const config = loadConfig();
|
1604
|
+
|
1605
|
+
// 如果依然缺少必要配置项,提示并退出
|
1606
|
+
if (!config.default_author || config.default_author === '') {
|
1607
|
+
console.log(colorize('\n❌ 错误: 未设置默认作者,这是必需的。', 'red'));
|
1608
|
+
console.log(colorize('💡 请使用 g2log --set-default-author="用户名" 进行设置后再试。', 'yellow'));
|
1609
|
+
process.exit(1);
|
1610
|
+
}
|
1611
|
+
} else if (configStatus.missingConfig.includes('default_author')) {
|
1612
|
+
// 如果用户拒绝配置且缺少必要的default_author,提示并退出
|
1613
|
+
console.log(colorize('\n❌ 错误: 未设置默认作者,这是必需的。', 'red'));
|
1614
|
+
console.log(colorize('💡 请使用 g2log --set-default-author="用户名" 进行设置后再试。', 'yellow'));
|
1615
|
+
process.exit(1);
|
1616
|
+
}
|
1617
|
+
} else if (configStatus.missingConfig.includes('default_author')) {
|
1618
|
+
// 对于非NPX运行但缺少必要default_author的情况,直接错误提示
|
1619
|
+
console.error(colorize('❌ 错误: 配置文件中未设置默认作者。请使用 --set-default-author="用户名" 设置默认作者', 'red'));
|
1620
|
+
process.exit(1);
|
1621
|
+
}
|
1622
|
+
}
|
1623
|
+
}
|
1624
|
+
|
1625
|
+
// 加载配置(在配置检查和可能的设置之后)
|
1626
|
+
const config = loadConfig();
|
1627
|
+
|
1628
|
+
// 修复配置文件
|
1629
|
+
if (args['fix-config']) {
|
1630
|
+
const fixSpinner = spinner.start('🔧 正在修复配置文件...');
|
1631
|
+
if (fixConfigFile()) {
|
1632
|
+
fixSpinner.stop('✅ 配置文件已修复');
|
1633
|
+
} else {
|
1634
|
+
fixSpinner.fail('❌ 配置文件修复失败');
|
1635
|
+
}
|
1636
|
+
return;
|
1637
|
+
}
|
1638
|
+
|
1639
|
+
// 配置管理
|
1640
|
+
if (args['set-api-key']) {
|
1641
|
+
const keySpinner = spinner.start('🔑 正在设置API密钥...');
|
1642
|
+
if (setApiKey(args['set-api-key'])) {
|
1643
|
+
keySpinner.stop('✅ API密钥设置成功');
|
1644
|
+
} else {
|
1645
|
+
keySpinner.fail('❌ API密钥设置失败');
|
1646
|
+
}
|
1647
|
+
return;
|
1648
|
+
}
|
1649
|
+
|
1650
|
+
if (args['set-api-provider']) {
|
1651
|
+
const providerSpinner = spinner.start('🎨 正在设置API提供商...');
|
1652
|
+
if (setAPIProvider(args['set-api-provider'])) {
|
1653
|
+
providerSpinner.stop(`✅ API提供商已设置为: ${args['set-api-provider']}`);
|
1654
|
+
} else {
|
1655
|
+
providerSpinner.fail('❌ API提供商设置失败');
|
1656
|
+
}
|
1657
|
+
return;
|
1658
|
+
}
|
1659
|
+
|
1660
|
+
if (args['set-api-url']) {
|
1661
|
+
const urlSpinner = spinner.start('🔗 正在设置API基础URL...');
|
1662
|
+
if (setAPIBaseURL(args['set-api-url'])) {
|
1663
|
+
urlSpinner.stop(`✅ API基础URL已设置为: ${args['set-api-url']}`);
|
1664
|
+
} else {
|
1665
|
+
urlSpinner.fail('❌ API基础URL设置失败');
|
1666
|
+
}
|
1667
|
+
return;
|
1668
|
+
}
|
1669
|
+
|
1670
|
+
if (args['set-ai-model']) {
|
1671
|
+
const modelSpinner = spinner.start('🤖 正在设置AI模型...');
|
1672
|
+
if (setAIModel(args['set-ai-model'])) {
|
1673
|
+
modelSpinner.stop(`✅ AI模型已设置为: ${args['set-ai-model']}`);
|
1674
|
+
} else {
|
1675
|
+
modelSpinner.fail('❌ AI模型设置失败');
|
1676
|
+
}
|
1677
|
+
return;
|
1678
|
+
}
|
1679
|
+
|
1680
|
+
if (args['set-default-author']) {
|
1681
|
+
const authorSpinner = spinner.start('👤 正在设置默认作者...');
|
1682
|
+
if (setDefaultAuthor(args['set-default-author'])) {
|
1683
|
+
authorSpinner.stop(`✅ 默认作者已设置为: ${args['set-default-author']}`);
|
1684
|
+
} else {
|
1685
|
+
authorSpinner.fail('❌ 默认作者设置失败');
|
1686
|
+
}
|
1687
|
+
return;
|
1688
|
+
}
|
1689
|
+
|
1690
|
+
if (args['set-time-range']) {
|
1691
|
+
const timeSpinner = spinner.start('🕒 正在设置默认时间范围...');
|
1692
|
+
if (setDefaultTimeRange(args.since, args.until)) {
|
1693
|
+
timeSpinner.stop(`✅ 默认时间范围已设置为: ${args.since || '(未更改)'} 至 ${args.until || '(未更改)'}`);
|
1694
|
+
} else {
|
1695
|
+
timeSpinner.fail('❌ 默认时间范围设置失败');
|
1696
|
+
}
|
1697
|
+
return;
|
1698
|
+
}
|
1699
|
+
|
1700
|
+
if (args['add-repo'] && args.path) {
|
1701
|
+
const repoSpinner = spinner.start(`🔖 正在添加仓库配置: ${args['add-repo']} -> ${args.path}`);
|
1702
|
+
if (addRepository(args['add-repo'], args.path)) {
|
1703
|
+
repoSpinner.stop('✅ 仓库配置已添加');
|
1704
|
+
} else {
|
1705
|
+
repoSpinner.fail('❌ 仓库配置添加失败');
|
1706
|
+
}
|
1707
|
+
return;
|
1708
|
+
}
|
1709
|
+
|
1710
|
+
if (args['remove-repo']) {
|
1711
|
+
const repoSpinner = spinner.start(`🗑️ 正在删除仓库配置: ${args['remove-repo']}`);
|
1712
|
+
if (removeRepository(args['remove-repo'])) {
|
1713
|
+
repoSpinner.stop('✅ 仓库配置已删除');
|
1714
|
+
} else {
|
1715
|
+
repoSpinner.fail('❌ 仓库配置删除失败或不存在');
|
1716
|
+
}
|
1717
|
+
return;
|
1718
|
+
}
|
1719
|
+
|
1720
|
+
if (args['list-repos']) {
|
1721
|
+
const repos = listRepositories();
|
1722
|
+
console.log(`\n${colorize('配置的仓库:', 'bright')}\n`);
|
1723
|
+
|
1724
|
+
if (Object.keys(repos).length === 0) {
|
1725
|
+
console.log(colorize(' 没有配置任何仓库', 'yellow'));
|
1726
|
+
} else {
|
1727
|
+
for (const [alias, repoPath] of Object.entries(repos)) {
|
1728
|
+
console.log(` ${colorize(alias, 'green')}: ${repoPath}`);
|
1729
|
+
}
|
1730
|
+
}
|
1731
|
+
|
1732
|
+
console.log('');
|
1733
|
+
return;
|
1734
|
+
}
|
1735
|
+
|
1736
|
+
// 重置prompt模板
|
1737
|
+
if (args['reset-prompt-template']) {
|
1738
|
+
const promptSpinner = spinner.start('🔄 正在重置prompt模板...');
|
1739
|
+
if (resetPromptTemplate()) {
|
1740
|
+
promptSpinner.stop('✅ Prompt模板已重置为默认值');
|
1741
|
+
} else {
|
1742
|
+
promptSpinner.fail('❌ Prompt模板重置失败');
|
1743
|
+
}
|
1744
|
+
return;
|
1745
|
+
}
|
1746
|
+
|
1747
|
+
// 添加设置prompt模板的功能
|
1748
|
+
if (args['set-prompt-template']) {
|
1749
|
+
const templatePath = args['set-prompt-template'];
|
1750
|
+
try {
|
1751
|
+
const promptSpinner = spinner.start(`📄 正在读取prompt模板文件: ${templatePath}`);
|
1752
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
1753
|
+
if (setPromptTemplate(templateContent)) {
|
1754
|
+
promptSpinner.stop(`✅ Prompt模板已更新`);
|
1755
|
+
} else {
|
1756
|
+
promptSpinner.fail(`❌ Prompt模板更新失败`);
|
1757
|
+
}
|
1758
|
+
return;
|
1759
|
+
} catch (error) {
|
1760
|
+
console.error(colorize(`读取模板文件失败: ${error.message}`, 'red'));
|
1761
|
+
process.exit(1);
|
1762
|
+
}
|
1763
|
+
}
|
1764
|
+
|
1765
|
+
// 显示NPX运行信息
|
1766
|
+
showNpxInfo();
|
1767
|
+
|
1768
|
+
// 使用参数值或默认配置
|
1769
|
+
const useLocalRepo = args.local === true;
|
1770
|
+
const author = config.default_author;
|
1771
|
+
const since = args.since || config.default_since;
|
1772
|
+
const until = args.until || config.default_until;
|
1773
|
+
|
1774
|
+
// 其他参数从配置文件获取
|
1775
|
+
const simpleMode = true; // 总是使用简单模式
|
1776
|
+
const aiSummary = true; // 总是使用AI总结
|
1777
|
+
const outputFile = args.output;
|
1778
|
+
|
1779
|
+
// 参数验证
|
1780
|
+
if (!author) {
|
1781
|
+
console.error(colorize('错误: 配置文件中未设置默认作者。请使用 --set-default-author="用户名" 设置默认作者', 'red'));
|
1782
|
+
process.exit(1);
|
1783
|
+
}
|
1784
|
+
|
1785
|
+
// 多仓库处理 - 如果不是--local模式,尝试处理配置中的所有仓库
|
1786
|
+
if (!useLocalRepo) {
|
1787
|
+
const multiRepoOptions = {
|
1788
|
+
noMerges: true,
|
1789
|
+
simpleMode: true
|
1790
|
+
};
|
1791
|
+
const multiRepoLogs = await getLogsFromMultipleRepos(author, since, until, multiRepoOptions);
|
1792
|
+
|
1793
|
+
// 如果有多仓库日志结果
|
1794
|
+
if (multiRepoLogs) {
|
1795
|
+
if (multiRepoLogs.trim() === '') {
|
1796
|
+
console.log(colorize(`📭 在所有配置的仓库中未找到 ${author} 在 ${since} 至 ${until} 期间的提交记录。`, 'yellow'));
|
1797
|
+
return;
|
1798
|
+
}
|
1799
|
+
|
1800
|
+
// 生成AI总结
|
1801
|
+
try {
|
1802
|
+
const summarySpinner = spinner.start('🧠 正在总结所有仓库的提交记录...');
|
1803
|
+
|
1804
|
+
// 直接调用带spinner参数的summarizeWithAI函数
|
1805
|
+
const aiResult = await summarizeWithAI(multiRepoLogs, author, since, until, summarySpinner);
|
1806
|
+
|
1807
|
+
// 如果指定了输出文件,保存AI总结结果
|
1808
|
+
if (outputFile) {
|
1809
|
+
const fileSpinner = spinner.start(`💾 正在保存多仓库AI总结到文件: ${outputFile}`);
|
1810
|
+
fs.writeFileSync(outputFile, `# 📊 ${author} 的多仓库工作总结 (${since} 至 ${until})\n\n${aiResult}`, 'utf-8');
|
1811
|
+
fileSpinner.stop(`✅ 多仓库AI总结已保存到文件: ${outputFile}`);
|
1812
|
+
}
|
1813
|
+
return;
|
1814
|
+
} catch (error) {
|
1815
|
+
console.error(colorize(`❌ AI总结失败: ${error.message}`, 'red'));
|
1816
|
+
}
|
1817
|
+
return;
|
1818
|
+
}
|
1819
|
+
}
|
1820
|
+
|
1821
|
+
// 单仓库处理逻辑 - 当使用local模式或没有配置多个仓库时
|
1822
|
+
const repoPath = useLocalRepo ? process.cwd() : Object.values(config.repositories)[0] || process.cwd();
|
1823
|
+
|
1824
|
+
// 检查仓库路径是否有效
|
1825
|
+
try {
|
1826
|
+
const pathSpinner = spinner.start(`🔍 检查仓库路径: ${repoPath}`);
|
1827
|
+
execSync(`git -C "${repoPath}" rev-parse --is-inside-work-tree`, { stdio: 'ignore' });
|
1828
|
+
pathSpinner.stop(`✅ 仓库路径有效: ${repoPath}`);
|
1829
|
+
} catch (error) {
|
1830
|
+
console.error(colorize(`❌ 错误: 指定的路径 "${repoPath}" 不是有效的Git仓库`, 'red'));
|
1831
|
+
process.exit(1);
|
1832
|
+
}
|
1833
|
+
|
1834
|
+
// 获取简化格式的日志
|
1835
|
+
const logSpinner = spinner.start(`🔍 正在获取 ${author} 在 ${since} 至 ${until} 期间的提交记录...`);
|
1836
|
+
const simpleCommand = `git -C "${repoPath}" log --author="${author}" --since="${since}" --until="${until}" --pretty=format:"%ad: %s%n%b%n" --date=format:"%Y-%m-%d %H:%M:%S" --no-merges`;
|
1837
|
+
|
1838
|
+
try {
|
1839
|
+
const result = execSync(simpleCommand, { encoding: 'utf-8' });
|
1840
|
+
logSpinner.stop(`✅ 找到提交记录`);
|
1841
|
+
|
1842
|
+
if (!result.trim()) {
|
1843
|
+
const message = `📭 在指定时间范围内没有找到 ${author} 的提交记录。`;
|
1844
|
+
console.log(colorize(message, 'yellow'));
|
1845
|
+
|
1846
|
+
if (outputFile) {
|
1847
|
+
fs.writeFileSync(outputFile, message, 'utf-8');
|
1848
|
+
console.log(colorize(`💾 结果已保存到文件: ${outputFile}`, 'green'));
|
1849
|
+
}
|
1850
|
+
|
1851
|
+
return;
|
1852
|
+
}
|
1853
|
+
|
1854
|
+
// 生成AI总结
|
1855
|
+
try {
|
1856
|
+
const summarySpinner = spinner.start('🧠 正在总结提交记录...');
|
1857
|
+
|
1858
|
+
// 直接调用带spinner参数的summarizeWithAI函数
|
1859
|
+
const aiSummaryResult = await summarizeWithAI(result, author, since, until, summarySpinner);
|
1860
|
+
|
1861
|
+
// 如果指定了输出文件,保存AI总结结果
|
1862
|
+
if (outputFile) {
|
1863
|
+
const fileSpinner = spinner.start(`💾 正在保存AI总结到文件: ${outputFile}`);
|
1864
|
+
fs.writeFileSync(outputFile, `# ${author} 的工作总结 (${since} 至 ${until})\n\n${aiSummaryResult}`, 'utf-8');
|
1865
|
+
fileSpinner.stop(`✅ AI总结已保存到文件: ${outputFile}`);
|
1866
|
+
return;
|
1867
|
+
}
|
1868
|
+
} catch (error) {
|
1869
|
+
console.error(colorize(`❌ AI总结失败: ${error.message}`, 'red'));
|
1870
|
+
// 如果AI总结失败,输出原始日志
|
1871
|
+
console.log(`\n📋 ${author} 的Git提交日志 (${since} 至 ${until})\n`);
|
1872
|
+
console.log(result);
|
1873
|
+
|
1874
|
+
// 如果指定了输出文件,保存结果
|
1875
|
+
if (outputFile) {
|
1876
|
+
const fileSpinner = spinner.start(`💾 正在保存结果到文件: ${outputFile}`);
|
1877
|
+
const outputContent = `# ${author} 的Git提交日志 (${since} 至 ${until})\n\n${result}`;
|
1878
|
+
fs.writeFileSync(outputFile, outputContent, 'utf-8');
|
1879
|
+
fileSpinner.stop(`✅ 结果已保存到文件: ${outputFile}`);
|
1880
|
+
}
|
1881
|
+
}
|
1882
|
+
} catch (error) {
|
1883
|
+
logSpinner.fail(`❌ 获取提交记录失败: ${error.message}`);
|
1884
|
+
process.exit(1);
|
1885
|
+
}
|
1886
|
+
} catch (error) {
|
1887
|
+
console.error(colorize('❌ 执行出错:', 'red'), error.message);
|
1888
|
+
process.exit(1);
|
1889
|
+
}
|
1890
|
+
}
|
1891
|
+
|
1892
|
+
// 执行主函数
|
1893
|
+
getGitLogs();
|
1894
|
+
|
1895
|
+
// 如果直接调用setupConfigInteractive进行测试(需要注释掉主函数调用)
|
1896
|
+
// setupConfigInteractive(['api_key', 'default_author', 'repositories'])
|
1897
|
+
// .then(() => console.log('配置测试已完成'));
|
1898
|
+
|
1899
|
+
// 如果需要测试checkConfig(需要注释掉主函数调用)
|
1900
|
+
// console.log(checkConfig());
|
1901
|
+
|
1902
|
+
// 重置prompt模板为默认值
|
1903
|
+
function resetPromptTemplate() {
|
1904
|
+
try {
|
1905
|
+
const config = loadConfig();
|
1906
|
+
if (config.prompt_template) {
|
1907
|
+
delete config.prompt_template;
|
1908
|
+
return saveConfig(config);
|
1909
|
+
}
|
1910
|
+
return true; // 如果没有设置自定义模板,则视为重置成功
|
1911
|
+
} catch (error) {
|
1912
|
+
console.error(`❌ 重置prompt模板失败: ${error.message}`);
|
1913
|
+
return false;
|
1914
|
+
}
|
1915
|
+
}
|
1916
|
+
|
1917
|
+
// 删除配置文件
|
1918
|
+
function removeConfigFile() {
|
1919
|
+
try {
|
1920
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
1921
|
+
fs.unlinkSync(CONFIG_PATH);
|
1922
|
+
return true;
|
1923
|
+
}
|
1924
|
+
return false; // 文件不存在
|
1925
|
+
} catch (error) {
|
1926
|
+
console.error(`❌ 删除配置文件失败: ${error.message}`);
|
1927
|
+
return false;
|
1928
|
+
}
|
1929
|
+
}
|
1930
|
+
|
1931
|
+
// 设置API提供商
|
1932
|
+
function setAPIProvider(provider) {
|
1933
|
+
try {
|
1934
|
+
const config = loadConfig();
|
1935
|
+
config.api_provider = provider;
|
1936
|
+
return saveConfig(config);
|
1937
|
+
} catch (error) {
|
1938
|
+
console.error(`❌ 设置API提供商失败: ${error.message}`);
|
1939
|
+
return false;
|
1940
|
+
}
|
1846
1941
|
}
|