git-coco 0.3.1 → 0.3.3

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/dist/index.js CHANGED
@@ -2,13 +2,14 @@
2
2
  'use strict';
3
3
 
4
4
  var yargs = require('yargs');
5
- var prompts$1 = require('@inquirer/prompts');
6
5
  var simpleGit = require('simple-git');
7
6
  var fs = require('fs');
8
7
  var os = require('os');
9
8
  var path = require('path');
10
9
  var ini = require('ini');
11
10
  var prompts = require('langchain/prompts');
11
+ var chalk = require('chalk');
12
+ var prompts$1 = require('@inquirer/prompts');
12
13
  var pQueue = require('p-queue');
13
14
  var document = require('langchain/document');
14
15
  var hf = require('langchain/llms/hf');
@@ -16,12 +17,11 @@ var chains = require('langchain/chains');
16
17
  var openai = require('langchain/llms/openai');
17
18
  var text_splitter = require('langchain/text_splitter');
18
19
  var diff = require('diff');
19
- var chalk = require('chalk');
20
+ var minimatch = require('minimatch');
20
21
  var GPT3NodeTokenizer = require('gpt3-tokenizer');
21
22
  var ora = require('ora');
22
23
  var now = require('performance-now');
23
24
  var prettyMilliseconds = require('pretty-ms');
24
- var minimatch = require('minimatch');
25
25
 
26
26
  function _interopNamespaceDefault(e) {
27
27
  var n = Object.create(null);
@@ -174,11 +174,13 @@ function loadXDGConfig(config) {
174
174
  return config;
175
175
  }
176
176
 
177
- const template$1 = `Write informative git commit message based on the diffs & file changes provided in the "Diff Summary" section.
177
+ const template$1 = `Write informative git commit message, in the imperative, based on the diffs & file changes provided in the "Diff Summary" section.
178
178
  Commit Messages must have a short description that is less than 50 characters followed by a newline character and then a more verbose detailed description.
179
+
180
+ - Typically a hyphen or asterisk is used for the bullet
179
181
  - Write concisely using an informal tone
180
- - List significant changes
181
- - DO NOT use phrases like "this commit", "this change", etc.
182
+ - DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
183
+ - DO NOT use phrases like "this commit", "this change", "this function", etc. Instead refer to the function, variable, or class by name
182
184
  - DO NOT use specific names or files from the code
183
185
  - Wrap variable, class, function, components, and dependency names in back ticks e.g. \`variable\`
184
186
 
@@ -209,7 +211,7 @@ const SUMMARIZE_PROMPT = new prompts.PromptTemplate({
209
211
  * @type {Config}
210
212
  */
211
213
  const DEFAULT_CONFIG = {
212
- model: 'openai/gpt-3.5-turbo',
214
+ model: 'openai/gpt-4',
213
215
  verbose: false,
214
216
  tokenLimit: 1024,
215
217
  prompt: COMMIT_PROMPT.template,
@@ -394,7 +396,7 @@ const createDiffTree = (changes) => {
394
396
  const root = new DiffTreeNode();
395
397
  for (const change of changes) {
396
398
  let currentParent = root;
397
- const parts = change.filepath.split('/');
399
+ const parts = change.filePath.split('/');
398
400
  parts.pop();
399
401
  for (const part of parts) {
400
402
  let childNode = currentParent.getChild(part);
@@ -406,8 +408,8 @@ const createDiffTree = (changes) => {
406
408
  }
407
409
  // Create a NodeFile object and add it to the parent
408
410
  currentParent.addFile({
409
- filepath: change.filepath,
410
- oldFilepath: change.oldFilepath,
411
+ filePath: change.filePath,
412
+ oldFilePath: change.oldFilePath,
411
413
  summary: change.summary,
412
414
  status: change.status,
413
415
  });
@@ -425,11 +427,11 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger) {
425
427
  // TODO: Swap out the GPT3Tokenizer for LangChain tokenizer
426
428
  const tokenizedDiff = tokenizer.encode(diff).text;
427
429
  const tokenCount = tokenizedDiff.length;
428
- logger.verbose(`Collected diff for ${nodeFile.filepath} (${tokenCount} tokens)`, {
430
+ logger.verbose(`Collected diff for ${nodeFile.filePath} (${tokenCount} tokens)`, {
429
431
  color: 'magenta',
430
432
  });
431
433
  return {
432
- file: nodeFile.filepath,
434
+ file: nodeFile.filePath,
433
435
  summary: nodeFile.summary,
434
436
  diff,
435
437
  tokenCount,
@@ -454,7 +456,7 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger) {
454
456
  * @param configuration
455
457
  * @returns LLM Model
456
458
  */
457
- function getModel(name, key, fields, configuration) {
459
+ function getModel(name, key, fields) {
458
460
  const [llm, model] = name.split(/\/(.*)/s);
459
461
  if (!model) {
460
462
  throw new Error(`Invalid model: ${name}`);
@@ -473,7 +475,7 @@ function getModel(name, key, fields, configuration) {
473
475
  openAIApiKey: key,
474
476
  modelName: model,
475
477
  ...fields,
476
- }, configuration);
478
+ });
477
479
  }
478
480
  }
479
481
  /**
@@ -540,18 +542,18 @@ function validatePromptTemplate(text, inputVariables) {
540
542
  }
541
543
 
542
544
  const parseDefaultFileDiff = async (nodeFile, git) => {
543
- return await git.diff(['--staged', nodeFile.filepath]);
545
+ return await git.diff(['--staged', nodeFile.filePath]);
544
546
  };
545
547
  const parseRenamedFileDiff = async (nodeFile, git, logger) => {
546
548
  let result = '';
547
- const oldFilepath = nodeFile?.oldFilepath || nodeFile.filepath;
549
+ const oldFilePath = nodeFile?.oldFilePath || nodeFile.filePath;
548
550
  try {
549
551
  const [headContent, indexContent] = await Promise.all([
550
- git.show([`HEAD:${oldFilepath}`]),
551
- git.show([`:${nodeFile.filepath}`]),
552
+ git.show([`HEAD:${oldFilePath}`]),
553
+ git.show([`:${nodeFile.filePath}`]),
552
554
  ]);
553
555
  if (headContent !== indexContent) {
554
- result = diff.createTwoFilesPatch(oldFilepath, nodeFile.filepath, headContent, indexContent, '', '', {
556
+ result = diff.createTwoFilesPatch(oldFilePath, nodeFile.filePath, headContent, indexContent, '', '', {
555
557
  context: 3,
556
558
  });
557
559
  // remove the first 4 lines of the patch (they contain the old and new file names)
@@ -562,7 +564,7 @@ const parseRenamedFileDiff = async (nodeFile, git, logger) => {
562
564
  }
563
565
  }
564
566
  catch (err) {
565
- logger.verbose(`Error comparing file contents for ${nodeFile.filepath}`, { color: 'red' });
567
+ logger.verbose(`Error comparing file contents for ${nodeFile.filePath}`, { color: 'red' });
566
568
  result = 'Error comparing file contents.';
567
569
  }
568
570
  return result;
@@ -571,7 +573,7 @@ const getDiff = async (nodeFile, { git, logger, }) => {
571
573
  if (nodeFile.status === 'deleted') {
572
574
  return 'This file has been deleted.';
573
575
  }
574
- if (nodeFile.status === 'renamed' && nodeFile.oldFilepath) {
576
+ if (nodeFile.status === 'renamed' && nodeFile.oldFilePath) {
575
577
  const renamedDiff = await parseRenamedFileDiff(nodeFile, git, logger);
576
578
  return renamedDiff;
577
579
  }
@@ -608,87 +610,6 @@ const fileChangeParser = async (changes, { tokenizer, git, model, logger }) => {
608
610
  return summary;
609
611
  };
610
612
 
611
- const SEPERATOR = chalk.blue('----------------');
612
- const logCommit = (commit) => {
613
- console.log(`\n${chalk.bgBlue(chalk.bold('Proposed Commit:'))}\n${SEPERATOR}\n${commit}\n${SEPERATOR}\n`);
614
- };
615
- const logSuccess = () => {
616
- console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
617
- };
618
-
619
- /**
620
- * Wrapper around GPT3NodeTokenizer to handle default export.
621
- *
622
- * @see https://github.com/botisan-ai/gpt3-tokenizer/issues/18
623
- *
624
- * @returns {GPT3NodeTokenizer} The GPT3NodeTokenizer instance.
625
- */
626
- const getTokenizer = () => {
627
- let tokenizer;
628
- // eslint-disable-next-line
629
- // @ts-ignore
630
- if (GPT3NodeTokenizer.default) {
631
- // eslint-disable-next-line
632
- // @ts-ignore
633
- tokenizer = new GPT3NodeTokenizer.default({ type: 'gpt3' });
634
- }
635
- else {
636
- tokenizer = new GPT3NodeTokenizer({ type: 'gpt3' });
637
- }
638
- return tokenizer;
639
- };
640
-
641
- class Logger {
642
- constructor(config) {
643
- this.config = config;
644
- this.spinner = null;
645
- }
646
- log(message, options = { color: 'blue' }) {
647
- let outputMessage = message;
648
- if (options.color) {
649
- outputMessage = chalk[options.color](outputMessage);
650
- }
651
- console.log(outputMessage);
652
- return this;
653
- }
654
- verbose(message, options = {}) {
655
- if (!this.config?.verbose) {
656
- return this;
657
- }
658
- this.log(message, options);
659
- return this;
660
- }
661
- startTimer() {
662
- this.timerStart = now();
663
- return this;
664
- }
665
- stopTimer(message, options = { color: 'yellow' }) {
666
- if (!this.config?.verbose || !this.timerStart) {
667
- return this;
668
- }
669
- const elapsedTime = prettyMilliseconds(now() - this.timerStart);
670
- let outputMessage = message
671
- ? `${message} (⏲ ${elapsedTime})`
672
- : `⏲ ${elapsedTime}`;
673
- if (options.color) {
674
- outputMessage = chalk[options.color](outputMessage);
675
- }
676
- console.log(outputMessage);
677
- return this;
678
- }
679
- startSpinner(message, options = { color: 'green' }) {
680
- const spinnerMessage = options.color ? chalk[options.color](message) : message;
681
- this.spinner = ora(spinnerMessage).start();
682
- return this;
683
- }
684
- stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
685
- const spinnerMessage = options?.color ? chalk[options.color](message) : message;
686
- this.spinner?.[options.mode || 'succeed'](spinnerMessage);
687
- this.spinner = null;
688
- return this;
689
- }
690
- }
691
-
692
613
  const llm = async ({ llm, prompt, variables }) => {
693
614
  if (!llm || !prompt || !variables) {
694
615
  throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
@@ -713,52 +634,72 @@ const llm = async ({ llm, prompt, variables }) => {
713
634
  };
714
635
 
715
636
  const getStatus = (file, location = 'index') => {
716
- const statusCode = file[location] ? file[location] : file.index;
717
- let status;
718
- switch (statusCode) {
719
- case 'A':
720
- status = 'added';
721
- break;
722
- case 'D':
723
- status = 'deleted';
724
- break;
725
- case 'M':
726
- status = 'modified';
727
- break;
728
- case 'R':
729
- status = 'renamed';
730
- break;
731
- case '?':
732
- status = 'untracked';
733
- break;
734
- default:
735
- status = 'unknown';
736
- break;
637
+ if ('index' in file && 'working_dir' in file) {
638
+ const statusCode = file[location];
639
+ switch (statusCode) {
640
+ case 'A':
641
+ return 'added';
642
+ case 'D':
643
+ return 'deleted';
644
+ case 'M':
645
+ return 'modified';
646
+ case 'R':
647
+ return 'renamed';
648
+ case '?':
649
+ return 'untracked';
650
+ default:
651
+ return 'unknown';
652
+ }
653
+ }
654
+ else if ('changes' in file && 'binary' in file) {
655
+ if (file.changes === 0)
656
+ return 'untracked';
657
+ if (file.file.includes('=>'))
658
+ return 'renamed';
659
+ if (file.deletions === 0 && file.insertions > 0)
660
+ return 'added';
661
+ if (file.insertions === 0 && file.deletions > 0)
662
+ return 'deleted';
663
+ if ((file.insertions > 0 && file.deletions > 0) || file.changes > 0)
664
+ return 'modified';
665
+ return 'unknown';
666
+ }
667
+ else {
668
+ throw new Error("Invalid file type");
737
669
  }
738
- return status;
739
670
  };
740
671
 
741
672
  const getSummaryText = (file, change) => {
742
673
  const status = change.status || getStatus(file);
743
- if (change.oldFilepath) {
744
- return `${status}: ${change.oldFilepath} -> ${file.path}`;
674
+ let filePath;
675
+ if ('path' in file) {
676
+ filePath = file.path;
677
+ }
678
+ else if ('file' in file) {
679
+ filePath = change?.filePath || file.file;
680
+ }
681
+ else {
682
+ throw new Error("Invalid file type");
683
+ }
684
+ if (change.oldFilePath) {
685
+ return `${status}: ${change.oldFilePath} -> ${filePath}`;
745
686
  }
746
- return `${status}: ${file.path}`;
687
+ return `${status}: ${filePath}`;
747
688
  };
748
689
 
749
690
  const config = loadConfig();
750
691
  const DEFAULT_IGNORED_FILES = config?.ignoredFiles?.length ? config.ignoredFiles : [];
751
692
  const DEFAULT_IGNORED_EXTENSIONS = config?.ignoredExtensions?.length ? config.ignoredExtensions : [];
752
- async function getChanges(git, options = {}) {
753
- const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options;
693
+ async function getChanges({ git, options }) {
694
+ const { ignoredFiles = DEFAULT_IGNORED_FILES, ignoredExtensions = DEFAULT_IGNORED_EXTENSIONS } = options || {};
754
695
  const staged = [];
755
696
  const unstaged = [];
756
697
  const untracked = [];
757
698
  const status = await git.status();
758
699
  status.files.forEach((file) => {
759
700
  const fileChange = {
760
- filepath: file.path,
761
- oldFilepath: status.renamed.filter((renamed) => renamed.to === file.path)[0]?.from,
701
+ filePath: file.path,
702
+ oldFilePath: status.renamed.filter((renamed) => renamed.to === file.path)[0]?.from,
762
703
  };
763
704
  // Unstaged files
764
705
  if (file.working_dir !== '?' && file.working_dir !== ' ') {
@@ -781,16 +722,19 @@ async function getChanges(git, options = {}) {
781
722
  });
782
723
  const ignoredExtensionsSet = new Set(ignoredExtensions.map((extension) => extension.toLowerCase()));
783
724
  const filteredStaged = staged.filter((file) => {
784
- const extension = path.extname(file.filepath).toLowerCase();
785
- return !ignoredExtensionsSet.has(extension) && !ignoredFiles.some(ignoredPattern => minimatch.minimatch(file.filepath, ignoredPattern));
725
+ const extension = path.extname(file.filePath).toLowerCase();
726
+ return (!ignoredExtensionsSet.has(extension) &&
727
+ !ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
786
728
  });
787
729
  const filteredUnstaged = unstaged.filter((file) => {
788
- const extension = path.extname(file.filepath).toLowerCase();
789
- return !ignoredExtensionsSet.has(extension) && !ignoredFiles.some(ignoredPattern => minimatch.minimatch(file.filepath, ignoredPattern));
730
+ const extension = path.extname(file.filePath).toLowerCase();
731
+ return (!ignoredExtensionsSet.has(extension) &&
732
+ !ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
790
733
  });
791
734
  const filteredUntracked = untracked.filter((file) => {
792
- const extension = path.extname(file.filepath).toLowerCase();
793
- return !ignoredExtensionsSet.has(extension) && !ignoredFiles.some(ignoredPattern => minimatch.minimatch(file.filepath, ignoredPattern));
735
+ const extension = path.extname(file.filePath).toLowerCase();
736
+ return (!ignoredExtensionsSet.has(extension) &&
737
+ !ignoredFiles.some((ignoredPattern) => minimatch.minimatch(file.filePath, ignoredPattern)));
794
738
  });
795
739
  return {
796
740
  staged: filteredStaged,
@@ -800,7 +744,7 @@ async function getChanges(git, options = {}) {
800
744
  }
801
745
 
802
746
  const noResult = async ({ git, logger }) => {
803
- const { staged, unstaged, untracked } = await getChanges(git);
747
+ const { staged, unstaged, untracked } = await getChanges({ git });
804
748
  const hasStaged = staged && staged.length > 0;
805
749
  const hasUnstaged = unstaged && unstaged.length > 0;
806
750
  const hasUntracked = untracked && untracked.length > 0;
@@ -832,76 +776,25 @@ async function createCommit(commitMsg, git) {
832
776
  return await git.commit(commitMsg);
833
777
  }
834
778
 
835
- // const argv = loadArgv()
836
- const tokenizer = getTokenizer();
837
- const git = simpleGit.simpleGit();
838
- const command = ['commit', '$0'];
839
- const description = 'Generate a commit message based on the diff summary';
840
- const builder = {
841
- model: { type: 'string', description: 'LLM/Model-Name' },
842
- openAIApiKey: {
843
- type: 'string',
844
- description: 'OpenAI API Key',
845
- conflicts: 'huggingFaceHubApiKey',
846
- },
847
- huggingFaceHubApiKey: {
848
- type: 'string',
849
- description: 'HuggingFace Hub API Key',
850
- conflicts: 'openAIApiKey',
851
- },
852
- tokenLimit: { type: 'number', description: 'Token limit' },
853
- prompt: {
854
- type: 'string',
855
- alias: 'p',
856
- description: 'Commit message prompt',
857
- },
858
- i: {
859
- type: 'boolean',
860
- alias: 'interactive',
861
- description: 'Toggle interactive mode',
862
- },
863
- s: {
864
- type: 'boolean',
865
- description: 'Automatically commit staged changes with generated commit message',
866
- default: false,
867
- },
868
- e: {
869
- type: 'boolean',
870
- alias: 'edit',
871
- description: 'Open commit message in editor before proceeding',
872
- },
873
- summarizePrompt: {
874
- type: 'string',
875
- description: 'Large file summary prompt',
876
- },
877
- ignoredFiles: {
878
- type: 'array',
879
- description: 'Ignored files',
880
- },
881
- ignoredExtensions: {
882
- type: 'array',
883
- description: 'Ignored extensions',
884
- },
779
+ const SEPERATOR = chalk.blue('----------------');
780
+ const isInteractive = (argv) => {
781
+ return argv?.mode === 'interactive' || argv.interactive;
885
782
  };
886
- async function handler(argv) {
887
- const options = loadConfig(argv);
888
- const logger = new Logger(options);
889
- const key = getModelAPIKey(options.model, options);
890
- if (!key) {
891
- logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
892
- process.exit(1);
893
- }
894
- const model = getModel(options.model, key, {
895
- temperature: 0.4,
896
- maxConcurrency: 10,
897
- });
898
- const INTERACTIVE = options?.mode === 'interactive' || options.interactive;
899
- const { staged: changes } = await getChanges(git);
783
+ const logCommit = (commit) => {
784
+ console.log(`\n${chalk.bgBlue(chalk.bold('Proposed Commit:'))}\n${SEPERATOR}\n${commit}\n${SEPERATOR}\n`);
785
+ };
786
+ const logSuccess = () => {
787
+ console.log(chalk.green(chalk.bold('\nAll set! 🦾🤖')));
788
+ };
789
+ const generateCommitMessageAndReviewLoop = async (changes, options) => {
790
+ const { logger, model, git, tokenizer } = options;
900
791
  let summary = '';
901
792
  let commitMsg = '';
902
793
  let promptTemplate = options?.prompt || '';
903
794
  let modifyPrompt = false;
904
- while (true) {
795
+ // determine if we continue generating commit messages
796
+ let continueLoop = true;
797
+ while (continueLoop) {
905
798
  if (changes.length !== 0 && !summary.length) {
906
799
  logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
907
800
  color: 'blue',
@@ -947,7 +840,7 @@ async function handler(argv) {
947
840
  mode: 'succeed',
948
841
  })
949
842
  .stopTimer();
950
- if (INTERACTIVE) {
843
+ if (options?.interactive) {
951
844
  logCommit(commitMsg);
952
845
  const reviewAnswer = await prompts$1.select({
953
846
  message: 'Would you like to make any changes to the commit message?',
@@ -1019,23 +912,180 @@ async function handler(argv) {
1019
912
  },
1020
913
  });
1021
914
  }
1022
- const MODE = (options.interactive && 'interactive') ||
1023
- (options.commit && 'interactive') ||
1024
- options?.mode ||
1025
- 'stdout';
1026
- // Handle resulting commit message
1027
- switch (MODE) {
1028
- case 'interactive':
1029
- await createCommit(commitMsg, git);
1030
- logSuccess();
1031
- break;
1032
- case 'stdout':
1033
- default:
1034
- process.stdout.write(commitMsg, 'utf8');
1035
- break;
915
+ continueLoop = false;
916
+ }
917
+ return commitMsg;
918
+ };
919
+ const handleResult = async (commit, { mode, git }) => {
920
+ // Handle resulting commit message
921
+ switch (mode) {
922
+ case 'interactive':
923
+ await createCommit(commit, git);
924
+ logSuccess();
925
+ break;
926
+ case 'stdout':
927
+ default:
928
+ process.stdout.write(commit, 'utf8');
929
+ break;
930
+ }
931
+ process.exit(0);
932
+ };
933
+
934
+ /**
935
+ * Wrapper around GPT3NodeTokenizer to handle default export.
936
+ *
937
+ * @see https://github.com/botisan-ai/gpt3-tokenizer/issues/18
938
+ *
939
+ * @returns {GPT3NodeTokenizer} The GPT3NodeTokenizer instance.
940
+ */
941
+ const getTokenizer = () => {
942
+ let tokenizer;
943
+ // eslint-disable-next-line
944
+ // @ts-ignore
945
+ if (GPT3NodeTokenizer.default) {
946
+ // eslint-disable-next-line
947
+ // @ts-ignore
948
+ tokenizer = new GPT3NodeTokenizer.default({ type: 'gpt3' });
949
+ }
950
+ else {
951
+ tokenizer = new GPT3NodeTokenizer({ type: 'gpt3' });
952
+ }
953
+ return tokenizer;
954
+ };
955
+
956
+ class Logger {
957
+ constructor(config) {
958
+ this.config = config;
959
+ this.spinner = null;
960
+ }
961
+ log(message, options = { color: 'blue' }) {
962
+ let outputMessage = message;
963
+ if (options.color) {
964
+ outputMessage = chalk[options.color](outputMessage);
965
+ }
966
+ console.log(outputMessage);
967
+ return this;
968
+ }
969
+ verbose(message, options = {}) {
970
+ if (!this.config?.verbose) {
971
+ return this;
972
+ }
973
+ this.log(message, options);
974
+ return this;
975
+ }
976
+ startTimer() {
977
+ this.timerStart = now();
978
+ return this;
979
+ }
980
+ stopTimer(message, options = { color: 'yellow' }) {
981
+ if (!this.config?.verbose || !this.timerStart) {
982
+ return this;
983
+ }
984
+ const elapsedTime = prettyMilliseconds(now() - this.timerStart);
985
+ let outputMessage = message
986
+ ? `${message} (⏲ ${elapsedTime})`
987
+ : `⏲ ${elapsedTime}`;
988
+ if (options.color) {
989
+ outputMessage = chalk[options.color](outputMessage);
1036
990
  }
1037
- process.exit(0);
991
+ console.log(outputMessage);
992
+ return this;
993
+ }
994
+ startSpinner(message, options = { color: 'green' }) {
995
+ const spinnerMessage = options.color ? chalk[options.color](message) : message;
996
+ this.spinner = ora(spinnerMessage).start();
997
+ return this;
998
+ }
999
+ stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
1000
+ const spinnerMessage = options?.color ? chalk[options.color](message) : message;
1001
+ this.spinner?.[options.mode || 'succeed'](spinnerMessage);
1002
+ this.spinner = null;
1003
+ return this;
1004
+ }
1005
+ }
1006
+
1007
+ // const argv = loadArgv()
1008
+ const tokenizer = getTokenizer();
1009
+ const git = simpleGit.simpleGit();
1010
+ const command = ['commit', '$0'];
1011
+ const description = 'Generate a commit message based on the diff summary';
1012
+ const builder = {
1013
+ model: { type: 'string', description: 'LLM/Model-Name' },
1014
+ openAIApiKey: {
1015
+ type: 'string',
1016
+ description: 'OpenAI API Key',
1017
+ conflicts: 'huggingFaceHubApiKey',
1018
+ },
1019
+ huggingFaceHubApiKey: {
1020
+ type: 'string',
1021
+ description: 'HuggingFace Hub API Key',
1022
+ conflicts: 'openAIApiKey',
1023
+ },
1024
+ tokenLimit: { type: 'number', description: 'Token limit' },
1025
+ prompt: {
1026
+ type: 'string',
1027
+ alias: 'p',
1028
+ description: 'Commit message prompt',
1029
+ },
1030
+ i: {
1031
+ type: 'boolean',
1032
+ alias: 'interactive',
1033
+ description: 'Toggle interactive mode',
1034
+ },
1035
+ s: {
1036
+ type: 'boolean',
1037
+ description: 'Automatically commit staged changes with generated commit message',
1038
+ default: false,
1039
+ },
1040
+ e: {
1041
+ type: 'boolean',
1042
+ alias: 'edit',
1043
+ description: 'Open commit message in editor before proceeding',
1044
+ },
1045
+ summarizePrompt: {
1046
+ type: 'string',
1047
+ description: 'Large file summary prompt',
1048
+ },
1049
+ ignoredFiles: {
1050
+ type: 'array',
1051
+ description: 'Ignored files',
1052
+ },
1053
+ ignoredExtensions: {
1054
+ type: 'array',
1055
+ description: 'Ignored extensions',
1056
+ },
1057
+ };
1058
+ async function handler(argv) {
1059
+ const options = loadConfig(argv);
1060
+ const logger = new Logger(options);
1061
+ const key = getModelAPIKey(options.model, options);
1062
+ if (!key) {
1063
+ logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
1064
+ process.exit(1);
1038
1065
  }
1066
+ const model = getModel(options.model, key, {
1067
+ temperature: 0.4,
1068
+ maxConcurrency: 10,
1069
+ });
1070
+ const INTERACTIVE = isInteractive(options);
1071
+ const { staged: changes } = await getChanges({ git });
1072
+ const commitMsg = await generateCommitMessageAndReviewLoop(changes, {
1073
+ logger,
1074
+ model,
1075
+ git,
1076
+ tokenizer,
1077
+ prompt: options.prompt,
1078
+ interactive: INTERACTIVE,
1079
+ openInEditor: options.openInEditor,
1080
+ });
1081
+ const MODE = (options.interactive && 'interactive') ||
1082
+ (options.commit && 'interactive') ||
1083
+ options?.mode ||
1084
+ 'stdout';
1085
+ handleResult(commitMsg, {
1086
+ mode: MODE,
1087
+ git,
1088
+ });
1039
1089
  }
1040
1090
 
1041
1091
  var commit = /*#__PURE__*/Object.freeze({
@@ -1061,3 +1111,4 @@ yargs
1061
1111
  }).argv;
1062
1112
 
1063
1113
  exports.commit = commit;
1114
+ exports.loadConfig = loadConfig;
@@ -2,7 +2,7 @@ export interface Config {
2
2
  /**
3
3
  * The LLM model to use for generating results.
4
4
  *
5
- * @default 'openai/gpt-3.5-turbo'
5
+ * @default 'openai/gpt-4'
6
6
  *
7
7
  * @example 'openai/gpt-4'
8
8
  * @example 'openai/gpt-3.5-turbo'
@@ -1,3 +1,3 @@
1
1
  import { PromptTemplate } from 'langchain/prompts';
2
2
  export declare const inputVariables: string[];
3
- export declare const COMMIT_PROMPT: PromptTemplate;
3
+ export declare const COMMIT_PROMPT: PromptTemplate<any, any>;