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.
@@ -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.17.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 = [...CONFIG_KEYS, 'COCO_SERVICE_PROVIDER', 'COCO_SERVICE_MODEL', 'OPEN_AI_KEY'];
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' || key === 'COCO_SERVICE_MODEL' || key === 'OPEN_AI_KEY') {
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.fields = { apiKey: value };
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
- function formatEnvValue(value) {
526
- if (typeof value === 'number') {
527
- return `${value}`;
528
- }
529
- else if (Array.isArray(value)) {
530
- return `${value.join(',')}`;
531
- }
532
- else if (typeof value === 'string') {
533
- // Escape newlines and tabs in strings
534
- return `${value.replace(/\n/g, '\\n').replace(/\t/g, '\\t')}`;
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 `${value}`;
537
- }
587
+ return flattened;
588
+ };
538
589
  const appendToEnvFile = async (filePath, config) => {
539
590
  const getNewContent = async () => {
540
- return Object.entries(config)
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
- const envValue = formatEnvValue(value);
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 (gitConfigServiceObject) {
577
- const gitServiceConfig = JSON.parse(gitConfigServiceObject);
578
- service = gitServiceConfig || config?.service;
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 (typeof value === 'object') {
610
- // Serialize object to JSON string
611
- contentLines.push(`\t${key} = ${JSON.stringify(value)}`);
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.17.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 = [...CONFIG_KEYS, 'COCO_SERVICE_PROVIDER', 'COCO_SERVICE_MODEL', 'OPEN_AI_KEY'];
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' || key === 'COCO_SERVICE_MODEL' || key === 'OPEN_AI_KEY') {
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.fields = { apiKey: value };
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
- function formatEnvValue(value) {
548
- if (typeof value === 'number') {
549
- return `${value}`;
550
- }
551
- else if (Array.isArray(value)) {
552
- return `${value.join(',')}`;
553
- }
554
- else if (typeof value === 'string') {
555
- // Escape newlines and tabs in strings
556
- return `${value.replace(/\n/g, '\\n').replace(/\t/g, '\\t')}`;
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 `${value}`;
559
- }
609
+ return flattened;
610
+ };
560
611
  const appendToEnvFile = async (filePath, config) => {
561
612
  const getNewContent = async () => {
562
- return Object.entries(config)
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
- const envValue = formatEnvValue(value);
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 (gitConfigServiceObject) {
599
- const gitServiceConfig = JSON.parse(gitConfigServiceObject);
600
- service = gitServiceConfig || config?.service;
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 (typeof value === 'object') {
632
- // Serialize object to JSON string
633
- contentLines.push(`\t${key} = ${JSON.stringify(value)}`);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-coco",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "zero-effort git commits with coco.",
5
5
  "author": "gfargo <ghfargo@gmail.com>",
6
6
  "license": "MIT",