git-coco 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.mjs +167 -46
- package/dist/index.js +167 -46
- package/package.json +1 -1
package/dist/index.esm.mjs
CHANGED
|
@@ -46,7 +46,7 @@ import * as readline from 'readline';
|
|
|
46
46
|
/**
|
|
47
47
|
* Current build version from package.json
|
|
48
48
|
*/
|
|
49
|
-
const BUILD_VERSION = "0.
|
|
49
|
+
const BUILD_VERSION = "0.18.0";
|
|
50
50
|
|
|
51
51
|
const isInteractive = (config) => {
|
|
52
52
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -454,14 +454,29 @@ const CONFIG_KEYS = Object.keys({
|
|
|
454
454
|
**/
|
|
455
455
|
function loadEnvConfig(config) {
|
|
456
456
|
const envConfig = {};
|
|
457
|
-
const envKeys = [
|
|
457
|
+
const envKeys = [
|
|
458
|
+
...CONFIG_KEYS,
|
|
459
|
+
'COCO_SERVICE_PROVIDER',
|
|
460
|
+
'COCO_SERVICE_MODEL',
|
|
461
|
+
'OPEN_AI_KEY',
|
|
462
|
+
'COCO_SERVICE_ENDPOINT',
|
|
463
|
+
'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
|
|
464
|
+
'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
|
|
465
|
+
'COCO_SERVICE_FIELDS',
|
|
466
|
+
];
|
|
458
467
|
envKeys.forEach((key) => {
|
|
459
468
|
const envVarName = toEnvVarName(key);
|
|
460
469
|
const envValue = parseEnvValue(key, process.env[envVarName]);
|
|
461
470
|
if (envValue === undefined) {
|
|
462
471
|
return;
|
|
463
472
|
}
|
|
464
|
-
if (key === 'COCO_SERVICE_PROVIDER' ||
|
|
473
|
+
if (key === 'COCO_SERVICE_PROVIDER' ||
|
|
474
|
+
key === 'COCO_SERVICE_MODEL' ||
|
|
475
|
+
key === 'OPEN_AI_KEY' ||
|
|
476
|
+
key === 'COCO_SERVICE_ENDPOINT' ||
|
|
477
|
+
key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
|
|
478
|
+
key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
|
|
479
|
+
key === 'COCO_SERVICE_FIELDS') {
|
|
465
480
|
// NOTE: We want to ensure that the service object is always defined
|
|
466
481
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
467
482
|
// @ts-ignore
|
|
@@ -488,9 +503,28 @@ function handleServiceEnvVar(service, key, value) {
|
|
|
488
503
|
break;
|
|
489
504
|
case 'OPEN_AI_KEY':
|
|
490
505
|
if (service.provider === 'openai') {
|
|
491
|
-
service.
|
|
506
|
+
service.authentication = {
|
|
507
|
+
type: 'APIKey',
|
|
508
|
+
credentials: {
|
|
509
|
+
apiKey: value,
|
|
510
|
+
},
|
|
511
|
+
};
|
|
492
512
|
}
|
|
493
513
|
break;
|
|
514
|
+
case 'COCO_SERVICE_ENDPOINT':
|
|
515
|
+
if (service.provider === 'ollama') {
|
|
516
|
+
service.endpoint = value;
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
case 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT':
|
|
520
|
+
service.requestOptions = { ...service.requestOptions, timeout: value };
|
|
521
|
+
break;
|
|
522
|
+
case 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES':
|
|
523
|
+
service.requestOptions = { ...service.requestOptions, maxRetries: value };
|
|
524
|
+
break;
|
|
525
|
+
case 'COCO_SERVICE_FIELDS':
|
|
526
|
+
service.fields = value;
|
|
527
|
+
break;
|
|
494
528
|
}
|
|
495
529
|
}
|
|
496
530
|
function parseEnvValue(key, value) {
|
|
@@ -509,6 +543,9 @@ function parseEnvValue(key, value) {
|
|
|
509
543
|
// Handle number values
|
|
510
544
|
case typeof value === 'string' && !isNaN(Number(value)):
|
|
511
545
|
return Number(value);
|
|
546
|
+
// Handle JSON strings
|
|
547
|
+
case typeof value === 'string' && value.startsWith('{'):
|
|
548
|
+
return JSON.parse(value);
|
|
512
549
|
default:
|
|
513
550
|
return value;
|
|
514
551
|
}
|
|
@@ -522,32 +559,40 @@ function toEnvVarName(key) {
|
|
|
522
559
|
}
|
|
523
560
|
return `COCO_${key.replace(/([A-Z])/g, '_$1').toLocaleUpperCase()}`;
|
|
524
561
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
562
|
+
const flattenObject = (obj, prefix = '') => {
|
|
563
|
+
let flattened = {};
|
|
564
|
+
for (const key in obj) {
|
|
565
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
566
|
+
const propName = prefix ? `${prefix}_${key}` : key;
|
|
567
|
+
const value = obj[key];
|
|
568
|
+
// Skip undefined or null values
|
|
569
|
+
if (value === undefined || value === null) {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
573
|
+
// Handle nested objects, but specifically handle 'fields' as JSON string
|
|
574
|
+
if (key === 'fields') {
|
|
575
|
+
flattened[propName.toUpperCase()] = JSON.stringify(value);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
flattened = { ...flattened, ...flattenObject(value, propName) };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// For primitive types (string, number, boolean, symbol, bigint) and arrays
|
|
583
|
+
flattened[propName.toUpperCase()] = String(value);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
535
586
|
}
|
|
536
|
-
return
|
|
537
|
-
}
|
|
587
|
+
return flattened;
|
|
588
|
+
};
|
|
538
589
|
const appendToEnvFile = async (filePath, config) => {
|
|
539
590
|
const getNewContent = async () => {
|
|
540
|
-
|
|
591
|
+
const flattenedConfig = flattenObject(config);
|
|
592
|
+
return Object.entries(flattenedConfig)
|
|
541
593
|
.map(([key, value]) => {
|
|
542
|
-
if (key === 'service') {
|
|
543
|
-
const service = value;
|
|
544
|
-
return `${service.provider ? `COCO_SERVICE_PROVIDER=${service.provider}` : ''}\n${service.model ? `COCO_SERVICE_MODEL=${service.model}` : ''}\n${service.authentication.type === 'APIKey'
|
|
545
|
-
? `OPEN_AI_KEY=${service.authentication.credentials.apiKey}`
|
|
546
|
-
: ''}`;
|
|
547
|
-
}
|
|
548
594
|
const envVarName = toEnvVarName(key);
|
|
549
|
-
|
|
550
|
-
return `${envVarName}=${envValue}`;
|
|
595
|
+
return `${envVarName}=${value}`;
|
|
551
596
|
})
|
|
552
597
|
.join('\n');
|
|
553
598
|
};
|
|
@@ -571,22 +616,31 @@ function loadGitConfig(config) {
|
|
|
571
616
|
if (fs.existsSync(gitConfigPath)) {
|
|
572
617
|
const gitConfigRaw = fs.readFileSync(gitConfigPath, 'utf-8');
|
|
573
618
|
const gitConfigParsed = ini.parse(gitConfigRaw);
|
|
574
|
-
const gitConfigServiceObject = gitConfigParsed.coco?.service;
|
|
575
619
|
let service = config.service;
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
620
|
+
if (gitConfigParsed.coco) {
|
|
621
|
+
service = {
|
|
622
|
+
provider: gitConfigParsed.coco?.['service.provider'],
|
|
623
|
+
model: gitConfigParsed.coco?.['service.model'],
|
|
624
|
+
authentication: {
|
|
625
|
+
type: 'APIKey',
|
|
626
|
+
credentials: {
|
|
627
|
+
apiKey: gitConfigParsed.coco?.['service.apiKey'],
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
requestOptions: {
|
|
631
|
+
timeout: Number(gitConfigParsed.coco?.['service.requestOptions.timeout']),
|
|
632
|
+
maxRetries: Number(gitConfigParsed.coco?.['service.requestOptions.maxRetries']),
|
|
633
|
+
},
|
|
634
|
+
endpoint: gitConfigParsed.coco?.['service.endpoint'],
|
|
635
|
+
fields: gitConfigParsed.coco?.['service.fields']
|
|
636
|
+
? JSON.parse(gitConfigParsed.coco?.['service.fields'])
|
|
637
|
+
: undefined,
|
|
638
|
+
};
|
|
579
639
|
}
|
|
580
640
|
config = {
|
|
581
641
|
...config,
|
|
642
|
+
...gitConfigParsed.coco,
|
|
582
643
|
service: service,
|
|
583
|
-
prompt: gitConfigParsed.coco?.prompt || config.prompt,
|
|
584
|
-
mode: gitConfigParsed.coco?.mode || config.mode,
|
|
585
|
-
summarizePrompt: gitConfigParsed.coco?.summarizePrompt || config.summarizePrompt,
|
|
586
|
-
ignoredFiles: gitConfigParsed.coco?.ignoredFiles || config.ignoredFiles,
|
|
587
|
-
ignoredExtensions: gitConfigParsed.coco?.ignoredExtensions || config.ignoredExtensions,
|
|
588
|
-
defaultBranch: gitConfigParsed.coco?.defaultBranch || config.defaultBranch,
|
|
589
|
-
verbose: gitConfigParsed.coco?.verbose || config.verbose,
|
|
590
644
|
};
|
|
591
645
|
}
|
|
592
646
|
return removeUndefined(config);
|
|
@@ -606,9 +660,28 @@ const appendToGitConfig = async (filePath, config) => {
|
|
|
606
660
|
const contentLines = [header];
|
|
607
661
|
for (const key in config) {
|
|
608
662
|
const value = config[key];
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
contentLines.push(`\
|
|
663
|
+
if (key === 'service') {
|
|
664
|
+
const service = value;
|
|
665
|
+
contentLines.push(`\tservice.provider = ${service.provider}`);
|
|
666
|
+
contentLines.push(`\tservice.model = ${service.model}`);
|
|
667
|
+
if (service.authentication.type === 'APIKey') {
|
|
668
|
+
contentLines.push(`\tservice.apiKey = ${service.authentication.credentials.apiKey}`);
|
|
669
|
+
}
|
|
670
|
+
if (service.requestOptions?.timeout) {
|
|
671
|
+
contentLines.push(`\tservice.requestOptions.timeout = ${service.requestOptions.timeout}`);
|
|
672
|
+
}
|
|
673
|
+
if (service.requestOptions?.maxRetries) {
|
|
674
|
+
contentLines.push(`\tservice.requestOptions.maxRetries = ${service.requestOptions.maxRetries}`);
|
|
675
|
+
}
|
|
676
|
+
if (service.provider === 'ollama') {
|
|
677
|
+
const ollamaService = service;
|
|
678
|
+
if (ollamaService.endpoint) {
|
|
679
|
+
contentLines.push(` service.endpoint = ${ollamaService.endpoint}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (service.fields) {
|
|
683
|
+
contentLines.push(`\tservice.fields = ${JSON.stringify(service.fields)}`);
|
|
684
|
+
}
|
|
612
685
|
}
|
|
613
686
|
else if (typeof value === 'string' && value.includes('\n')) {
|
|
614
687
|
// Wrap strings with new lines in quotes
|
|
@@ -6058,7 +6131,6 @@ const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
|
6058
6131
|
try {
|
|
6059
6132
|
const chain = prompt.pipe(llm).pipe(parser);
|
|
6060
6133
|
const result = await chain.invoke(variables);
|
|
6061
|
-
console.debug('LLMChain call result:', result);
|
|
6062
6134
|
if (result === null || result === undefined) {
|
|
6063
6135
|
throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
|
|
6064
6136
|
}
|
|
@@ -10870,6 +10942,11 @@ const handler$3 = async (argv, logger) => {
|
|
|
10870
10942
|
else {
|
|
10871
10943
|
logger.setConfig({ silent: true });
|
|
10872
10944
|
}
|
|
10945
|
+
if (config.service.provider === 'ollama') {
|
|
10946
|
+
logger.verbose('⚠️ Ollama models may not strictly adhere to the output format instructions.', {
|
|
10947
|
+
color: 'yellow',
|
|
10948
|
+
});
|
|
10949
|
+
}
|
|
10873
10950
|
async function factory() {
|
|
10874
10951
|
const changes = await getChanges({
|
|
10875
10952
|
git,
|
|
@@ -10925,11 +11002,6 @@ const handler$3 = async (argv, logger) => {
|
|
|
10925
11002
|
variables: promptTemplate.inputVariables,
|
|
10926
11003
|
fallback: promptTemplate,
|
|
10927
11004
|
});
|
|
10928
|
-
if (config.service.provider === 'ollama') {
|
|
10929
|
-
logger.log('Note: Ollama models may not strictly adhere to the output format instructions.', {
|
|
10930
|
-
color: 'yellow',
|
|
10931
|
-
});
|
|
10932
|
-
}
|
|
10933
11005
|
// Get additional context if provided
|
|
10934
11006
|
let additional_context = '';
|
|
10935
11007
|
if (argv.additional) {
|
|
@@ -10970,7 +11042,7 @@ const handler$3 = async (argv, logger) => {
|
|
|
10970
11042
|
maxAttempts,
|
|
10971
11043
|
onRetry: (attempt, error) => {
|
|
10972
11044
|
logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
|
|
10973
|
-
}
|
|
11045
|
+
},
|
|
10974
11046
|
},
|
|
10975
11047
|
fallbackParser: (text) => ({
|
|
10976
11048
|
title: text.split('\n')[0] || 'Auto-generated commit',
|
|
@@ -11340,6 +11412,32 @@ const questions = {
|
|
|
11340
11412
|
});
|
|
11341
11413
|
return parseFloat(temperature);
|
|
11342
11414
|
},
|
|
11415
|
+
inputOllamaEndpoint: async () => {
|
|
11416
|
+
return await input({
|
|
11417
|
+
message: 'Ollama endpoint (e.g., http://localhost:11434):',
|
|
11418
|
+
default: 'http://localhost:11434',
|
|
11419
|
+
});
|
|
11420
|
+
},
|
|
11421
|
+
inputRequestTimeout: async () => {
|
|
11422
|
+
const timeout = await input({
|
|
11423
|
+
message: 'Request timeout in milliseconds:',
|
|
11424
|
+
default: '30000',
|
|
11425
|
+
});
|
|
11426
|
+
return parseInt(timeout);
|
|
11427
|
+
},
|
|
11428
|
+
inputRequestMaxRetries: async () => {
|
|
11429
|
+
const maxRetries = await input({
|
|
11430
|
+
message: 'Maximum number of request retries:',
|
|
11431
|
+
default: '3',
|
|
11432
|
+
});
|
|
11433
|
+
return parseInt(maxRetries);
|
|
11434
|
+
},
|
|
11435
|
+
inputServiceFields: async () => {
|
|
11436
|
+
return await editor({
|
|
11437
|
+
message: 'Enter additional service fields as a JSON string (optional):',
|
|
11438
|
+
default: '{}',
|
|
11439
|
+
});
|
|
11440
|
+
},
|
|
11343
11441
|
selectDefaultGitBranch: async () => (await input({
|
|
11344
11442
|
message: 'default branch for the repository:',
|
|
11345
11443
|
default: 'main',
|
|
@@ -11433,6 +11531,29 @@ const handler$2 = async (argv, logger) => {
|
|
|
11433
11531
|
tokenLimit: await questions.inputTokenLimit(),
|
|
11434
11532
|
};
|
|
11435
11533
|
config.verbose = await questions.enableVerboseMode();
|
|
11534
|
+
if (llmProvider === 'ollama') {
|
|
11535
|
+
config.service.endpoint = await questions.inputOllamaEndpoint();
|
|
11536
|
+
}
|
|
11537
|
+
config.service.requestOptions = {
|
|
11538
|
+
timeout: await questions.inputRequestTimeout(),
|
|
11539
|
+
maxRetries: await questions.inputRequestMaxRetries(),
|
|
11540
|
+
};
|
|
11541
|
+
const promptForServiceFields = await confirm({
|
|
11542
|
+
message: 'would you like to configure additional service fields (advanced)?',
|
|
11543
|
+
default: false,
|
|
11544
|
+
});
|
|
11545
|
+
if (promptForServiceFields) {
|
|
11546
|
+
const fieldsJson = await questions.inputServiceFields();
|
|
11547
|
+
try {
|
|
11548
|
+
config.service.fields = JSON.parse(fieldsJson);
|
|
11549
|
+
}
|
|
11550
|
+
catch (e) {
|
|
11551
|
+
logger.log('Invalid JSON for service fields. Skipping.', { color: 'red' });
|
|
11552
|
+
logger.verbose(`Error parsing service fields: ${e.message}`, {
|
|
11553
|
+
color: 'red',
|
|
11554
|
+
});
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11436
11557
|
const promptForIgnores = await confirm({
|
|
11437
11558
|
message: 'would you like to configure ignored files and extensions?',
|
|
11438
11559
|
default: false,
|
package/dist/index.js
CHANGED
|
@@ -68,7 +68,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline$1);
|
|
|
68
68
|
/**
|
|
69
69
|
* Current build version from package.json
|
|
70
70
|
*/
|
|
71
|
-
const BUILD_VERSION = "0.
|
|
71
|
+
const BUILD_VERSION = "0.18.0";
|
|
72
72
|
|
|
73
73
|
const isInteractive = (config) => {
|
|
74
74
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -476,14 +476,29 @@ const CONFIG_KEYS = Object.keys({
|
|
|
476
476
|
**/
|
|
477
477
|
function loadEnvConfig(config) {
|
|
478
478
|
const envConfig = {};
|
|
479
|
-
const envKeys = [
|
|
479
|
+
const envKeys = [
|
|
480
|
+
...CONFIG_KEYS,
|
|
481
|
+
'COCO_SERVICE_PROVIDER',
|
|
482
|
+
'COCO_SERVICE_MODEL',
|
|
483
|
+
'OPEN_AI_KEY',
|
|
484
|
+
'COCO_SERVICE_ENDPOINT',
|
|
485
|
+
'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
|
|
486
|
+
'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
|
|
487
|
+
'COCO_SERVICE_FIELDS',
|
|
488
|
+
];
|
|
480
489
|
envKeys.forEach((key) => {
|
|
481
490
|
const envVarName = toEnvVarName(key);
|
|
482
491
|
const envValue = parseEnvValue(key, process.env[envVarName]);
|
|
483
492
|
if (envValue === undefined) {
|
|
484
493
|
return;
|
|
485
494
|
}
|
|
486
|
-
if (key === 'COCO_SERVICE_PROVIDER' ||
|
|
495
|
+
if (key === 'COCO_SERVICE_PROVIDER' ||
|
|
496
|
+
key === 'COCO_SERVICE_MODEL' ||
|
|
497
|
+
key === 'OPEN_AI_KEY' ||
|
|
498
|
+
key === 'COCO_SERVICE_ENDPOINT' ||
|
|
499
|
+
key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
|
|
500
|
+
key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
|
|
501
|
+
key === 'COCO_SERVICE_FIELDS') {
|
|
487
502
|
// NOTE: We want to ensure that the service object is always defined
|
|
488
503
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
489
504
|
// @ts-ignore
|
|
@@ -510,9 +525,28 @@ function handleServiceEnvVar(service, key, value) {
|
|
|
510
525
|
break;
|
|
511
526
|
case 'OPEN_AI_KEY':
|
|
512
527
|
if (service.provider === 'openai') {
|
|
513
|
-
service.
|
|
528
|
+
service.authentication = {
|
|
529
|
+
type: 'APIKey',
|
|
530
|
+
credentials: {
|
|
531
|
+
apiKey: value,
|
|
532
|
+
},
|
|
533
|
+
};
|
|
514
534
|
}
|
|
515
535
|
break;
|
|
536
|
+
case 'COCO_SERVICE_ENDPOINT':
|
|
537
|
+
if (service.provider === 'ollama') {
|
|
538
|
+
service.endpoint = value;
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT':
|
|
542
|
+
service.requestOptions = { ...service.requestOptions, timeout: value };
|
|
543
|
+
break;
|
|
544
|
+
case 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES':
|
|
545
|
+
service.requestOptions = { ...service.requestOptions, maxRetries: value };
|
|
546
|
+
break;
|
|
547
|
+
case 'COCO_SERVICE_FIELDS':
|
|
548
|
+
service.fields = value;
|
|
549
|
+
break;
|
|
516
550
|
}
|
|
517
551
|
}
|
|
518
552
|
function parseEnvValue(key, value) {
|
|
@@ -531,6 +565,9 @@ function parseEnvValue(key, value) {
|
|
|
531
565
|
// Handle number values
|
|
532
566
|
case typeof value === 'string' && !isNaN(Number(value)):
|
|
533
567
|
return Number(value);
|
|
568
|
+
// Handle JSON strings
|
|
569
|
+
case typeof value === 'string' && value.startsWith('{'):
|
|
570
|
+
return JSON.parse(value);
|
|
534
571
|
default:
|
|
535
572
|
return value;
|
|
536
573
|
}
|
|
@@ -544,32 +581,40 @@ function toEnvVarName(key) {
|
|
|
544
581
|
}
|
|
545
582
|
return `COCO_${key.replace(/([A-Z])/g, '_$1').toLocaleUpperCase()}`;
|
|
546
583
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
584
|
+
const flattenObject = (obj, prefix = '') => {
|
|
585
|
+
let flattened = {};
|
|
586
|
+
for (const key in obj) {
|
|
587
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
588
|
+
const propName = prefix ? `${prefix}_${key}` : key;
|
|
589
|
+
const value = obj[key];
|
|
590
|
+
// Skip undefined or null values
|
|
591
|
+
if (value === undefined || value === null) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
595
|
+
// Handle nested objects, but specifically handle 'fields' as JSON string
|
|
596
|
+
if (key === 'fields') {
|
|
597
|
+
flattened[propName.toUpperCase()] = JSON.stringify(value);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
flattened = { ...flattened, ...flattenObject(value, propName) };
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
// For primitive types (string, number, boolean, symbol, bigint) and arrays
|
|
605
|
+
flattened[propName.toUpperCase()] = String(value);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
557
608
|
}
|
|
558
|
-
return
|
|
559
|
-
}
|
|
609
|
+
return flattened;
|
|
610
|
+
};
|
|
560
611
|
const appendToEnvFile = async (filePath, config) => {
|
|
561
612
|
const getNewContent = async () => {
|
|
562
|
-
|
|
613
|
+
const flattenedConfig = flattenObject(config);
|
|
614
|
+
return Object.entries(flattenedConfig)
|
|
563
615
|
.map(([key, value]) => {
|
|
564
|
-
if (key === 'service') {
|
|
565
|
-
const service = value;
|
|
566
|
-
return `${service.provider ? `COCO_SERVICE_PROVIDER=${service.provider}` : ''}\n${service.model ? `COCO_SERVICE_MODEL=${service.model}` : ''}\n${service.authentication.type === 'APIKey'
|
|
567
|
-
? `OPEN_AI_KEY=${service.authentication.credentials.apiKey}`
|
|
568
|
-
: ''}`;
|
|
569
|
-
}
|
|
570
616
|
const envVarName = toEnvVarName(key);
|
|
571
|
-
|
|
572
|
-
return `${envVarName}=${envValue}`;
|
|
617
|
+
return `${envVarName}=${value}`;
|
|
573
618
|
})
|
|
574
619
|
.join('\n');
|
|
575
620
|
};
|
|
@@ -593,22 +638,31 @@ function loadGitConfig(config) {
|
|
|
593
638
|
if (fs__namespace.existsSync(gitConfigPath)) {
|
|
594
639
|
const gitConfigRaw = fs__namespace.readFileSync(gitConfigPath, 'utf-8');
|
|
595
640
|
const gitConfigParsed = ini__namespace.parse(gitConfigRaw);
|
|
596
|
-
const gitConfigServiceObject = gitConfigParsed.coco?.service;
|
|
597
641
|
let service = config.service;
|
|
598
|
-
if (
|
|
599
|
-
|
|
600
|
-
|
|
642
|
+
if (gitConfigParsed.coco) {
|
|
643
|
+
service = {
|
|
644
|
+
provider: gitConfigParsed.coco?.['service.provider'],
|
|
645
|
+
model: gitConfigParsed.coco?.['service.model'],
|
|
646
|
+
authentication: {
|
|
647
|
+
type: 'APIKey',
|
|
648
|
+
credentials: {
|
|
649
|
+
apiKey: gitConfigParsed.coco?.['service.apiKey'],
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
requestOptions: {
|
|
653
|
+
timeout: Number(gitConfigParsed.coco?.['service.requestOptions.timeout']),
|
|
654
|
+
maxRetries: Number(gitConfigParsed.coco?.['service.requestOptions.maxRetries']),
|
|
655
|
+
},
|
|
656
|
+
endpoint: gitConfigParsed.coco?.['service.endpoint'],
|
|
657
|
+
fields: gitConfigParsed.coco?.['service.fields']
|
|
658
|
+
? JSON.parse(gitConfigParsed.coco?.['service.fields'])
|
|
659
|
+
: undefined,
|
|
660
|
+
};
|
|
601
661
|
}
|
|
602
662
|
config = {
|
|
603
663
|
...config,
|
|
664
|
+
...gitConfigParsed.coco,
|
|
604
665
|
service: service,
|
|
605
|
-
prompt: gitConfigParsed.coco?.prompt || config.prompt,
|
|
606
|
-
mode: gitConfigParsed.coco?.mode || config.mode,
|
|
607
|
-
summarizePrompt: gitConfigParsed.coco?.summarizePrompt || config.summarizePrompt,
|
|
608
|
-
ignoredFiles: gitConfigParsed.coco?.ignoredFiles || config.ignoredFiles,
|
|
609
|
-
ignoredExtensions: gitConfigParsed.coco?.ignoredExtensions || config.ignoredExtensions,
|
|
610
|
-
defaultBranch: gitConfigParsed.coco?.defaultBranch || config.defaultBranch,
|
|
611
|
-
verbose: gitConfigParsed.coco?.verbose || config.verbose,
|
|
612
666
|
};
|
|
613
667
|
}
|
|
614
668
|
return removeUndefined(config);
|
|
@@ -628,9 +682,28 @@ const appendToGitConfig = async (filePath, config) => {
|
|
|
628
682
|
const contentLines = [header];
|
|
629
683
|
for (const key in config) {
|
|
630
684
|
const value = config[key];
|
|
631
|
-
if (
|
|
632
|
-
|
|
633
|
-
contentLines.push(`\
|
|
685
|
+
if (key === 'service') {
|
|
686
|
+
const service = value;
|
|
687
|
+
contentLines.push(`\tservice.provider = ${service.provider}`);
|
|
688
|
+
contentLines.push(`\tservice.model = ${service.model}`);
|
|
689
|
+
if (service.authentication.type === 'APIKey') {
|
|
690
|
+
contentLines.push(`\tservice.apiKey = ${service.authentication.credentials.apiKey}`);
|
|
691
|
+
}
|
|
692
|
+
if (service.requestOptions?.timeout) {
|
|
693
|
+
contentLines.push(`\tservice.requestOptions.timeout = ${service.requestOptions.timeout}`);
|
|
694
|
+
}
|
|
695
|
+
if (service.requestOptions?.maxRetries) {
|
|
696
|
+
contentLines.push(`\tservice.requestOptions.maxRetries = ${service.requestOptions.maxRetries}`);
|
|
697
|
+
}
|
|
698
|
+
if (service.provider === 'ollama') {
|
|
699
|
+
const ollamaService = service;
|
|
700
|
+
if (ollamaService.endpoint) {
|
|
701
|
+
contentLines.push(` service.endpoint = ${ollamaService.endpoint}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (service.fields) {
|
|
705
|
+
contentLines.push(`\tservice.fields = ${JSON.stringify(service.fields)}`);
|
|
706
|
+
}
|
|
634
707
|
}
|
|
635
708
|
else if (typeof value === 'string' && value.includes('\n')) {
|
|
636
709
|
// Wrap strings with new lines in quotes
|
|
@@ -6080,7 +6153,6 @@ const executeChain = async ({ llm, prompt, variables, parser }) => {
|
|
|
6080
6153
|
try {
|
|
6081
6154
|
const chain = prompt.pipe(llm).pipe(parser);
|
|
6082
6155
|
const result = await chain.invoke(variables);
|
|
6083
|
-
console.debug('LLMChain call result:', result);
|
|
6084
6156
|
if (result === null || result === undefined) {
|
|
6085
6157
|
throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
|
|
6086
6158
|
}
|
|
@@ -10892,6 +10964,11 @@ const handler$3 = async (argv, logger) => {
|
|
|
10892
10964
|
else {
|
|
10893
10965
|
logger.setConfig({ silent: true });
|
|
10894
10966
|
}
|
|
10967
|
+
if (config.service.provider === 'ollama') {
|
|
10968
|
+
logger.verbose('⚠️ Ollama models may not strictly adhere to the output format instructions.', {
|
|
10969
|
+
color: 'yellow',
|
|
10970
|
+
});
|
|
10971
|
+
}
|
|
10895
10972
|
async function factory() {
|
|
10896
10973
|
const changes = await getChanges({
|
|
10897
10974
|
git,
|
|
@@ -10947,11 +11024,6 @@ const handler$3 = async (argv, logger) => {
|
|
|
10947
11024
|
variables: promptTemplate.inputVariables,
|
|
10948
11025
|
fallback: promptTemplate,
|
|
10949
11026
|
});
|
|
10950
|
-
if (config.service.provider === 'ollama') {
|
|
10951
|
-
logger.log('Note: Ollama models may not strictly adhere to the output format instructions.', {
|
|
10952
|
-
color: 'yellow',
|
|
10953
|
-
});
|
|
10954
|
-
}
|
|
10955
11027
|
// Get additional context if provided
|
|
10956
11028
|
let additional_context = '';
|
|
10957
11029
|
if (argv.additional) {
|
|
@@ -10992,7 +11064,7 @@ const handler$3 = async (argv, logger) => {
|
|
|
10992
11064
|
maxAttempts,
|
|
10993
11065
|
onRetry: (attempt, error) => {
|
|
10994
11066
|
logger.verbose(`Failed to parse commit message (attempt ${attempt}/${maxAttempts}): ${error.message}`, { color: 'yellow' });
|
|
10995
|
-
}
|
|
11067
|
+
},
|
|
10996
11068
|
},
|
|
10997
11069
|
fallbackParser: (text) => ({
|
|
10998
11070
|
title: text.split('\n')[0] || 'Auto-generated commit',
|
|
@@ -11362,6 +11434,32 @@ const questions = {
|
|
|
11362
11434
|
});
|
|
11363
11435
|
return parseFloat(temperature);
|
|
11364
11436
|
},
|
|
11437
|
+
inputOllamaEndpoint: async () => {
|
|
11438
|
+
return await prompts.input({
|
|
11439
|
+
message: 'Ollama endpoint (e.g., http://localhost:11434):',
|
|
11440
|
+
default: 'http://localhost:11434',
|
|
11441
|
+
});
|
|
11442
|
+
},
|
|
11443
|
+
inputRequestTimeout: async () => {
|
|
11444
|
+
const timeout = await prompts.input({
|
|
11445
|
+
message: 'Request timeout in milliseconds:',
|
|
11446
|
+
default: '30000',
|
|
11447
|
+
});
|
|
11448
|
+
return parseInt(timeout);
|
|
11449
|
+
},
|
|
11450
|
+
inputRequestMaxRetries: async () => {
|
|
11451
|
+
const maxRetries = await prompts.input({
|
|
11452
|
+
message: 'Maximum number of request retries:',
|
|
11453
|
+
default: '3',
|
|
11454
|
+
});
|
|
11455
|
+
return parseInt(maxRetries);
|
|
11456
|
+
},
|
|
11457
|
+
inputServiceFields: async () => {
|
|
11458
|
+
return await prompts.editor({
|
|
11459
|
+
message: 'Enter additional service fields as a JSON string (optional):',
|
|
11460
|
+
default: '{}',
|
|
11461
|
+
});
|
|
11462
|
+
},
|
|
11365
11463
|
selectDefaultGitBranch: async () => (await prompts.input({
|
|
11366
11464
|
message: 'default branch for the repository:',
|
|
11367
11465
|
default: 'main',
|
|
@@ -11455,6 +11553,29 @@ const handler$2 = async (argv, logger) => {
|
|
|
11455
11553
|
tokenLimit: await questions.inputTokenLimit(),
|
|
11456
11554
|
};
|
|
11457
11555
|
config.verbose = await questions.enableVerboseMode();
|
|
11556
|
+
if (llmProvider === 'ollama') {
|
|
11557
|
+
config.service.endpoint = await questions.inputOllamaEndpoint();
|
|
11558
|
+
}
|
|
11559
|
+
config.service.requestOptions = {
|
|
11560
|
+
timeout: await questions.inputRequestTimeout(),
|
|
11561
|
+
maxRetries: await questions.inputRequestMaxRetries(),
|
|
11562
|
+
};
|
|
11563
|
+
const promptForServiceFields = await prompts.confirm({
|
|
11564
|
+
message: 'would you like to configure additional service fields (advanced)?',
|
|
11565
|
+
default: false,
|
|
11566
|
+
});
|
|
11567
|
+
if (promptForServiceFields) {
|
|
11568
|
+
const fieldsJson = await questions.inputServiceFields();
|
|
11569
|
+
try {
|
|
11570
|
+
config.service.fields = JSON.parse(fieldsJson);
|
|
11571
|
+
}
|
|
11572
|
+
catch (e) {
|
|
11573
|
+
logger.log('Invalid JSON for service fields. Skipping.', { color: 'red' });
|
|
11574
|
+
logger.verbose(`Error parsing service fields: ${e.message}`, {
|
|
11575
|
+
color: 'red',
|
|
11576
|
+
});
|
|
11577
|
+
}
|
|
11578
|
+
}
|
|
11458
11579
|
const promptForIgnores = await prompts.confirm({
|
|
11459
11580
|
message: 'would you like to configure ignored files and extensions?',
|
|
11460
11581
|
default: false,
|