git-coco 0.8.2 → 0.8.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.
Files changed (3) hide show
  1. package/dist/index.esm.mjs +423 -103
  2. package/dist/index.js +423 -103
  3. package/package.json +6 -4
@@ -10,6 +10,7 @@ import os__default from 'os';
10
10
  import * as path from 'path';
11
11
  import path__default from 'path';
12
12
  import * as ini from 'ini';
13
+ import Ajv from 'ajv';
13
14
  import ora from 'ora';
14
15
  import now from 'performance-now';
15
16
  import prettyMilliseconds from 'pretty-ms';
@@ -318,6 +319,318 @@ function loadIgnore(config) {
318
319
  return config;
319
320
  }
320
321
 
322
+ // this file is auto-generated by the 'build:schema' script
323
+ const SCHEMA_PUBLIC_URL = "http://git-co.co/schema.json";
324
+ const schema = {
325
+ "$id": "http://git-co.co/schema.json",
326
+ "$schema": "http://json-schema.org/draft-07/schema#",
327
+ "type": "object",
328
+ "properties": {
329
+ "service": {
330
+ "description": "The LLM provider to use",
331
+ "default": "openai",
332
+ "enum": [
333
+ "openai",
334
+ "ollama"
335
+ ]
336
+ },
337
+ "model": {
338
+ "type": "string",
339
+ "description": "The LLM model to use",
340
+ "default": "gpt-4o",
341
+ "oneOf": [
342
+ {
343
+ "if": {
344
+ "$ref": "#/definitions/is-openai"
345
+ },
346
+ "then": {
347
+ "enum": [
348
+ "gpt-3.5-turbo",
349
+ "gpt-3.5-turbo-16k",
350
+ "gpt-4",
351
+ "gpt-4-32k",
352
+ "gpt-4-turbo",
353
+ "gpt-4-turbo-preview",
354
+ "gpt-4o",
355
+ "gpt-4o-2024-05-13"
356
+ ]
357
+ }
358
+ },
359
+ {
360
+ "if": {
361
+ "$ref": "#/definitions/is-ollama"
362
+ },
363
+ "then": {
364
+ "enum": [
365
+ "orca-mini",
366
+ "orca-mini:13b",
367
+ "orca2",
368
+ "aya:8b",
369
+ "aya:35b",
370
+ "mistral",
371
+ "codegemma",
372
+ "codegemma:7b-code",
373
+ "codellama",
374
+ "llama2",
375
+ "llama2-uncensored",
376
+ "llama2:13b",
377
+ "llama2:70b",
378
+ "llama3",
379
+ "llama3:70b",
380
+ "phi3",
381
+ "phi3:mini",
382
+ "phi3:medium",
383
+ "qwen2",
384
+ "qwen2:1.5b",
385
+ "qwen2:0.5b"
386
+ ]
387
+ }
388
+ }
389
+ ]
390
+ },
391
+ "tokenLimit": {
392
+ "type": "number",
393
+ "description": "Maximum number of tokens for the commit message",
394
+ "default": 500
395
+ },
396
+ "verbose": {
397
+ "type": "boolean",
398
+ "description": "Verbose output",
399
+ "default": false
400
+ },
401
+ "prompt": {
402
+ "type": "string",
403
+ "description": "Prompt for the LLM service",
404
+ "default": "What are the changes in this commit?"
405
+ },
406
+ "temperature": {
407
+ "type": "number",
408
+ "description": "Controls randomness in GPT-3 output. Lower values yield focused output; higher values offer diversity",
409
+ "default": 0.4
410
+ },
411
+ "mode": {
412
+ "type": "string",
413
+ "description": "Preferred output method for generated commit messages",
414
+ "enum": [
415
+ "stdout",
416
+ "interactive"
417
+ ],
418
+ "default": "stdout"
419
+ },
420
+ "summarizePrompt": {
421
+ "type": "string",
422
+ "description": "GPT-3 prompt for summarizing large files",
423
+ "default": "Summarize the changes in this large file:"
424
+ },
425
+ "ignoredFiles": {
426
+ "type": "array",
427
+ "description": "Paths of files to be excluded when generating commit messages",
428
+ "items": {
429
+ "type": "string"
430
+ },
431
+ "default": [
432
+ "package-lock.json"
433
+ ]
434
+ },
435
+ "ignoredExtensions": {
436
+ "type": "array",
437
+ "description": "File extensions to be excluded when generating commit messages",
438
+ "items": {
439
+ "type": "string"
440
+ },
441
+ "default": [
442
+ ".map",
443
+ ".lock"
444
+ ]
445
+ },
446
+ "defaultBranch": {
447
+ "type": "string",
448
+ "description": "Default branch for the repository",
449
+ "default": "main"
450
+ }
451
+ },
452
+ "required": [
453
+ "service",
454
+ "model"
455
+ ],
456
+ "allOf": [
457
+ {
458
+ "if": {
459
+ "properties": {
460
+ "service": {
461
+ "const": "openai"
462
+ }
463
+ }
464
+ },
465
+ "then": {
466
+ "properties": {
467
+ "model": {
468
+ "enum": [
469
+ "gpt-3.5-turbo",
470
+ "gpt-3.5-turbo-16k",
471
+ "gpt-4",
472
+ "gpt-4-32k",
473
+ "gpt-4-turbo",
474
+ "gpt-4-turbo-preview",
475
+ "gpt-4o",
476
+ "gpt-4o-2024-05-13"
477
+ ]
478
+ },
479
+ "openAIApiKey": {
480
+ "type": "string",
481
+ "description": "Your OpenAI API key",
482
+ "default": null
483
+ },
484
+ "endpoint": false
485
+ },
486
+ "required": [
487
+ "openAIApiKey"
488
+ ],
489
+ "not": {
490
+ "required": [
491
+ "endpoint"
492
+ ]
493
+ }
494
+ }
495
+ },
496
+ {
497
+ "if": {
498
+ "properties": {
499
+ "service": {
500
+ "const": "ollama"
501
+ }
502
+ }
503
+ },
504
+ "then": {
505
+ "properties": {
506
+ "model": {
507
+ "enum": [
508
+ "orca-mini",
509
+ "orca-mini:13b",
510
+ "orca2",
511
+ "aya:8b",
512
+ "aya:35b",
513
+ "mistral",
514
+ "codegemma",
515
+ "codegemma:7b-code",
516
+ "codellama",
517
+ "llama2",
518
+ "llama2-uncensored",
519
+ "llama2:13b",
520
+ "llama2:70b",
521
+ "llama3",
522
+ "llama3:70b",
523
+ "phi3",
524
+ "phi3:mini",
525
+ "phi3:medium",
526
+ "qwen2",
527
+ "qwen2:1.5b",
528
+ "qwen2:0.5b"
529
+ ]
530
+ },
531
+ "openAIApiKey": false,
532
+ "endpoint": {
533
+ "type": "string",
534
+ "description": "The endpoint to use for the LLM service"
535
+ }
536
+ },
537
+ "not": {
538
+ "required": [
539
+ "openAIApiKey"
540
+ ]
541
+ },
542
+ "required": [
543
+ "endpoint"
544
+ ]
545
+ }
546
+ }
547
+ ],
548
+ "definitions": {
549
+ "is-openai": {
550
+ "properties": {
551
+ "service": {
552
+ "enum": [
553
+ "openai"
554
+ ]
555
+ }
556
+ },
557
+ "required": [
558
+ "service"
559
+ ]
560
+ },
561
+ "is-ollama": {
562
+ "properties": {
563
+ "service": {
564
+ "enum": [
565
+ "ollama"
566
+ ]
567
+ }
568
+ },
569
+ "required": [
570
+ "service"
571
+ ]
572
+ },
573
+ "ollama-requires-endpoint": {
574
+ "anyOf": [
575
+ {
576
+ "not": {
577
+ "$ref": "#/definitions/is-openai"
578
+ }
579
+ },
580
+ {
581
+ "required": [
582
+ "endpoint"
583
+ ]
584
+ }
585
+ ]
586
+ },
587
+ "openai-models": {
588
+ "enum": [
589
+ "gpt-3.5-turbo",
590
+ "gpt-3.5-turbo-16k",
591
+ "gpt-4",
592
+ "gpt-4-32k",
593
+ "gpt-4-turbo",
594
+ "gpt-4-turbo-preview",
595
+ "gpt-4o",
596
+ "gpt-4o-2024-05-13"
597
+ ]
598
+ },
599
+ "ollama-models": {
600
+ "enum": [
601
+ "orca-mini",
602
+ "orca-mini:13b",
603
+ "orca2",
604
+ "aya:8b",
605
+ "aya:35b",
606
+ "mistral",
607
+ "codegemma",
608
+ "codegemma:7b-code",
609
+ "codellama",
610
+ "llama2",
611
+ "llama2-uncensored",
612
+ "llama2:13b",
613
+ "llama2:70b",
614
+ "llama3",
615
+ "llama3:70b",
616
+ "phi3",
617
+ "phi3:mini",
618
+ "phi3:medium",
619
+ "qwen2",
620
+ "qwen2:1.5b",
621
+ "qwen2:0.5b"
622
+ ]
623
+ }
624
+ }
625
+ };
626
+
627
+ const ajv = new Ajv({
628
+ allErrors: true,
629
+ verbose: true,
630
+ strictTypes: false,
631
+ });
632
+
633
+ const validate = ajv.compile(schema);
321
634
  /**
322
635
  * Load project config
323
636
  *
@@ -325,11 +638,13 @@ function loadIgnore(config) {
325
638
  * @returns {Config} Updated config
326
639
  **/
327
640
  function loadProjectJsonConfig(config) {
328
- // TODO: Add validation based of JSON schema?
329
- // @see https://github.com/acornejo/jjv
330
641
  if (fs.existsSync('.coco.config.json')) {
331
642
  const projectConfig = JSON.parse(fs.readFileSync('.coco.config.json', 'utf-8'));
332
643
  config = { ...config, ...projectConfig };
644
+ const isProjectConfigValid = validate(config);
645
+ if (!isProjectConfigValid) {
646
+ throw new Error('Invalid project config', { cause: validate.errors });
647
+ }
333
648
  }
334
649
  return config;
335
650
  }
@@ -338,7 +653,7 @@ const appendToProjectJsonConfig = (filePath, config) => {
338
653
  fs.writeFileSync(filePath, '{}');
339
654
  }
340
655
  fs.writeFileSync(filePath, JSON.stringify({
341
- $schema: 'https://git-co.co/schema.json',
656
+ $schema: SCHEMA_PUBLIC_URL,
342
657
  ...config,
343
658
  }, null, 2));
344
659
  };
@@ -359,6 +674,106 @@ function loadXDGConfig(config) {
359
674
  return config;
360
675
  }
361
676
 
677
+ const DEFAULT_OLLAMA_LLM_SERVICE = {
678
+ provider: 'ollama',
679
+ model: 'codellama',
680
+ endpoint: 'http://localhost:11434',
681
+ maxConcurrent: 1,
682
+ tokenLimit: 1024,
683
+ };
684
+ const DEFAULT_OPENAI_LLM_SERVICE = {
685
+ provider: 'openai',
686
+ model: 'gpt-4',
687
+ authentication: {
688
+ type: 'APIKey',
689
+ credentials: {
690
+ apiKey: '',
691
+ },
692
+ },
693
+ tokenLimit: 1024,
694
+ };
695
+
696
+ /**
697
+ * Retrieves the provider and model from the given configuration object.
698
+ * @param config The configuration object.
699
+ * @returns An object containing the provider and model.
700
+ * @throws Error if the configuration is invalid or missing required properties.
701
+ */
702
+ function getModelAndProviderFromConfig(config) {
703
+ if (!config.service) {
704
+ throw new Error('Invalid service: undefined');
705
+ }
706
+ let result;
707
+ switch (typeof config.service) {
708
+ case 'string':
709
+ result = getDefaultServiceConfigFromAlias(config.service, config?.model);
710
+ break;
711
+ case 'object':
712
+ default:
713
+ result = config.service;
714
+ break;
715
+ }
716
+ const { provider, model } = result;
717
+ if (!model || !provider) {
718
+ throw new Error(`Invalid service: ${config.service}`);
719
+ }
720
+ return { provider, model };
721
+ }
722
+ /**
723
+ * Retrieve appropriate API key based on selected model
724
+ * @param service
725
+ * @param options
726
+ * @returns API Key
727
+ */
728
+ function getApiKeyForModel(config) {
729
+ const { provider } = getModelAndProviderFromConfig(config);
730
+ switch (provider) {
731
+ case 'openai':
732
+ return config.openAIApiKey || getDefaultServiceApiKey(config);
733
+ default:
734
+ return getDefaultServiceApiKey(config);
735
+ }
736
+ }
737
+ /**
738
+ * Retrieves the default service API key from the given configuration.
739
+ * @param config The configuration object.
740
+ * @returns The default service API key.
741
+ */
742
+ function getDefaultServiceApiKey(config) {
743
+ const service = config.service;
744
+ if (service.authentication.type === 'APIKey') {
745
+ return service.authentication.credentials?.apiKey;
746
+ }
747
+ else if (service.authentication.type === 'OAuth') {
748
+ return service.authentication.credentials?.token;
749
+ }
750
+ return '';
751
+ }
752
+ /**
753
+ * Retrieves the default service configuration based on the provided alias and optional model.
754
+ * @param alias - The alias of the service.
755
+ * @param model - The optional model to be used.
756
+ * @returns The default service configuration.
757
+ * @throws Error if the alias is invalid or undefined.
758
+ */
759
+ function getDefaultServiceConfigFromAlias(alias, model) {
760
+ if (!alias) {
761
+ throw new Error('Invalid alias: undefined');
762
+ }
763
+ switch (alias) {
764
+ case 'openai':
765
+ return {
766
+ ...DEFAULT_OPENAI_LLM_SERVICE,
767
+ model: model || DEFAULT_OPENAI_LLM_SERVICE.model,
768
+ };
769
+ case 'ollama':
770
+ return {
771
+ ...DEFAULT_OLLAMA_LLM_SERVICE,
772
+ model: model || DEFAULT_OLLAMA_LLM_SERVICE.model,
773
+ };
774
+ }
775
+ }
776
+
362
777
  /**
363
778
  * Load application config
364
779
  *
@@ -379,6 +794,11 @@ function loadXDGConfig(config) {
379
794
  function loadConfig(argv = {}) {
380
795
  // Default config
381
796
  let config = DEFAULT_CONFIG;
797
+ const { model } = getDefaultServiceConfigFromAlias(config.service, config.model);
798
+ config = {
799
+ model: model,
800
+ ...config,
801
+ };
382
802
  config = loadGitignore(config);
383
803
  config = loadIgnore(config);
384
804
  config = loadXDGConfig(config);
@@ -823,106 +1243,6 @@ const COMMIT_PROMPT = new PromptTemplate({
823
1243
  inputVariables: inputVariables$1,
824
1244
  });
825
1245
 
826
- const DEFAULT_OLLAMA_LLM_SERVICE = {
827
- provider: 'ollama',
828
- model: 'codellama',
829
- endpoint: 'http://localhost:11434',
830
- maxConcurrent: 1,
831
- tokenLimit: 1024,
832
- };
833
- const DEFAULT_OPENAI_LLM_SERVICE = {
834
- provider: 'openai',
835
- model: 'gpt-4',
836
- authentication: {
837
- type: 'APIKey',
838
- credentials: {
839
- apiKey: '',
840
- },
841
- },
842
- tokenLimit: 1024,
843
- };
844
-
845
- /**
846
- * Retrieves the provider and model from the given configuration object.
847
- * @param config The configuration object.
848
- * @returns An object containing the provider and model.
849
- * @throws Error if the configuration is invalid or missing required properties.
850
- */
851
- function getModelAndProviderFromConfig(config) {
852
- if (!config.service) {
853
- throw new Error('Invalid service: undefined');
854
- }
855
- let result;
856
- switch (typeof config.service) {
857
- case 'string':
858
- result = getDefaultServiceConfigFromAlias(config.service, config?.model);
859
- break;
860
- case 'object':
861
- default:
862
- result = config.service;
863
- break;
864
- }
865
- const { provider, model } = result;
866
- if (!model || !provider) {
867
- throw new Error(`Invalid service: ${config.service}`);
868
- }
869
- return { provider, model };
870
- }
871
- /**
872
- * Retrieve appropriate API key based on selected model
873
- * @param service
874
- * @param options
875
- * @returns API Key
876
- */
877
- function getApiKeyForModel(config) {
878
- const { provider } = getModelAndProviderFromConfig(config);
879
- switch (provider) {
880
- case 'openai':
881
- return config.openAIApiKey || getDefaultServiceApiKey(config);
882
- default:
883
- return getDefaultServiceApiKey(config);
884
- }
885
- }
886
- /**
887
- * Retrieves the default service API key from the given configuration.
888
- * @param config The configuration object.
889
- * @returns The default service API key.
890
- */
891
- function getDefaultServiceApiKey(config) {
892
- const service = config.service;
893
- if (service.authentication.type === 'APIKey') {
894
- return service.authentication.credentials?.apiKey;
895
- }
896
- else if (service.authentication.type === 'OAuth') {
897
- return service.authentication.credentials?.token;
898
- }
899
- return '';
900
- }
901
- /**
902
- * Retrieves the default service configuration based on the provided alias and optional model.
903
- * @param alias - The alias of the service.
904
- * @param model - The optional model to be used.
905
- * @returns The default service configuration.
906
- * @throws Error if the alias is invalid or undefined.
907
- */
908
- function getDefaultServiceConfigFromAlias(alias, model) {
909
- if (!alias) {
910
- throw new Error('Invalid alias: undefined');
911
- }
912
- switch (alias) {
913
- case 'openai':
914
- return {
915
- ...DEFAULT_OPENAI_LLM_SERVICE,
916
- model: model || DEFAULT_OPENAI_LLM_SERVICE.model,
917
- };
918
- case 'ollama':
919
- return {
920
- ...DEFAULT_OLLAMA_LLM_SERVICE,
921
- model: model || DEFAULT_OLLAMA_LLM_SERVICE.model,
922
- };
923
- }
924
- }
925
-
926
1246
  /**
927
1247
  * Get LLM Model Based on Configuration
928
1248
  *
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ var chalk = require('chalk');
9
9
  var os = require('os');
10
10
  var path = require('path');
11
11
  var ini = require('ini');
12
+ var Ajv = require('ajv');
12
13
  var ora = require('ora');
13
14
  var now = require('performance-now');
14
15
  var prettyMilliseconds = require('pretty-ms');
@@ -339,6 +340,318 @@ function loadIgnore(config) {
339
340
  return config;
340
341
  }
341
342
 
343
+ // this file is auto-generated by the 'build:schema' script
344
+ const SCHEMA_PUBLIC_URL = "http://git-co.co/schema.json";
345
+ const schema = {
346
+ "$id": "http://git-co.co/schema.json",
347
+ "$schema": "http://json-schema.org/draft-07/schema#",
348
+ "type": "object",
349
+ "properties": {
350
+ "service": {
351
+ "description": "The LLM provider to use",
352
+ "default": "openai",
353
+ "enum": [
354
+ "openai",
355
+ "ollama"
356
+ ]
357
+ },
358
+ "model": {
359
+ "type": "string",
360
+ "description": "The LLM model to use",
361
+ "default": "gpt-4o",
362
+ "oneOf": [
363
+ {
364
+ "if": {
365
+ "$ref": "#/definitions/is-openai"
366
+ },
367
+ "then": {
368
+ "enum": [
369
+ "gpt-3.5-turbo",
370
+ "gpt-3.5-turbo-16k",
371
+ "gpt-4",
372
+ "gpt-4-32k",
373
+ "gpt-4-turbo",
374
+ "gpt-4-turbo-preview",
375
+ "gpt-4o",
376
+ "gpt-4o-2024-05-13"
377
+ ]
378
+ }
379
+ },
380
+ {
381
+ "if": {
382
+ "$ref": "#/definitions/is-ollama"
383
+ },
384
+ "then": {
385
+ "enum": [
386
+ "orca-mini",
387
+ "orca-mini:13b",
388
+ "orca2",
389
+ "aya:8b",
390
+ "aya:35b",
391
+ "mistral",
392
+ "codegemma",
393
+ "codegemma:7b-code",
394
+ "codellama",
395
+ "llama2",
396
+ "llama2-uncensored",
397
+ "llama2:13b",
398
+ "llama2:70b",
399
+ "llama3",
400
+ "llama3:70b",
401
+ "phi3",
402
+ "phi3:mini",
403
+ "phi3:medium",
404
+ "qwen2",
405
+ "qwen2:1.5b",
406
+ "qwen2:0.5b"
407
+ ]
408
+ }
409
+ }
410
+ ]
411
+ },
412
+ "tokenLimit": {
413
+ "type": "number",
414
+ "description": "Maximum number of tokens for the commit message",
415
+ "default": 500
416
+ },
417
+ "verbose": {
418
+ "type": "boolean",
419
+ "description": "Verbose output",
420
+ "default": false
421
+ },
422
+ "prompt": {
423
+ "type": "string",
424
+ "description": "Prompt for the LLM service",
425
+ "default": "What are the changes in this commit?"
426
+ },
427
+ "temperature": {
428
+ "type": "number",
429
+ "description": "Controls randomness in GPT-3 output. Lower values yield focused output; higher values offer diversity",
430
+ "default": 0.4
431
+ },
432
+ "mode": {
433
+ "type": "string",
434
+ "description": "Preferred output method for generated commit messages",
435
+ "enum": [
436
+ "stdout",
437
+ "interactive"
438
+ ],
439
+ "default": "stdout"
440
+ },
441
+ "summarizePrompt": {
442
+ "type": "string",
443
+ "description": "GPT-3 prompt for summarizing large files",
444
+ "default": "Summarize the changes in this large file:"
445
+ },
446
+ "ignoredFiles": {
447
+ "type": "array",
448
+ "description": "Paths of files to be excluded when generating commit messages",
449
+ "items": {
450
+ "type": "string"
451
+ },
452
+ "default": [
453
+ "package-lock.json"
454
+ ]
455
+ },
456
+ "ignoredExtensions": {
457
+ "type": "array",
458
+ "description": "File extensions to be excluded when generating commit messages",
459
+ "items": {
460
+ "type": "string"
461
+ },
462
+ "default": [
463
+ ".map",
464
+ ".lock"
465
+ ]
466
+ },
467
+ "defaultBranch": {
468
+ "type": "string",
469
+ "description": "Default branch for the repository",
470
+ "default": "main"
471
+ }
472
+ },
473
+ "required": [
474
+ "service",
475
+ "model"
476
+ ],
477
+ "allOf": [
478
+ {
479
+ "if": {
480
+ "properties": {
481
+ "service": {
482
+ "const": "openai"
483
+ }
484
+ }
485
+ },
486
+ "then": {
487
+ "properties": {
488
+ "model": {
489
+ "enum": [
490
+ "gpt-3.5-turbo",
491
+ "gpt-3.5-turbo-16k",
492
+ "gpt-4",
493
+ "gpt-4-32k",
494
+ "gpt-4-turbo",
495
+ "gpt-4-turbo-preview",
496
+ "gpt-4o",
497
+ "gpt-4o-2024-05-13"
498
+ ]
499
+ },
500
+ "openAIApiKey": {
501
+ "type": "string",
502
+ "description": "Your OpenAI API key",
503
+ "default": null
504
+ },
505
+ "endpoint": false
506
+ },
507
+ "required": [
508
+ "openAIApiKey"
509
+ ],
510
+ "not": {
511
+ "required": [
512
+ "endpoint"
513
+ ]
514
+ }
515
+ }
516
+ },
517
+ {
518
+ "if": {
519
+ "properties": {
520
+ "service": {
521
+ "const": "ollama"
522
+ }
523
+ }
524
+ },
525
+ "then": {
526
+ "properties": {
527
+ "model": {
528
+ "enum": [
529
+ "orca-mini",
530
+ "orca-mini:13b",
531
+ "orca2",
532
+ "aya:8b",
533
+ "aya:35b",
534
+ "mistral",
535
+ "codegemma",
536
+ "codegemma:7b-code",
537
+ "codellama",
538
+ "llama2",
539
+ "llama2-uncensored",
540
+ "llama2:13b",
541
+ "llama2:70b",
542
+ "llama3",
543
+ "llama3:70b",
544
+ "phi3",
545
+ "phi3:mini",
546
+ "phi3:medium",
547
+ "qwen2",
548
+ "qwen2:1.5b",
549
+ "qwen2:0.5b"
550
+ ]
551
+ },
552
+ "openAIApiKey": false,
553
+ "endpoint": {
554
+ "type": "string",
555
+ "description": "The endpoint to use for the LLM service"
556
+ }
557
+ },
558
+ "not": {
559
+ "required": [
560
+ "openAIApiKey"
561
+ ]
562
+ },
563
+ "required": [
564
+ "endpoint"
565
+ ]
566
+ }
567
+ }
568
+ ],
569
+ "definitions": {
570
+ "is-openai": {
571
+ "properties": {
572
+ "service": {
573
+ "enum": [
574
+ "openai"
575
+ ]
576
+ }
577
+ },
578
+ "required": [
579
+ "service"
580
+ ]
581
+ },
582
+ "is-ollama": {
583
+ "properties": {
584
+ "service": {
585
+ "enum": [
586
+ "ollama"
587
+ ]
588
+ }
589
+ },
590
+ "required": [
591
+ "service"
592
+ ]
593
+ },
594
+ "ollama-requires-endpoint": {
595
+ "anyOf": [
596
+ {
597
+ "not": {
598
+ "$ref": "#/definitions/is-openai"
599
+ }
600
+ },
601
+ {
602
+ "required": [
603
+ "endpoint"
604
+ ]
605
+ }
606
+ ]
607
+ },
608
+ "openai-models": {
609
+ "enum": [
610
+ "gpt-3.5-turbo",
611
+ "gpt-3.5-turbo-16k",
612
+ "gpt-4",
613
+ "gpt-4-32k",
614
+ "gpt-4-turbo",
615
+ "gpt-4-turbo-preview",
616
+ "gpt-4o",
617
+ "gpt-4o-2024-05-13"
618
+ ]
619
+ },
620
+ "ollama-models": {
621
+ "enum": [
622
+ "orca-mini",
623
+ "orca-mini:13b",
624
+ "orca2",
625
+ "aya:8b",
626
+ "aya:35b",
627
+ "mistral",
628
+ "codegemma",
629
+ "codegemma:7b-code",
630
+ "codellama",
631
+ "llama2",
632
+ "llama2-uncensored",
633
+ "llama2:13b",
634
+ "llama2:70b",
635
+ "llama3",
636
+ "llama3:70b",
637
+ "phi3",
638
+ "phi3:mini",
639
+ "phi3:medium",
640
+ "qwen2",
641
+ "qwen2:1.5b",
642
+ "qwen2:0.5b"
643
+ ]
644
+ }
645
+ }
646
+ };
647
+
648
+ const ajv = new Ajv({
649
+ allErrors: true,
650
+ verbose: true,
651
+ strictTypes: false,
652
+ });
653
+
654
+ const validate = ajv.compile(schema);
342
655
  /**
343
656
  * Load project config
344
657
  *
@@ -346,11 +659,13 @@ function loadIgnore(config) {
346
659
  * @returns {Config} Updated config
347
660
  **/
348
661
  function loadProjectJsonConfig(config) {
349
- // TODO: Add validation based of JSON schema?
350
- // @see https://github.com/acornejo/jjv
351
662
  if (fs__namespace.existsSync('.coco.config.json')) {
352
663
  const projectConfig = JSON.parse(fs__namespace.readFileSync('.coco.config.json', 'utf-8'));
353
664
  config = { ...config, ...projectConfig };
665
+ const isProjectConfigValid = validate(config);
666
+ if (!isProjectConfigValid) {
667
+ throw new Error('Invalid project config', { cause: validate.errors });
668
+ }
354
669
  }
355
670
  return config;
356
671
  }
@@ -359,7 +674,7 @@ const appendToProjectJsonConfig = (filePath, config) => {
359
674
  fs__namespace.writeFileSync(filePath, '{}');
360
675
  }
361
676
  fs__namespace.writeFileSync(filePath, JSON.stringify({
362
- $schema: 'https://git-co.co/schema.json',
677
+ $schema: SCHEMA_PUBLIC_URL,
363
678
  ...config,
364
679
  }, null, 2));
365
680
  };
@@ -380,6 +695,106 @@ function loadXDGConfig(config) {
380
695
  return config;
381
696
  }
382
697
 
698
+ const DEFAULT_OLLAMA_LLM_SERVICE = {
699
+ provider: 'ollama',
700
+ model: 'codellama',
701
+ endpoint: 'http://localhost:11434',
702
+ maxConcurrent: 1,
703
+ tokenLimit: 1024,
704
+ };
705
+ const DEFAULT_OPENAI_LLM_SERVICE = {
706
+ provider: 'openai',
707
+ model: 'gpt-4',
708
+ authentication: {
709
+ type: 'APIKey',
710
+ credentials: {
711
+ apiKey: '',
712
+ },
713
+ },
714
+ tokenLimit: 1024,
715
+ };
716
+
717
+ /**
718
+ * Retrieves the provider and model from the given configuration object.
719
+ * @param config The configuration object.
720
+ * @returns An object containing the provider and model.
721
+ * @throws Error if the configuration is invalid or missing required properties.
722
+ */
723
+ function getModelAndProviderFromConfig(config) {
724
+ if (!config.service) {
725
+ throw new Error('Invalid service: undefined');
726
+ }
727
+ let result;
728
+ switch (typeof config.service) {
729
+ case 'string':
730
+ result = getDefaultServiceConfigFromAlias(config.service, config?.model);
731
+ break;
732
+ case 'object':
733
+ default:
734
+ result = config.service;
735
+ break;
736
+ }
737
+ const { provider, model } = result;
738
+ if (!model || !provider) {
739
+ throw new Error(`Invalid service: ${config.service}`);
740
+ }
741
+ return { provider, model };
742
+ }
743
+ /**
744
+ * Retrieve appropriate API key based on selected model
745
+ * @param service
746
+ * @param options
747
+ * @returns API Key
748
+ */
749
+ function getApiKeyForModel(config) {
750
+ const { provider } = getModelAndProviderFromConfig(config);
751
+ switch (provider) {
752
+ case 'openai':
753
+ return config.openAIApiKey || getDefaultServiceApiKey(config);
754
+ default:
755
+ return getDefaultServiceApiKey(config);
756
+ }
757
+ }
758
+ /**
759
+ * Retrieves the default service API key from the given configuration.
760
+ * @param config The configuration object.
761
+ * @returns The default service API key.
762
+ */
763
+ function getDefaultServiceApiKey(config) {
764
+ const service = config.service;
765
+ if (service.authentication.type === 'APIKey') {
766
+ return service.authentication.credentials?.apiKey;
767
+ }
768
+ else if (service.authentication.type === 'OAuth') {
769
+ return service.authentication.credentials?.token;
770
+ }
771
+ return '';
772
+ }
773
+ /**
774
+ * Retrieves the default service configuration based on the provided alias and optional model.
775
+ * @param alias - The alias of the service.
776
+ * @param model - The optional model to be used.
777
+ * @returns The default service configuration.
778
+ * @throws Error if the alias is invalid or undefined.
779
+ */
780
+ function getDefaultServiceConfigFromAlias(alias, model) {
781
+ if (!alias) {
782
+ throw new Error('Invalid alias: undefined');
783
+ }
784
+ switch (alias) {
785
+ case 'openai':
786
+ return {
787
+ ...DEFAULT_OPENAI_LLM_SERVICE,
788
+ model: model || DEFAULT_OPENAI_LLM_SERVICE.model,
789
+ };
790
+ case 'ollama':
791
+ return {
792
+ ...DEFAULT_OLLAMA_LLM_SERVICE,
793
+ model: model || DEFAULT_OLLAMA_LLM_SERVICE.model,
794
+ };
795
+ }
796
+ }
797
+
383
798
  /**
384
799
  * Load application config
385
800
  *
@@ -400,6 +815,11 @@ function loadXDGConfig(config) {
400
815
  function loadConfig(argv = {}) {
401
816
  // Default config
402
817
  let config = DEFAULT_CONFIG;
818
+ const { model } = getDefaultServiceConfigFromAlias(config.service, config.model);
819
+ config = {
820
+ model: model,
821
+ ...config,
822
+ };
403
823
  config = loadGitignore(config);
404
824
  config = loadIgnore(config);
405
825
  config = loadXDGConfig(config);
@@ -844,106 +1264,6 @@ const COMMIT_PROMPT = new prompts.PromptTemplate({
844
1264
  inputVariables: inputVariables$1,
845
1265
  });
846
1266
 
847
- const DEFAULT_OLLAMA_LLM_SERVICE = {
848
- provider: 'ollama',
849
- model: 'codellama',
850
- endpoint: 'http://localhost:11434',
851
- maxConcurrent: 1,
852
- tokenLimit: 1024,
853
- };
854
- const DEFAULT_OPENAI_LLM_SERVICE = {
855
- provider: 'openai',
856
- model: 'gpt-4',
857
- authentication: {
858
- type: 'APIKey',
859
- credentials: {
860
- apiKey: '',
861
- },
862
- },
863
- tokenLimit: 1024,
864
- };
865
-
866
- /**
867
- * Retrieves the provider and model from the given configuration object.
868
- * @param config The configuration object.
869
- * @returns An object containing the provider and model.
870
- * @throws Error if the configuration is invalid or missing required properties.
871
- */
872
- function getModelAndProviderFromConfig(config) {
873
- if (!config.service) {
874
- throw new Error('Invalid service: undefined');
875
- }
876
- let result;
877
- switch (typeof config.service) {
878
- case 'string':
879
- result = getDefaultServiceConfigFromAlias(config.service, config?.model);
880
- break;
881
- case 'object':
882
- default:
883
- result = config.service;
884
- break;
885
- }
886
- const { provider, model } = result;
887
- if (!model || !provider) {
888
- throw new Error(`Invalid service: ${config.service}`);
889
- }
890
- return { provider, model };
891
- }
892
- /**
893
- * Retrieve appropriate API key based on selected model
894
- * @param service
895
- * @param options
896
- * @returns API Key
897
- */
898
- function getApiKeyForModel(config) {
899
- const { provider } = getModelAndProviderFromConfig(config);
900
- switch (provider) {
901
- case 'openai':
902
- return config.openAIApiKey || getDefaultServiceApiKey(config);
903
- default:
904
- return getDefaultServiceApiKey(config);
905
- }
906
- }
907
- /**
908
- * Retrieves the default service API key from the given configuration.
909
- * @param config The configuration object.
910
- * @returns The default service API key.
911
- */
912
- function getDefaultServiceApiKey(config) {
913
- const service = config.service;
914
- if (service.authentication.type === 'APIKey') {
915
- return service.authentication.credentials?.apiKey;
916
- }
917
- else if (service.authentication.type === 'OAuth') {
918
- return service.authentication.credentials?.token;
919
- }
920
- return '';
921
- }
922
- /**
923
- * Retrieves the default service configuration based on the provided alias and optional model.
924
- * @param alias - The alias of the service.
925
- * @param model - The optional model to be used.
926
- * @returns The default service configuration.
927
- * @throws Error if the alias is invalid or undefined.
928
- */
929
- function getDefaultServiceConfigFromAlias(alias, model) {
930
- if (!alias) {
931
- throw new Error('Invalid alias: undefined');
932
- }
933
- switch (alias) {
934
- case 'openai':
935
- return {
936
- ...DEFAULT_OPENAI_LLM_SERVICE,
937
- model: model || DEFAULT_OPENAI_LLM_SERVICE.model,
938
- };
939
- case 'ollama':
940
- return {
941
- ...DEFAULT_OLLAMA_LLM_SERVICE,
942
- model: model || DEFAULT_OLLAMA_LLM_SERVICE.model,
943
- };
944
- }
945
- }
946
-
947
1267
  /**
948
1268
  * Get LLM Model Based on Configuration
949
1269
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-coco",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "zero-effort git commits with coco.",
5
5
  "author": "gfargo <ghfargo@gmail.com>",
6
6
  "license": "MIT",
@@ -26,7 +26,7 @@
26
26
  "start": "npm run dev",
27
27
  "dev": "tsx watch src/index.ts",
28
28
  "build": "rollup -c",
29
- "build:schema": "node bin/generateSchema.js",
29
+ "build:schema": "node bin/generateSchema.mjs",
30
30
  "build:watch": "rollup -c -w",
31
31
  "clean": "rimraf dist && rimraf coverage",
32
32
  "lint": "eslint src",
@@ -60,7 +60,7 @@
60
60
  "@typescript-eslint/parser": "^7.13.1",
61
61
  "eslint": "^8.54.0",
62
62
  "eslint-formatter-pretty": "^6.0.0",
63
- "jest": "^29.5.0",
63
+ "jest": "^29.7.0",
64
64
  "jest-mock": "^29.5.0",
65
65
  "release-it": "^17.0.0",
66
66
  "rimraf": "^5.0.1",
@@ -82,11 +82,13 @@
82
82
  "dependencies": {
83
83
  "@inquirer/prompts": "3.3.0",
84
84
  "@jridgewell/sourcemap-codec": "^1.4.15",
85
+ "ajv": "^8.16.0",
86
+ "ajv-formats": "^3.0.1",
85
87
  "chalk": "4.1.2",
86
88
  "diff": "5.2.0",
87
89
  "ini": "4.1.1",
88
90
  "langchain": "0.0.196",
89
- "minimatch": "9.0.3",
91
+ "minimatch": "9.0.4",
90
92
  "ora": "5.4.1",
91
93
  "p-queue": "5.0.0",
92
94
  "performance-now": "2.1.0",