git-coco 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/commit.d.ts +16 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.mjs +212 -170
- package/dist/index.esm.mjs.map +1 -1
- package/dist/index.js +213 -170
- package/dist/lib/config/{index.d.ts → loadConfig.d.ts} +1 -3
- package/dist/lib/langchain/utils.d.ts +26 -2
- package/dist/lib/parsers/default/utils/collectDiffs.d.ts +1 -1
- package/dist/lib/parsers/default/utils/summarizeDiffs.d.ts +3 -1
- package/dist/lib/parsers/noResult.d.ts +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/stats.html +2 -2
- package/dist/types.d.ts +8 -0
- package/package.json +4 -4
- package/dist/lib/config/services/yargs.d.ts +0 -25
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Argv, CommandBuilder } from 'yargs';
|
|
2
|
+
import { BaseCommandOptions } from '../types';
|
|
3
|
+
export interface CommitOptions extends BaseCommandOptions {
|
|
4
|
+
interactive: boolean;
|
|
5
|
+
tokenLimit: number;
|
|
6
|
+
prompt: string;
|
|
7
|
+
commit: boolean;
|
|
8
|
+
summarizePrompt: string;
|
|
9
|
+
openInEditor: boolean;
|
|
10
|
+
ignoredFiles: string[];
|
|
11
|
+
ignoredExtensions: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare const command: string[];
|
|
14
|
+
export declare const description = "Generate a commit message based on the diff summary";
|
|
15
|
+
export declare const builder: CommandBuilder<CommitOptions>;
|
|
16
|
+
export declare function handler(argv: Argv<CommitOptions>["argv"]): Promise<void>;
|
package/dist/index.d.ts
CHANGED
package/dist/index.esm.mjs
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import yargs from 'yargs';
|
|
2
3
|
import { select, editor } from '@inquirer/prompts';
|
|
4
|
+
import { simpleGit } from 'simple-git';
|
|
3
5
|
import * as fs from 'fs';
|
|
4
6
|
import * as os from 'os';
|
|
5
7
|
import * as path from 'path';
|
|
6
8
|
import path__default from 'path';
|
|
7
9
|
import * as ini from 'ini';
|
|
8
|
-
import yargs from 'yargs';
|
|
9
|
-
import { hideBin } from 'yargs/helpers';
|
|
10
10
|
import { PromptTemplate } from 'langchain/prompts';
|
|
11
11
|
import pQueue from 'p-queue';
|
|
12
|
-
import chalk from 'chalk';
|
|
13
|
-
import ora from 'ora';
|
|
14
|
-
import now from 'performance-now';
|
|
15
|
-
import prettyMilliseconds from 'pretty-ms';
|
|
16
12
|
import { Document } from 'langchain/document';
|
|
17
13
|
import { HuggingFaceInference } from 'langchain/llms/hf';
|
|
18
14
|
import { loadSummarizationChain, LLMChain } from 'langchain/chains';
|
|
19
15
|
import { OpenAI } from 'langchain/llms/openai';
|
|
20
16
|
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
|
|
21
17
|
import { createTwoFilesPatch } from 'diff';
|
|
18
|
+
import chalk from 'chalk';
|
|
22
19
|
import GPT3NodeTokenizer from 'gpt3-tokenizer';
|
|
20
|
+
import ora from 'ora';
|
|
21
|
+
import now from 'performance-now';
|
|
22
|
+
import prettyMilliseconds from 'pretty-ms';
|
|
23
23
|
import { minimatch } from 'minimatch';
|
|
24
|
-
import { simpleGit } from 'simple-git';
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Returns a new object with all undefined keys removed
|
|
@@ -152,74 +151,6 @@ function loadXDGConfig(config) {
|
|
|
152
151
|
return config;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
/**
|
|
156
|
-
* Command line options via yargs
|
|
157
|
-
*/
|
|
158
|
-
const options = {
|
|
159
|
-
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
160
|
-
openAIApiKey: { type: 'string', description: 'OpenAI API Key' },
|
|
161
|
-
huggingFaceHubApiKey: { type: 'string', description: 'HuggingFace Hub API Key' },
|
|
162
|
-
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
163
|
-
prompt: {
|
|
164
|
-
type: 'string',
|
|
165
|
-
alias: 'p',
|
|
166
|
-
description: 'Commit message prompt',
|
|
167
|
-
},
|
|
168
|
-
interactive: {
|
|
169
|
-
type: 'boolean',
|
|
170
|
-
alias: 'i',
|
|
171
|
-
description: 'Toggle interactive mode',
|
|
172
|
-
},
|
|
173
|
-
commit: {
|
|
174
|
-
type: 'boolean',
|
|
175
|
-
alias: 's',
|
|
176
|
-
description: 'Commit staged changes with generated commit message',
|
|
177
|
-
default: false,
|
|
178
|
-
},
|
|
179
|
-
openInEditor: {
|
|
180
|
-
type: 'boolean',
|
|
181
|
-
alias: 'e',
|
|
182
|
-
description: 'Open commit message in editor before proceeding',
|
|
183
|
-
},
|
|
184
|
-
verbose: {
|
|
185
|
-
type: 'boolean',
|
|
186
|
-
description: 'Enable verbose logging',
|
|
187
|
-
},
|
|
188
|
-
summarizePrompt: {
|
|
189
|
-
type: 'string',
|
|
190
|
-
description: 'Large file summary prompt',
|
|
191
|
-
},
|
|
192
|
-
ignoredFiles: {
|
|
193
|
-
type: 'array',
|
|
194
|
-
description: 'Ignored files',
|
|
195
|
-
},
|
|
196
|
-
ignoredExtensions: {
|
|
197
|
-
type: 'array',
|
|
198
|
-
description: 'Ignored extensions',
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
/**
|
|
202
|
-
* Load command line flags via yargs
|
|
203
|
-
*
|
|
204
|
-
* @returns {Partial<Config>} Updated config
|
|
205
|
-
*/
|
|
206
|
-
const loadArgv = () => {
|
|
207
|
-
return yargs(hideBin(process.argv)).options(options).parseSync();
|
|
208
|
-
};
|
|
209
|
-
/**
|
|
210
|
-
* Load command line flags
|
|
211
|
-
*
|
|
212
|
-
* Note: Arugments are parsed using yargs.
|
|
213
|
-
*
|
|
214
|
-
* @param {Config} config
|
|
215
|
-
* @returns {Config} Updated config
|
|
216
|
-
**/
|
|
217
|
-
function loadCmdLineFlags(config) {
|
|
218
|
-
const argv = loadArgv();
|
|
219
|
-
config = { ...config, ...argv };
|
|
220
|
-
return config;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
154
|
const template$1 = `Write informative git commit message based on the diffs & file changes provided in the "Diff Summary" section.
|
|
224
155
|
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.
|
|
225
156
|
- Write concisely using an informal tone
|
|
@@ -282,7 +213,7 @@ const DEFAULT_CONFIG = {
|
|
|
282
213
|
*
|
|
283
214
|
* @returns {Config} application config
|
|
284
215
|
**/
|
|
285
|
-
function loadConfig() {
|
|
216
|
+
function loadConfig(argv = {}) {
|
|
286
217
|
// Default config
|
|
287
218
|
let config = DEFAULT_CONFIG;
|
|
288
219
|
config = loadGitignore(config);
|
|
@@ -291,60 +222,7 @@ function loadConfig() {
|
|
|
291
222
|
config = loadGitConfig(config);
|
|
292
223
|
config = loadProjectConfig(config);
|
|
293
224
|
config = loadEnvConfig(config);
|
|
294
|
-
config
|
|
295
|
-
return config;
|
|
296
|
-
}
|
|
297
|
-
const config = loadConfig();
|
|
298
|
-
|
|
299
|
-
class Logger {
|
|
300
|
-
constructor(config) {
|
|
301
|
-
this.config = config;
|
|
302
|
-
this.spinner = null;
|
|
303
|
-
}
|
|
304
|
-
log(message, options = { color: 'blue' }) {
|
|
305
|
-
let outputMessage = message;
|
|
306
|
-
if (options.color) {
|
|
307
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
308
|
-
}
|
|
309
|
-
console.log(outputMessage);
|
|
310
|
-
return this;
|
|
311
|
-
}
|
|
312
|
-
verbose(message, options = {}) {
|
|
313
|
-
if (!this.config?.verbose) {
|
|
314
|
-
return this;
|
|
315
|
-
}
|
|
316
|
-
this.log(message, options);
|
|
317
|
-
return this;
|
|
318
|
-
}
|
|
319
|
-
startTimer() {
|
|
320
|
-
this.timerStart = now();
|
|
321
|
-
return this;
|
|
322
|
-
}
|
|
323
|
-
stopTimer(message, options = { color: 'yellow' }) {
|
|
324
|
-
if (!this.config?.verbose || !this.timerStart) {
|
|
325
|
-
return this;
|
|
326
|
-
}
|
|
327
|
-
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
328
|
-
let outputMessage = message
|
|
329
|
-
? `${message} (⏲ ${elapsedTime})`
|
|
330
|
-
: `⏲ ${elapsedTime}`;
|
|
331
|
-
if (options.color) {
|
|
332
|
-
outputMessage = chalk[options.color](outputMessage);
|
|
333
|
-
}
|
|
334
|
-
console.log(outputMessage);
|
|
335
|
-
return this;
|
|
336
|
-
}
|
|
337
|
-
startSpinner(message, options = { color: 'green' }) {
|
|
338
|
-
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
339
|
-
this.spinner = ora(spinnerMessage).start();
|
|
340
|
-
return this;
|
|
341
|
-
}
|
|
342
|
-
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
343
|
-
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
344
|
-
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
345
|
-
this.spinner = null;
|
|
346
|
-
return this;
|
|
347
|
-
}
|
|
225
|
+
return { ...config, ...argv };
|
|
348
226
|
}
|
|
349
227
|
|
|
350
228
|
/**
|
|
@@ -431,8 +309,7 @@ const defaultOutputCallback = (group) => {
|
|
|
431
309
|
}
|
|
432
310
|
return output;
|
|
433
311
|
};
|
|
434
|
-
async function summarizeDiffs(rootDiffNode, { tokenizer, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
435
|
-
const logger = new Logger(config);
|
|
312
|
+
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
436
313
|
const queue = new pQueue({ concurrency: 8 });
|
|
437
314
|
logger.startTimer().startSpinner(`Organizing Diffs...`, { color: 'blue' });
|
|
438
315
|
const directoryDiffs = createDirectoryDiffs(rootDiffNode);
|
|
@@ -518,7 +395,7 @@ const createDiffTree = (changes) => {
|
|
|
518
395
|
/**
|
|
519
396
|
* Asynchronously collect diffs for a given node and its children.
|
|
520
397
|
*/
|
|
521
|
-
async function collectDiffs(node, getFileDiff, tokenizer, logger
|
|
398
|
+
async function collectDiffs(node, getFileDiff, tokenizer, logger) {
|
|
522
399
|
// Collect diffs for the files of the current node
|
|
523
400
|
const diffPromises = node.files.map(async (nodeFile) => {
|
|
524
401
|
const diff = await getFileDiff(nodeFile);
|
|
@@ -536,7 +413,7 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
536
413
|
};
|
|
537
414
|
});
|
|
538
415
|
// Collect diffs for the children of the current node
|
|
539
|
-
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer));
|
|
416
|
+
const childrenPromises = Array.from(node.children.values()).map(async (child) => collectDiffs(child, getFileDiff, tokenizer, logger));
|
|
540
417
|
const [diffs, children] = await Promise.all([
|
|
541
418
|
Promise.all(diffPromises),
|
|
542
419
|
Promise.all(childrenPromises),
|
|
@@ -550,36 +427,65 @@ async function collectDiffs(node, getFileDiff, tokenizer, logger = new Logger(co
|
|
|
550
427
|
|
|
551
428
|
/**
|
|
552
429
|
* Get LLM Model Based on Configuration
|
|
553
|
-
*
|
|
554
430
|
* @param fields
|
|
555
431
|
* @param configuration
|
|
556
432
|
* @returns LLM Model
|
|
557
433
|
*/
|
|
558
|
-
function getModel(fields, configuration) {
|
|
559
|
-
const [llm, model] =
|
|
434
|
+
function getModel(name, key, fields, configuration) {
|
|
435
|
+
const [llm, model] = name.split(/\/(.*)/s);
|
|
560
436
|
if (!model) {
|
|
561
|
-
throw new Error(`Invalid model: ${
|
|
437
|
+
throw new Error(`Invalid model: ${name}`);
|
|
562
438
|
}
|
|
563
439
|
switch (llm) {
|
|
564
440
|
case 'huggingface':
|
|
565
441
|
return new HuggingFaceInference({
|
|
566
442
|
model: model,
|
|
567
|
-
apiKey:
|
|
443
|
+
apiKey: key,
|
|
568
444
|
maxConcurrency: 4,
|
|
569
445
|
...fields,
|
|
570
446
|
});
|
|
571
447
|
case 'openai':
|
|
572
448
|
default:
|
|
573
449
|
return new OpenAI({
|
|
574
|
-
openAIApiKey:
|
|
450
|
+
openAIApiKey: key,
|
|
575
451
|
modelName: model,
|
|
576
452
|
...fields,
|
|
577
453
|
}, configuration);
|
|
578
454
|
}
|
|
579
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Retrieve appropriate API key based on selected model
|
|
458
|
+
* @param name
|
|
459
|
+
* @param options
|
|
460
|
+
* @returns
|
|
461
|
+
*/
|
|
462
|
+
function getModelAPIKey(name, options) {
|
|
463
|
+
const [llm, model] = name.split(/\/(.*)/s);
|
|
464
|
+
if (!model) {
|
|
465
|
+
throw new Error(`Invalid model: ${name}`);
|
|
466
|
+
}
|
|
467
|
+
switch (llm) {
|
|
468
|
+
case 'huggingface':
|
|
469
|
+
return options.huggingFaceHubApiKey;
|
|
470
|
+
case 'openai':
|
|
471
|
+
default:
|
|
472
|
+
return options.openAIApiKey;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Get Recursive Character Text Splitter
|
|
477
|
+
* @param options
|
|
478
|
+
* @returns
|
|
479
|
+
*/
|
|
580
480
|
function getTextSplitter(options = {}) {
|
|
581
481
|
return new RecursiveCharacterTextSplitter(options);
|
|
582
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Get Summarization Chain
|
|
485
|
+
* @param model
|
|
486
|
+
* @param options
|
|
487
|
+
* @returns
|
|
488
|
+
*/
|
|
583
489
|
function getChain(model, options = { type: 'map_reduce' }) {
|
|
584
490
|
return loadSummarizationChain(model, options);
|
|
585
491
|
}
|
|
@@ -593,6 +499,12 @@ function getPrompt({ template, variables, fallback }) {
|
|
|
593
499
|
})
|
|
594
500
|
: fallback);
|
|
595
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* Verify template string contains all required input variables
|
|
504
|
+
* @param text template string
|
|
505
|
+
* @param inputVariables template variables
|
|
506
|
+
* @returns boolean or error message
|
|
507
|
+
*/
|
|
596
508
|
function validatePromptTemplate(text, inputVariables) {
|
|
597
509
|
if (!text) {
|
|
598
510
|
return 'Prompt template cannot be empty';
|
|
@@ -646,8 +558,7 @@ const getDiff = async (nodeFile, { git, logger, }) => {
|
|
|
646
558
|
};
|
|
647
559
|
|
|
648
560
|
const MAX_TOKENS_PER_SUMMARY = 2048;
|
|
649
|
-
const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
650
|
-
const logger = new Logger(config);
|
|
561
|
+
const fileChangeParser = async (changes, { tokenizer, git, model, logger }) => {
|
|
651
562
|
const textSplitter = getTextSplitter({ chunkSize: 2000, chunkOverlap: 125, });
|
|
652
563
|
const summarizationChain = getChain(model, {
|
|
653
564
|
type: 'map_reduce',
|
|
@@ -668,6 +579,7 @@ const fileChangeParser = async (changes, { tokenizer, git, model }) => {
|
|
|
668
579
|
maxTokens: MAX_TOKENS_PER_SUMMARY,
|
|
669
580
|
textSplitter,
|
|
670
581
|
chain: summarizationChain,
|
|
582
|
+
logger
|
|
671
583
|
});
|
|
672
584
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
673
585
|
return summary;
|
|
@@ -703,6 +615,57 @@ const getTokenizer = () => {
|
|
|
703
615
|
return tokenizer;
|
|
704
616
|
};
|
|
705
617
|
|
|
618
|
+
class Logger {
|
|
619
|
+
constructor(config) {
|
|
620
|
+
this.config = config;
|
|
621
|
+
this.spinner = null;
|
|
622
|
+
}
|
|
623
|
+
log(message, options = { color: 'blue' }) {
|
|
624
|
+
let outputMessage = message;
|
|
625
|
+
if (options.color) {
|
|
626
|
+
outputMessage = chalk[options.color](outputMessage);
|
|
627
|
+
}
|
|
628
|
+
console.log(outputMessage);
|
|
629
|
+
return this;
|
|
630
|
+
}
|
|
631
|
+
verbose(message, options = {}) {
|
|
632
|
+
if (!this.config?.verbose) {
|
|
633
|
+
return this;
|
|
634
|
+
}
|
|
635
|
+
this.log(message, options);
|
|
636
|
+
return this;
|
|
637
|
+
}
|
|
638
|
+
startTimer() {
|
|
639
|
+
this.timerStart = now();
|
|
640
|
+
return this;
|
|
641
|
+
}
|
|
642
|
+
stopTimer(message, options = { color: 'yellow' }) {
|
|
643
|
+
if (!this.config?.verbose || !this.timerStart) {
|
|
644
|
+
return this;
|
|
645
|
+
}
|
|
646
|
+
const elapsedTime = prettyMilliseconds(now() - this.timerStart);
|
|
647
|
+
let outputMessage = message
|
|
648
|
+
? `${message} (⏲ ${elapsedTime})`
|
|
649
|
+
: `⏲ ${elapsedTime}`;
|
|
650
|
+
if (options.color) {
|
|
651
|
+
outputMessage = chalk[options.color](outputMessage);
|
|
652
|
+
}
|
|
653
|
+
console.log(outputMessage);
|
|
654
|
+
return this;
|
|
655
|
+
}
|
|
656
|
+
startSpinner(message, options = { color: 'green' }) {
|
|
657
|
+
const spinnerMessage = options.color ? chalk[options.color](message) : message;
|
|
658
|
+
this.spinner = ora(spinnerMessage).start();
|
|
659
|
+
return this;
|
|
660
|
+
}
|
|
661
|
+
stopSpinner(message = '', options = { mode: 'succeed', color: 'green' }) {
|
|
662
|
+
const spinnerMessage = options?.color ? chalk[options.color](message) : message;
|
|
663
|
+
this.spinner?.[options.mode || 'succeed'](spinnerMessage);
|
|
664
|
+
this.spinner = null;
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
706
669
|
const llm = async ({ llm, prompt, variables }) => {
|
|
707
670
|
if (!llm || !prompt || !variables) {
|
|
708
671
|
throw new Error('The input parameters "llm", "prompt", and "variables" are all required.');
|
|
@@ -760,6 +723,7 @@ const getSummaryText = (file, change) => {
|
|
|
760
723
|
return `${status}: ${file.path}`;
|
|
761
724
|
};
|
|
762
725
|
|
|
726
|
+
const config = loadConfig();
|
|
763
727
|
const DEFAULT_IGNORED_FILES = config?.ignoredFiles?.length ? config.ignoredFiles : [];
|
|
764
728
|
const DEFAULT_IGNORED_EXTENSIONS = config?.ignoredExtensions?.length ? config.ignoredExtensions : [];
|
|
765
729
|
async function getChanges(git, options = {}) {
|
|
@@ -814,62 +778,117 @@ async function getChanges(git, options = {}) {
|
|
|
814
778
|
|
|
815
779
|
const noResult = async ({ git, logger }) => {
|
|
816
780
|
const { staged, unstaged, untracked } = await getChanges(git);
|
|
817
|
-
|
|
781
|
+
const hasStaged = staged && staged.length > 0;
|
|
782
|
+
const hasUnstaged = unstaged && unstaged.length > 0;
|
|
783
|
+
const hasUntracked = untracked && untracked.length > 0;
|
|
784
|
+
if (hasStaged) {
|
|
818
785
|
logger.log(`Staged files detected, but no summary generated...`, { color: 'red' });
|
|
819
786
|
logger.log(`Files are likely either:\n • changed files are ignored\n • file diff is too large.`, { color: 'yellow' });
|
|
820
787
|
}
|
|
821
|
-
else if (
|
|
822
|
-
logger.log('No staged
|
|
823
|
-
|
|
824
|
-
color: 'yellow'
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
color: 'yellow'
|
|
831
|
-
|
|
788
|
+
else if (hasUnstaged || hasUntracked) {
|
|
789
|
+
logger.log('Forget something? No staged changes found... 👻', { color: 'red' });
|
|
790
|
+
if (hasUnstaged) {
|
|
791
|
+
logger.log('\nDetected unstaged changes', { color: 'yellow' });
|
|
792
|
+
logger.verbose(`\t${unstaged.map(({ summary }) => summary).join('\n\t')}`, {
|
|
793
|
+
color: 'red',
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
if (hasUntracked) {
|
|
797
|
+
logger.log('\nDetected untracked changes', { color: 'yellow' });
|
|
798
|
+
logger.verbose(`\t${untracked.map(({ summary }) => summary).join('\n\t')}`, {
|
|
799
|
+
color: 'red',
|
|
800
|
+
});
|
|
801
|
+
}
|
|
832
802
|
}
|
|
833
803
|
else {
|
|
834
|
-
logger.log('No repo changes detected.', { color: '
|
|
804
|
+
logger.log('No repo changes detected. 👀', { color: 'blue' });
|
|
835
805
|
}
|
|
836
|
-
process.exit(0);
|
|
837
806
|
};
|
|
838
807
|
|
|
839
808
|
async function createCommit(commitMsg, git) {
|
|
840
809
|
return await git.commit(commitMsg);
|
|
841
810
|
}
|
|
842
811
|
|
|
843
|
-
const argv = loadArgv()
|
|
812
|
+
// const argv = loadArgv()
|
|
844
813
|
const tokenizer = getTokenizer();
|
|
845
814
|
const git = simpleGit();
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
815
|
+
const command = ['commit', '$0'];
|
|
816
|
+
const description = 'Generate a commit message based on the diff summary';
|
|
817
|
+
const builder = {
|
|
818
|
+
model: { type: 'string', description: 'LLM/Model-Name' },
|
|
819
|
+
openAIApiKey: {
|
|
820
|
+
type: 'string',
|
|
821
|
+
description: 'OpenAI API Key',
|
|
822
|
+
conflicts: 'huggingFaceHubApiKey',
|
|
823
|
+
},
|
|
824
|
+
huggingFaceHubApiKey: {
|
|
825
|
+
type: 'string',
|
|
826
|
+
description: 'HuggingFace Hub API Key',
|
|
827
|
+
conflicts: 'openAIApiKey',
|
|
828
|
+
},
|
|
829
|
+
tokenLimit: { type: 'number', description: 'Token limit' },
|
|
830
|
+
prompt: {
|
|
831
|
+
type: 'string',
|
|
832
|
+
alias: 'p',
|
|
833
|
+
description: 'Commit message prompt',
|
|
834
|
+
},
|
|
835
|
+
i: {
|
|
836
|
+
type: 'boolean',
|
|
837
|
+
alias: 'interactive',
|
|
838
|
+
description: 'Toggle interactive mode',
|
|
839
|
+
},
|
|
840
|
+
s: {
|
|
841
|
+
type: 'boolean',
|
|
842
|
+
description: 'Automatically commit staged changes with generated commit message',
|
|
843
|
+
default: false,
|
|
844
|
+
},
|
|
845
|
+
e: {
|
|
846
|
+
type: 'boolean',
|
|
847
|
+
alias: 'edit',
|
|
848
|
+
description: 'Open commit message in editor before proceeding',
|
|
849
|
+
},
|
|
850
|
+
summarizePrompt: {
|
|
851
|
+
type: 'string',
|
|
852
|
+
description: 'Large file summary prompt',
|
|
853
|
+
},
|
|
854
|
+
ignoredFiles: {
|
|
855
|
+
type: 'array',
|
|
856
|
+
description: 'Ignored files',
|
|
857
|
+
},
|
|
858
|
+
ignoredExtensions: {
|
|
859
|
+
type: 'array',
|
|
860
|
+
description: 'Ignored extensions',
|
|
861
|
+
},
|
|
862
|
+
};
|
|
863
|
+
async function handler(argv) {
|
|
864
|
+
const options = loadConfig(argv);
|
|
865
|
+
const logger = new Logger(options);
|
|
866
|
+
const key = getModelAPIKey(options.model, options);
|
|
867
|
+
if (!key) {
|
|
849
868
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
850
869
|
process.exit(1);
|
|
851
870
|
}
|
|
852
|
-
const model = getModel({
|
|
871
|
+
const model = getModel(options.model, key, {
|
|
853
872
|
temperature: 0.4,
|
|
854
873
|
maxConcurrency: 10,
|
|
855
|
-
openAIApiKey: config.openAIApiKey,
|
|
856
874
|
});
|
|
857
|
-
const INTERACTIVE =
|
|
875
|
+
const INTERACTIVE = options?.mode === 'interactive' || options.interactive;
|
|
858
876
|
const { staged: changes } = await getChanges(git);
|
|
859
877
|
let summary = '';
|
|
860
878
|
let commitMsg = '';
|
|
861
|
-
let promptTemplate =
|
|
879
|
+
let promptTemplate = options?.prompt || '';
|
|
862
880
|
let modifyPrompt = false;
|
|
863
881
|
while (true) {
|
|
864
882
|
if (changes.length !== 0 && !summary.length) {
|
|
865
883
|
logger.verbose(`\nChanged Files: \n ${changes.map(({ summary }) => summary).join('\n ')}`, {
|
|
866
884
|
color: 'blue',
|
|
867
885
|
});
|
|
868
|
-
summary = await fileChangeParser(changes, { tokenizer, git, model });
|
|
886
|
+
summary = await fileChangeParser(changes, { tokenizer, git, model, logger });
|
|
869
887
|
}
|
|
870
888
|
// Handle empty summary
|
|
871
889
|
if (!summary.length) {
|
|
872
|
-
noResult({ git, logger });
|
|
890
|
+
await noResult({ git, logger });
|
|
891
|
+
process.exit(0);
|
|
873
892
|
}
|
|
874
893
|
// Prompt user for commit template prompt, if necessary
|
|
875
894
|
if (modifyPrompt) {
|
|
@@ -945,7 +964,7 @@ async function main(options) {
|
|
|
945
964
|
process.exit(0);
|
|
946
965
|
}
|
|
947
966
|
if (reviewAnswer === 'edit') {
|
|
948
|
-
|
|
967
|
+
options.openInEditor = true;
|
|
949
968
|
}
|
|
950
969
|
if (reviewAnswer === 'retryFull') {
|
|
951
970
|
summary = '';
|
|
@@ -964,7 +983,7 @@ async function main(options) {
|
|
|
964
983
|
continue;
|
|
965
984
|
}
|
|
966
985
|
}
|
|
967
|
-
if (
|
|
986
|
+
if (options.openInEditor) {
|
|
968
987
|
commitMsg = await editor({
|
|
969
988
|
message: 'Edit the commit message',
|
|
970
989
|
default: commitMsg,
|
|
@@ -979,7 +998,7 @@ async function main(options) {
|
|
|
979
998
|
}
|
|
980
999
|
const MODE = (options.interactive && 'interactive') ||
|
|
981
1000
|
(options.commit && 'interactive') ||
|
|
982
|
-
|
|
1001
|
+
options?.mode ||
|
|
983
1002
|
'stdout';
|
|
984
1003
|
// Handle resulting commit message
|
|
985
1004
|
switch (MODE) {
|
|
@@ -995,5 +1014,28 @@ async function main(options) {
|
|
|
995
1014
|
process.exit(0);
|
|
996
1015
|
}
|
|
997
1016
|
}
|
|
998
|
-
|
|
1017
|
+
|
|
1018
|
+
var commit = /*#__PURE__*/Object.freeze({
|
|
1019
|
+
__proto__: null,
|
|
1020
|
+
builder: builder,
|
|
1021
|
+
command: command,
|
|
1022
|
+
description: description,
|
|
1023
|
+
handler: handler
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
yargs
|
|
1027
|
+
.scriptName('coco')
|
|
1028
|
+
.commandDir('./commands', {
|
|
1029
|
+
extensions: ['ts'],
|
|
1030
|
+
})
|
|
1031
|
+
.demandCommand()
|
|
1032
|
+
.strict()
|
|
1033
|
+
.option('h', { alias: 'help' })
|
|
1034
|
+
.option('v', {
|
|
1035
|
+
alias: 'verbose',
|
|
1036
|
+
type: 'boolean',
|
|
1037
|
+
description: 'Run with verbose logging',
|
|
1038
|
+
}).argv;
|
|
1039
|
+
|
|
1040
|
+
export { commit, loadConfig };
|
|
999
1041
|
//# sourceMappingURL=index.esm.mjs.map
|