diff --git a/package.json b/package.json index 5a6b877..45b70b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/cli", - "version": "0.0.95", + "version": "0.0.96", "description": "envision 命令行工具", "type": "module", "basename": "/root/cli", diff --git a/src/command/claude/cc.ts b/src/command/coding-plan/cc.ts similarity index 100% rename from src/command/claude/cc.ts rename to src/command/coding-plan/cc.ts diff --git a/src/command/coding-plan/oc.ts b/src/command/coding-plan/oc.ts new file mode 100644 index 0000000..4d7f8d8 --- /dev/null +++ b/src/command/coding-plan/oc.ts @@ -0,0 +1,148 @@ +import { program, Command } from '@/program.ts'; +import { chalk } from '../../module/chalk.ts'; +import path from 'node:path'; +import os from 'node:os'; +import fs from 'node:fs'; +import { select } from '@inquirer/prompts'; + +type ProviderConfig = { + npm: string; + name: string; + models: Record; + options?: { + baseURL?: string; + apiKey?: string; + [key: string]: unknown; + }; +}; + +type OpencodeConfig = { + $schema?: string; + autoshare?: boolean; + share?: string; + autoupdate?: boolean; + permission?: string; + model?: string; + watcher?: { + ignore?: string[]; + }; + plugin?: string[]; + provider?: Record; +}; + +/** + * 读取 opencode 配置文件 + */ +const readOpencodeConfig = (configPath: string): OpencodeConfig => { + if (fs.existsSync(configPath)) { + const content = fs.readFileSync(configPath, 'utf-8'); + try { + return JSON.parse(content); + } catch { + return { provider: {} }; + } + } + return { provider: {} }; +}; + +/** + * 保存 opencode 配置文件 + */ +const saveOpencodeConfig = (configPath: string, config: OpencodeConfig) => { + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); +}; + +/** + * 从 provider 配置中提取所有可用的模型 + */ +const extractAvailableModels = (config: OpencodeConfig): Array<{ name: string; provider: string; model: string; label: string }> => { + const models: Array<{ name: string; provider: string; model: string; label: string }> = []; + const providers = config.provider || {}; + + for (const [providerKey, providerConfig] of Object.entries(providers)) { + const providerModels = providerConfig.models || {}; + for (const [modelKey] of Object.entries(providerModels)) { + models.push({ + name: providerConfig.name, + provider: providerKey, + model: modelKey, + label: `${providerKey}/${modelKey}`, + }); + } + } + + return models; +}; + +export const command = new Command('oc') + .description('切换 opencode 模型,从配置的 provider 中选择') + .option('-m, --model ', '选择模型 (格式: provider/model)') + .action(async (options) => { + const configPath = path.join(os.homedir(), '.config', 'opencode', 'opencode.json'); + + // 读取配置 + const config = readOpencodeConfig(configPath); + + // 提取可用的模型 + const availableModels = extractAvailableModels(config); + + if (availableModels.length === 0) { + console.log(chalk.red('没有找到可用的模型配置,请检查 opencode.json 中的 provider 配置')); + return; + } + + // 如果没有指定模型,使用 select 选择 + let selectedModel: string; + if (options.model) { + selectedModel = options.model; + } else { + selectedModel = await select({ + message: '请选择模型:', + choices: availableModels.map((m) => ({ + name: `${m.name} - ${m.model}`, + value: m.label, + })), + }); + } + + // 验证选择的模型是否有效 + const validModel = availableModels.find((m) => m.label === selectedModel); + if (!validModel) { + console.log(chalk.red(`无效的模型选择: ${selectedModel}`)); + return; + } + + // 更新配置 + config.model = selectedModel; + saveOpencodeConfig(configPath, config); + + console.log(`已切换到模型: ${chalk.green(selectedModel)}`); + console.log(`提供商: ${chalk.cyan(validModel.name)}`); + console.log(`配置已保存到: ${configPath}`); + }); + +// show 子命令:显示当前配置的 model +export const showCommand = new Command('show') + .description('显示当前 opencode 配置的 model') + .action(() => { + const configPath = path.join(os.homedir(), '.config', 'opencode', 'opencode.json'); + const config = readOpencodeConfig(configPath); + + if (!config.model) { + console.log(chalk.yellow('当前没有配置 model')); + return; + } + + const availableModels = extractAvailableModels(config); + const currentModel = availableModels.find((m) => m.label === config.model); + + console.log(chalk.bold('当前 opencode 配置:')); + console.log(`模型: ${chalk.green(config.model)}`); + if (currentModel) { + console.log(`提供商: ${chalk.cyan(currentModel.name)}`); + } + console.log(`配置文件: ${configPath}`); + }); + +command.addCommand(showCommand); +program.addCommand(command); diff --git a/src/index.ts b/src/index.ts index 54007ed..566244c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,8 @@ import './command/gist/index.ts'; import './command/config-remote.ts'; import './command/config-secret-remote.ts'; import './command/ai.ts'; -import './command/claude/cc.ts' +import './command/coding-plan/cc.ts' +import './command/coding-plan/oc.ts' import './command/docker.ts'; import './command/jwks.ts';