Merge branch 'master' into dev

This commit is contained in:
di-sukharev
2025-08-01 16:02:44 +03:00
44 changed files with 29840 additions and 21584 deletions
+567 -9
View File
@@ -25,13 +25,16 @@ export enum CONFIG_KEYS {
OCO_ONE_LINE_COMMIT = 'OCO_ONE_LINE_COMMIT',
OCO_TEST_MOCK_TYPE = 'OCO_TEST_MOCK_TYPE',
OCO_API_URL = 'OCO_API_URL',
OCO_API_CUSTOM_HEADERS = 'OCO_API_CUSTOM_HEADERS',
OCO_OMIT_SCOPE = 'OCO_OMIT_SCOPE',
OCO_GITPUSH = 'OCO_GITPUSH' // todo: deprecate
OCO_GITPUSH = 'OCO_GITPUSH', // todo: deprecate
OCO_HOOK_AUTO_UNCOMMENT = 'OCO_HOOK_AUTO_UNCOMMENT'
}
export enum CONFIG_MODES {
get = 'get',
set = 'set'
set = 'set',
describe = 'describe'
}
export const MODEL_LIST = {
@@ -234,8 +237,339 @@ export const MODEL_LIST = {
'perplexity/sonar-pro',
'x-ai/grok-4-07-09',
'x-ai/grok-3-beta',
'x-ai/grok-3-mini-beta',
'x-ai/grok-3-mini-beta'
],
// OpenRouter available models
// input_modalities: 'text'
// output_modalities: 'text'
// https://openrouter.ai/api/v1/models
openrouter: [
'openai/gpt-4o-mini', // used by default
'01-ai/yi-large',
'aetherwiing/mn-starcannon-12b',
'agentica-org/deepcoder-14b-preview:free',
'ai21/jamba-1.6-large',
'ai21/jamba-1.6-mini',
'aion-labs/aion-1.0',
'aion-labs/aion-1.0-mini',
'aion-labs/aion-rp-llama-3.1-8b',
'alfredpros/codellama-7b-instruct-solidity',
'all-hands/openhands-lm-32b-v0.1',
'alpindale/goliath-120b',
'alpindale/magnum-72b',
'amazon/nova-lite-v1',
'amazon/nova-micro-v1',
'amazon/nova-pro-v1',
'anthracite-org/magnum-v2-72b',
'anthracite-org/magnum-v4-72b',
'anthropic/claude-2',
'anthropic/claude-2.0',
'anthropic/claude-2.0:beta',
'anthropic/claude-2.1',
'anthropic/claude-2.1:beta',
'anthropic/claude-2:beta',
'anthropic/claude-3-haiku',
'anthropic/claude-3-haiku:beta',
'anthropic/claude-3-opus',
'anthropic/claude-3-opus:beta',
'anthropic/claude-3-sonnet',
'anthropic/claude-3-sonnet:beta',
'anthropic/claude-3.5-haiku',
'anthropic/claude-3.5-haiku-20241022',
'anthropic/claude-3.5-haiku-20241022:beta',
'anthropic/claude-3.5-haiku:beta',
'anthropic/claude-3.5-sonnet',
'anthropic/claude-3.5-sonnet-20240620',
'anthropic/claude-3.5-sonnet-20240620:beta',
'anthropic/claude-3.5-sonnet:beta',
'anthropic/claude-3.7-sonnet',
'anthropic/claude-3.7-sonnet:beta',
'anthropic/claude-3.7-sonnet:thinking',
'anthropic/claude-opus-4',
'anthropic/claude-sonnet-4',
'arcee-ai/arcee-blitz',
'arcee-ai/caller-large',
'arcee-ai/coder-large',
'arcee-ai/maestro-reasoning',
'arcee-ai/spotlight',
'arcee-ai/virtuoso-large',
'arcee-ai/virtuoso-medium-v2',
'arliai/qwq-32b-arliai-rpr-v1:free',
'cognitivecomputations/dolphin-mixtral-8x22b',
'cognitivecomputations/dolphin3.0-mistral-24b:free',
'cognitivecomputations/dolphin3.0-r1-mistral-24b:free',
'cohere/command',
'cohere/command-a',
'cohere/command-r',
'cohere/command-r-03-2024',
'cohere/command-r-08-2024',
'cohere/command-r-plus',
'cohere/command-r-plus-04-2024',
'cohere/command-r-plus-08-2024',
'cohere/command-r7b-12-2024',
'deepseek/deepseek-chat',
'deepseek/deepseek-chat-v3-0324',
'deepseek/deepseek-chat-v3-0324:free',
'deepseek/deepseek-chat:free',
'deepseek/deepseek-prover-v2',
'deepseek/deepseek-prover-v2:free',
'deepseek/deepseek-r1',
'deepseek/deepseek-r1-0528',
'deepseek/deepseek-r1-0528-qwen3-8b',
'deepseek/deepseek-r1-0528-qwen3-8b:free',
'deepseek/deepseek-r1-0528:free',
'deepseek/deepseek-r1-distill-llama-70b',
'deepseek/deepseek-r1-distill-llama-70b:free',
'deepseek/deepseek-r1-distill-llama-8b',
'deepseek/deepseek-r1-distill-qwen-1.5b',
'deepseek/deepseek-r1-distill-qwen-14b',
'deepseek/deepseek-r1-distill-qwen-14b:free',
'deepseek/deepseek-r1-distill-qwen-32b',
'deepseek/deepseek-r1-distill-qwen-32b:free',
'deepseek/deepseek-r1-distill-qwen-7b',
'deepseek/deepseek-r1-zero:free',
'deepseek/deepseek-r1:free',
'deepseek/deepseek-v3-base:free',
'eleutherai/llemma_7b',
'eva-unit-01/eva-llama-3.33-70b',
'eva-unit-01/eva-qwen-2.5-32b',
'eva-unit-01/eva-qwen-2.5-72b',
'featherless/qwerky-72b:free',
'google/gemini-2.0-flash-001',
'google/gemini-2.0-flash-exp:free',
'google/gemini-2.0-flash-lite-001',
'google/gemini-2.5-flash-preview',
'google/gemini-2.5-flash-preview-05-20',
'google/gemini-2.5-flash-preview-05-20:thinking',
'google/gemini-2.5-flash-preview:thinking',
'google/gemini-2.5-pro-exp-03-25',
'google/gemini-2.5-pro-preview',
'google/gemini-2.5-pro-preview-05-06',
'google/gemini-flash-1.5',
'google/gemini-flash-1.5-8b',
'google/gemini-pro-1.5',
'google/gemma-2-27b-it',
'google/gemma-2-9b-it',
'google/gemma-2-9b-it:free',
'google/gemma-3-12b-it',
'google/gemma-3-12b-it:free',
'google/gemma-3-1b-it:free',
'google/gemma-3-27b-it',
'google/gemma-3-27b-it:free',
'google/gemma-3-4b-it',
'google/gemma-3-4b-it:free',
'google/gemma-3n-e4b-it:free',
'gryphe/mythomax-l2-13b',
'inception/mercury-coder-small-beta',
'infermatic/mn-inferor-12b',
'inflection/inflection-3-pi',
'inflection/inflection-3-productivity',
'liquid/lfm-3b',
'liquid/lfm-40b',
'liquid/lfm-7b',
'mancer/weaver',
'meta-llama/llama-2-70b-chat',
'meta-llama/llama-3-70b-instruct',
'meta-llama/llama-3-8b-instruct',
'meta-llama/llama-3.1-405b',
'meta-llama/llama-3.1-405b-instruct',
'meta-llama/llama-3.1-405b:free',
'meta-llama/llama-3.1-70b-instruct',
'meta-llama/llama-3.1-8b-instruct',
'meta-llama/llama-3.1-8b-instruct:free',
'meta-llama/llama-3.2-11b-vision-instruct',
'meta-llama/llama-3.2-11b-vision-instruct:free',
'meta-llama/llama-3.2-1b-instruct',
'meta-llama/llama-3.2-1b-instruct:free',
'meta-llama/llama-3.2-3b-instruct',
'meta-llama/llama-3.2-3b-instruct:free',
'meta-llama/llama-3.2-90b-vision-instruct',
'meta-llama/llama-3.3-70b-instruct',
'meta-llama/llama-3.3-70b-instruct:free',
'meta-llama/llama-3.3-8b-instruct:free',
'meta-llama/llama-4-maverick',
'meta-llama/llama-4-maverick:free',
'meta-llama/llama-4-scout',
'meta-llama/llama-4-scout:free',
'meta-llama/llama-guard-2-8b',
'meta-llama/llama-guard-3-8b',
'meta-llama/llama-guard-4-12b',
'microsoft/mai-ds-r1:free',
'microsoft/phi-3-medium-128k-instruct',
'microsoft/phi-3-mini-128k-instruct',
'microsoft/phi-3.5-mini-128k-instruct',
'microsoft/phi-4',
'microsoft/phi-4-multimodal-instruct',
'microsoft/phi-4-reasoning-plus',
'microsoft/phi-4-reasoning-plus:free',
'microsoft/phi-4-reasoning:free',
'microsoft/wizardlm-2-8x22b',
'minimax/minimax-01',
'mistralai/codestral-2501',
'mistralai/devstral-small',
'mistralai/devstral-small:free',
'mistralai/magistral-medium-2506',
'mistralai/magistral-medium-2506:thinking',
'mistralai/magistral-small-2506',
'mistralai/ministral-3b',
'mistralai/ministral-8b',
'mistralai/mistral-7b-instruct',
'mistralai/mistral-7b-instruct-v0.1',
'mistralai/mistral-7b-instruct-v0.2',
'mistralai/mistral-7b-instruct-v0.3',
'mistralai/mistral-7b-instruct:free',
'mistralai/mistral-large',
'mistralai/mistral-large-2407',
'mistralai/mistral-large-2411',
'mistralai/mistral-medium',
'mistralai/mistral-medium-3',
'mistralai/mistral-nemo',
'mistralai/mistral-nemo:free',
'mistralai/mistral-saba',
'mistralai/mistral-small',
'mistralai/mistral-small-24b-instruct-2501',
'mistralai/mistral-small-24b-instruct-2501:free',
'mistralai/mistral-small-3.1-24b-instruct',
'mistralai/mistral-small-3.1-24b-instruct:free',
'mistralai/mistral-tiny',
'mistralai/mixtral-8x22b-instruct',
'mistralai/mixtral-8x7b-instruct',
'mistralai/pixtral-12b',
'mistralai/pixtral-large-2411',
'moonshotai/kimi-vl-a3b-thinking:free',
'moonshotai/moonlight-16b-a3b-instruct:free',
'neversleep/llama-3-lumimaid-70b',
'neversleep/llama-3-lumimaid-8b',
'neversleep/llama-3.1-lumimaid-70b',
'neversleep/llama-3.1-lumimaid-8b',
'neversleep/noromaid-20b',
'nothingiisreal/mn-celeste-12b',
'nousresearch/deephermes-3-llama-3-8b-preview:free',
'nousresearch/deephermes-3-mistral-24b-preview:free',
'nousresearch/hermes-2-pro-llama-3-8b',
'nousresearch/hermes-3-llama-3.1-405b',
'nousresearch/hermes-3-llama-3.1-70b',
'nousresearch/nous-hermes-2-mixtral-8x7b-dpo',
'nvidia/llama-3.1-nemotron-70b-instruct',
'nvidia/llama-3.1-nemotron-ultra-253b-v1',
'nvidia/llama-3.1-nemotron-ultra-253b-v1:free',
'nvidia/llama-3.3-nemotron-super-49b-v1',
'nvidia/llama-3.3-nemotron-super-49b-v1:free',
'open-r1/olympiccoder-32b:free',
'openai/chatgpt-4o-latest',
'openai/codex-mini',
'openai/gpt-3.5-turbo',
'openai/gpt-3.5-turbo-0125',
'openai/gpt-3.5-turbo-0613',
'openai/gpt-3.5-turbo-1106',
'openai/gpt-3.5-turbo-16k',
'openai/gpt-3.5-turbo-instruct',
'openai/gpt-4',
'openai/gpt-4-0314',
'openai/gpt-4-1106-preview',
'openai/gpt-4-turbo',
'openai/gpt-4-turbo-preview',
'openai/gpt-4.1',
'openai/gpt-4.1-mini',
'openai/gpt-4.1-nano',
'openai/gpt-4.5-preview',
'openai/gpt-4o',
'openai/gpt-4o-2024-05-13',
'openai/gpt-4o-2024-08-06',
'openai/gpt-4o-2024-11-20',
'openai/gpt-4o-mini-2024-07-18',
'openai/gpt-4o-mini-search-preview',
'openai/gpt-4o-search-preview',
'openai/gpt-4o:extended',
'openai/o1',
'openai/o1-mini',
'openai/o1-mini-2024-09-12',
'openai/o1-preview',
'openai/o1-preview-2024-09-12',
'openai/o1-pro',
'openai/o3',
'openai/o3-mini',
'openai/o3-mini-high',
'openai/o3-pro',
'openai/o4-mini',
'openai/o4-mini-high',
'opengvlab/internvl3-14b:free',
'opengvlab/internvl3-2b:free',
'openrouter/auto',
'perplexity/llama-3.1-sonar-large-128k-online',
'perplexity/llama-3.1-sonar-small-128k-online',
'perplexity/r1-1776',
'perplexity/sonar',
'perplexity/sonar-deep-research',
'perplexity/sonar-pro',
'perplexity/sonar-reasoning',
'perplexity/sonar-reasoning-pro',
'pygmalionai/mythalion-13b',
'qwen/qwen-2-72b-instruct',
'qwen/qwen-2.5-72b-instruct',
'qwen/qwen-2.5-72b-instruct:free',
'qwen/qwen-2.5-7b-instruct',
'qwen/qwen-2.5-7b-instruct:free',
'qwen/qwen-2.5-coder-32b-instruct',
'qwen/qwen-2.5-coder-32b-instruct:free',
'qwen/qwen-2.5-vl-7b-instruct',
'qwen/qwen-2.5-vl-7b-instruct:free',
'qwen/qwen-max',
'qwen/qwen-plus',
'qwen/qwen-turbo',
'qwen/qwen-vl-max',
'qwen/qwen-vl-plus',
'qwen/qwen2.5-vl-32b-instruct',
'qwen/qwen2.5-vl-32b-instruct:free',
'qwen/qwen2.5-vl-3b-instruct:free',
'qwen/qwen2.5-vl-72b-instruct',
'qwen/qwen2.5-vl-72b-instruct:free',
'qwen/qwen3-14b',
'qwen/qwen3-14b:free',
'qwen/qwen3-235b-a22b',
'qwen/qwen3-235b-a22b:free',
'qwen/qwen3-30b-a3b',
'qwen/qwen3-30b-a3b:free',
'qwen/qwen3-32b',
'qwen/qwen3-32b:free',
'qwen/qwen3-8b',
'qwen/qwen3-8b:free',
'qwen/qwq-32b',
'qwen/qwq-32b-preview',
'qwen/qwq-32b:free',
'raifle/sorcererlm-8x22b',
'rekaai/reka-flash-3:free',
'sao10k/fimbulvetr-11b-v2',
'sao10k/l3-euryale-70b',
'sao10k/l3-lunaris-8b',
'sao10k/l3.1-euryale-70b',
'sao10k/l3.3-euryale-70b',
'sarvamai/sarvam-m:free',
'scb10x/llama3.1-typhoon2-70b-instruct',
'sentientagi/dobby-mini-unhinged-plus-llama-3.1-8b',
'shisa-ai/shisa-v2-llama3.3-70b:free',
'sophosympatheia/midnight-rose-70b',
'thedrummer/anubis-pro-105b-v1',
'thedrummer/rocinante-12b',
'thedrummer/skyfall-36b-v2',
'thedrummer/unslopnemo-12b',
'thedrummer/valkyrie-49b-v1',
'thudm/glm-4-32b',
'thudm/glm-4-32b:free',
'thudm/glm-z1-32b',
'thudm/glm-z1-32b:free',
'thudm/glm-z1-rumination-32b',
'tngtech/deepseek-r1t-chimera:free',
'undi95/remm-slerp-l2-13b',
'undi95/toppy-m-7b',
'x-ai/grok-2-1212',
'x-ai/grok-2-vision-1212',
'x-ai/grok-3-beta',
'x-ai/grok-3-mini-beta',
'x-ai/grok-beta',
'x-ai/grok-vision-beta'
]
};
const getDefaultModel = (provider: string | undefined): string => {
@@ -256,14 +590,16 @@ const getDefaultModel = (provider: string | undefined): string => {
return MODEL_LIST.deepseek[0];
case 'aimlapi':
return MODEL_LIST.aimlapi[0];
case 'openrouter':
return MODEL_LIST.openrouter[0];
default:
return MODEL_LIST.openai[0];
}
};
export enum DEFAULT_TOKEN_LIMITS {
DEFAULT_MAX_TOKENS_INPUT = 40960,
DEFAULT_MAX_TOKENS_OUTPUT = 4096
DEFAULT_MAX_TOKENS_INPUT = 4096,
DEFAULT_MAX_TOKENS_OUTPUT = 500
}
const validateConfig = (
@@ -311,6 +647,22 @@ export const configValidators = {
return value;
},
[CONFIG_KEYS.OCO_API_CUSTOM_HEADERS](value) {
try {
// Custom headers must be a valid JSON string
if (typeof value === 'string') {
JSON.parse(value);
}
return value;
} catch (error) {
validateConfig(
CONFIG_KEYS.OCO_API_CUSTOM_HEADERS,
false,
'Must be a valid JSON string of headers'
);
}
},
[CONFIG_KEYS.OCO_TOKENS_MAX_INPUT](value: any) {
value = parseInt(value);
validateConfig(
@@ -431,6 +783,7 @@ export const configValidators = {
'groq',
'deepseek',
'aimlapi',
'openrouter'
].includes(value) || value.startsWith('ollama'),
`${value} is not supported yet, use 'ollama', 'mlx', 'anthropic', 'azure', 'gemini', 'flowise', 'mistral', 'deepseek', 'aimlapi' or 'openai' (default)`
);
@@ -466,6 +819,14 @@ export const configValidators = {
'Must be true or false'
);
return value;
},
[CONFIG_KEYS.OCO_HOOK_AUTO_UNCOMMENT](value: any) {
validateConfig(
CONFIG_KEYS.OCO_HOOK_AUTO_UNCOMMENT,
typeof value === 'boolean',
'Must be true or false'
);
}
};
@@ -482,6 +843,7 @@ export enum OCO_AI_PROVIDER_ENUM {
MLX = 'mlx',
DEEPSEEK = 'deepseek',
AIMLAPI = 'aimlapi',
OPENROUTER = 'openrouter'
}
export type ConfigType = {
@@ -489,6 +851,7 @@ export type ConfigType = {
[CONFIG_KEYS.OCO_TOKENS_MAX_INPUT]: number;
[CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT]: number;
[CONFIG_KEYS.OCO_API_URL]?: string;
[CONFIG_KEYS.OCO_API_CUSTOM_HEADERS]?: string;
[CONFIG_KEYS.OCO_DESCRIPTION]: boolean;
[CONFIG_KEYS.OCO_EMOJI]: boolean;
[CONFIG_KEYS.OCO_WHY]: boolean;
@@ -501,6 +864,7 @@ export type ConfigType = {
[CONFIG_KEYS.OCO_ONE_LINE_COMMIT]: boolean;
[CONFIG_KEYS.OCO_OMIT_SCOPE]: boolean;
[CONFIG_KEYS.OCO_TEST_MOCK_TYPE]: string;
[CONFIG_KEYS.OCO_HOOK_AUTO_UNCOMMENT]: boolean;
};
export const defaultConfigPath = pathJoin(homedir(), '.opencommit');
@@ -548,7 +912,8 @@ export const DEFAULT_CONFIG = {
OCO_TEST_MOCK_TYPE: 'commit-message',
OCO_WHY: false,
OCO_OMIT_SCOPE: false,
OCO_GITPUSH: true // todo: deprecate
OCO_GITPUSH: true, // todo: deprecate
OCO_HOOK_AUTO_UNCOMMENT: false
};
const initGlobalConfig = (configPath: string = defaultConfigPath) => {
@@ -571,6 +936,7 @@ const getEnvConfig = (envPath: string) => {
OCO_MODEL: process.env.OCO_MODEL,
OCO_API_URL: process.env.OCO_API_URL,
OCO_API_KEY: process.env.OCO_API_KEY,
OCO_API_CUSTOM_HEADERS: process.env.OCO_API_CUSTOM_HEADERS,
OCO_AI_PROVIDER: process.env.OCO_AI_PROVIDER as OCO_AI_PROVIDER_ENUM,
OCO_TOKENS_MAX_INPUT: parseConfigVarValue(process.env.OCO_TOKENS_MAX_INPUT),
@@ -712,28 +1078,220 @@ export const setConfig = (
outro(`${chalk.green('✔')} config successfully set`);
};
// --- HELP MESSAGE GENERATION ---
function getConfigKeyDetails(key) {
switch (key) {
case CONFIG_KEYS.OCO_MODEL:
return {
description: 'The AI model to use for generating commit messages',
values: MODEL_LIST
};
case CONFIG_KEYS.OCO_AI_PROVIDER:
return {
description: 'The AI provider to use',
values: Object.values(OCO_AI_PROVIDER_ENUM)
};
case CONFIG_KEYS.OCO_PROMPT_MODULE:
return {
description: 'The prompt module to use for commit message generation',
values: Object.values(OCO_PROMPT_MODULE_ENUM)
};
case CONFIG_KEYS.OCO_LANGUAGE:
return {
description: 'The locale to use for commit messages',
values: Object.keys(i18n)
};
case CONFIG_KEYS.OCO_TEST_MOCK_TYPE:
return {
description: 'The type of test mock to use',
values: ['commit-message', 'prompt-module-commitlint-config']
};
case CONFIG_KEYS.OCO_ONE_LINE_COMMIT:
return {
description: 'One line commit message',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_DESCRIPTION:
return {
description:
'Postface a message with ~3 sentences description of the changes',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_EMOJI:
return {
description: 'Preface a message with GitMoji',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_WHY:
return {
description:
'Output a short description of why the changes were done after the commit message (default: false)',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_OMIT_SCOPE:
return {
description: 'Do not include a scope in the commit message',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_GITPUSH:
return {
description:
'Push to git after commit (deprecated). If false, oco will exit after committing',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_TOKENS_MAX_INPUT:
return {
description: 'Max model token limit',
values: ['Any positive integer']
};
case CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT:
return {
description: 'Max response tokens',
values: ['Any positive integer']
};
case CONFIG_KEYS.OCO_API_KEY:
return {
description: 'API key for the selected provider',
values: ['String (required for most providers)']
};
case CONFIG_KEYS.OCO_API_URL:
return {
description:
'Custom API URL - may be used to set proxy path to OpenAI API',
values: ["URL string (must start with 'http://' or 'https://')"]
};
case CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER:
return {
description: 'Message template placeholder',
values: ['String (must start with $)']
};
case CONFIG_KEYS.OCO_HOOK_AUTO_UNCOMMENT:
return {
description: 'Automatically uncomment the commit message in the hook',
values: ['true', 'false']
};
default:
return {
description: 'String value',
values: ['Any string']
};
}
}
function printConfigKeyHelp(param) {
if (!Object.values(CONFIG_KEYS).includes(param)) {
console.log(chalk.red(`Unknown config parameter: ${param}`));
return;
}
const details = getConfigKeyDetails(param as CONFIG_KEYS);
let desc = details.description;
let defaultValue = undefined;
if (param in DEFAULT_CONFIG) {
defaultValue = DEFAULT_CONFIG[param];
}
console.log(chalk.bold(`\n${param}:`));
console.log(chalk.gray(` Description: ${desc}`));
if (defaultValue !== undefined) {
// Print booleans and numbers as-is, strings without quotes
if (typeof defaultValue === 'string') {
console.log(chalk.gray(` Default: ${defaultValue}`));
} else {
console.log(chalk.gray(` Default: ${defaultValue}`));
}
}
if (Array.isArray(details.values)) {
console.log(chalk.gray(' Accepted values:'));
details.values.forEach((value) => {
console.log(chalk.gray(` - ${value}`));
});
} else {
console.log(chalk.gray(' Accepted values by provider:'));
Object.entries(details.values).forEach(([provider, values]) => {
console.log(chalk.gray(` ${provider}:`));
(values as string[]).forEach((value) => {
console.log(chalk.gray(` - ${value}`));
});
});
}
}
function printAllConfigHelp() {
console.log(chalk.bold('Available config parameters:'));
for (const key of Object.values(CONFIG_KEYS).sort()) {
const details = getConfigKeyDetails(key);
// Try to get the default value from DEFAULT_CONFIG
let defaultValue = undefined;
if (key in DEFAULT_CONFIG) {
defaultValue = DEFAULT_CONFIG[key];
}
console.log(chalk.bold(`\n${key}:`));
console.log(chalk.gray(` Description: ${details.description}`));
if (defaultValue !== undefined) {
if (typeof defaultValue === 'string') {
console.log(chalk.gray(` Default: ${defaultValue}`));
} else {
console.log(chalk.gray(` Default: ${defaultValue}`));
}
}
}
console.log(
chalk.yellow(
'\nUse "oco config describe [PARAMETER]" to see accepted values and more details for a specific config parameter.'
)
);
}
export const configCommand = command(
{
name: COMMANDS.config,
parameters: ['<mode>', '<key=values...>']
parameters: ['<mode>', '[key=values...]'],
help: {
description: 'Configure opencommit settings',
examples: [
'Describe all config parameters: oco config describe',
'Describe a specific parameter: oco config describe OCO_MODEL',
'Get a config value: oco config get OCO_MODEL',
'Set a config value: oco config set OCO_MODEL=gpt-4'
]
}
},
async (argv) => {
try {
const { mode, keyValues } = argv._;
intro(`COMMAND: config ${mode} ${keyValues}`);
if (mode === CONFIG_MODES.get) {
if (mode === CONFIG_MODES.describe) {
if (!keyValues || keyValues.length === 0) {
printAllConfigHelp();
} else {
for (const key of keyValues) {
printConfigKeyHelp(key);
}
}
process.exit(0);
} else if (mode === CONFIG_MODES.get) {
if (!keyValues || keyValues.length === 0) {
throw new Error('No config keys specified for get mode');
}
const config = getConfig() || {};
for (const key of keyValues) {
outro(`${key}=${config[key as keyof typeof config]}`);
}
} else if (mode === CONFIG_MODES.set) {
if (!keyValues || keyValues.length === 0) {
throw new Error('No config keys specified for set mode');
}
await setConfig(
keyValues.map((keyValue) => keyValue.split('=') as [string, string])
);
} else {
throw new Error(
`Unsupported mode: ${mode}. Valid modes are: "set" and "get"`
`Unsupported mode: ${mode}. Valid modes are: "set", "get", and "describe"`
);
}
} catch (error) {