git-coco 0.8.2 → 0.8.4

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 +427 -107
  2. package/dist/index.js +427 -107
  3. package/package.json +8 -6
@@ -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
  *
@@ -1499,7 +1819,7 @@ const builder$2 = (yargs) => {
1499
1819
 
1500
1820
  var commit = {
1501
1821
  command: 'commit',
1502
- desc: 'Generate commit message',
1822
+ desc: 'Write a commit message summarizing the staged changes.',
1503
1823
  builder: builder$2,
1504
1824
  handler: commandExecutor(handler$2),
1505
1825
  options: options$2,
@@ -2049,17 +2369,17 @@ y.command([commit.command, '$0'], commit.desc,
2049
2369
  // TODO: fix type on builder
2050
2370
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2051
2371
  // @ts-ignore
2052
- commit.builder, commit.handler);
2372
+ commit.builder, commit.handler).options(commit.options);
2053
2373
  y.command(changelog.command, changelog.desc,
2054
2374
  // TODO: fix type on builder
2055
2375
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2056
2376
  // @ts-ignore
2057
- changelog.builder, changelog.handler);
2377
+ changelog.builder, changelog.handler).options(changelog.options);
2058
2378
  y.command(init.command, init.desc,
2059
2379
  // TODO: fix type on builder
2060
2380
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2061
2381
  // @ts-ignore
2062
- init.builder, init.handler);
2382
+ init.builder, init.handler).options(init.options);
2063
2383
  y.parse(process.argv.slice(2));
2064
2384
 
2065
2385
  export { changelog, commit, init, types };
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
  *
@@ -1520,7 +1840,7 @@ const builder$2 = (yargs) => {
1520
1840
 
1521
1841
  var commit = {
1522
1842
  command: 'commit',
1523
- desc: 'Generate commit message',
1843
+ desc: 'Write a commit message summarizing the staged changes.',
1524
1844
  builder: builder$2,
1525
1845
  handler: commandExecutor(handler$2),
1526
1846
  options: options$2,
@@ -2070,17 +2390,17 @@ y.command([commit.command, '$0'], commit.desc,
2070
2390
  // TODO: fix type on builder
2071
2391
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2072
2392
  // @ts-ignore
2073
- commit.builder, commit.handler);
2393
+ commit.builder, commit.handler).options(commit.options);
2074
2394
  y.command(changelog.command, changelog.desc,
2075
2395
  // TODO: fix type on builder
2076
2396
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2077
2397
  // @ts-ignore
2078
- changelog.builder, changelog.handler);
2398
+ changelog.builder, changelog.handler).options(changelog.options);
2079
2399
  y.command(init.command, init.desc,
2080
2400
  // TODO: fix type on builder
2081
2401
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2082
2402
  // @ts-ignore
2083
- init.builder, init.handler);
2403
+ init.builder, init.handler).options(init.options);
2084
2404
  y.parse(process.argv.slice(2));
2085
2405
 
2086
2406
  exports.changelog = changelog;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-coco",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
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",
@@ -43,7 +43,7 @@
43
43
  "coco:esm": "node dist/index.esm.mjs"
44
44
  },
45
45
  "devDependencies": {
46
- "@rollup/plugin-commonjs": "^25.0.2",
46
+ "@rollup/plugin-commonjs": "^26.0.1",
47
47
  "@rollup/plugin-eslint": "^9.0.5",
48
48
  "@rollup/plugin-json": "^6.0.0",
49
49
  "@rollup/plugin-node-resolve": "^15.1.0",
@@ -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,16 +82,18 @@
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",
93
95
  "pretty-ms": "7.0.1",
94
- "simple-git": "3.23.0",
96
+ "simple-git": "3.25.0",
95
97
  "tiktoken": "^1.0.15",
96
98
  "yargs": "17.7.2"
97
99
  }