Files

1 line
191 KiB
JSON

{"repo": "brainiac19/openwrt-packages", "files": {"luci-app-openclaw/root/usr/share/openclaw/oc-config-interactive.js": {"b3141a2e86966db064e62b8710cbd509d885503e": {"t": 1785768188.439605, "n": 82439, "h": "31994a356171acd1", "c": "#!/usr/bin/env node\n/**\n * ============================================================================\n * OpenClaw 配置工具 — 交互式菜单前端\n * ============================================================================\n *\n * 功能特性:\n * - 方向键 (↑↓) 导航菜单选项 (使用 oc-menu-engine 引擎)\n * - 回车/空格 确认选择\n * - 实时搜索过滤\n * - 与 oc-config.sh 业务逻辑完全一致\n * - 纯 Node.js 实现,零外部依赖\n * - 完全兼容 xterm.js / Web PTY 环境\n */\n\nconst path = require('path');\nconst fs = require('fs');\nconst { spawn, execSync, execFileSync } = require('child_process');\nconst menu = require('./oc-menu-engine');\nconst { C, select, input, confirm, spinner, resetRenderCount } = menu;\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 配置路径 (与 oc-config.sh 保持一致)\n// ═══════════════════════════════════════════════════════════════════════════\n\nconst OC_BASE_PATH = process.env.OC_BASE_PATH || '/opt';\nconst OC_INSTALL_PATH = process.env.OC_INSTALL_PATH || `${OC_BASE_PATH}/openclaw`;\nconst NODE_BASE = process.env.NODE_BASE || `${OC_INSTALL_PATH}/node`;\nconst OC_GLOBAL = process.env.OC_GLOBAL || `${OC_INSTALL_PATH}/global`;\nconst OC_DATA = process.env.OC_DATA || `${OC_INSTALL_PATH}/data`;\nconst OC_STATE_DIR = process.env.OPENCLAW_STATE_DIR || `${OC_DATA}/.openclaw`;\nconst CONFIG_FILE = process.env.OPENCLAW_CONFIG_PATH || `${OC_STATE_DIR}/openclaw.json`;\nconst NODE_BIN = `${NODE_BASE}/bin/node`;\nconst PERMISSIONS_HELPER = '/usr/libexec/openclaw-permissions.sh';\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 辅助函数 (与 oc-config.sh 逻辑对应)\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * 执行命令并返回结果\n */\nfunction runCommand(cmd, args = [], options = {}) {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, {\n stdio: ['inherit', 'pipe', 'pipe'],\n shell: true,\n env: { ...process.env, ...options.env },\n });\n\n let stdout = '';\n let stderr = '';\n\n child.stdout.on('data', (data) => {\n stdout += data;\n process.stdout.write(data);\n });\n\n child.stderr.on('data', (data) => {\n stderr += data;\n process.stderr.write(data);\n });\n\n child.on('close', (code) => {\n if (code === 0) {\n resolve({ stdout, stderr, code });\n } else {\n reject(new Error(`Command failed with code ${code}: ${stderr}`));\n }\n });\n });\n}\n\n/**\n * 读取 JSON 配置文件\n */\nfunction fixStatePermissions() {\n try {\n execFileSync(PERMISSIONS_HELPER, ['fix-state', OC_STATE_DIR], { stdio: 'ignore', timeout: 10000 });\n } catch {\n try {\n execSync(`find \"${OC_STATE_DIR}\" -user root ! -path \"*/extensions*\" ! -path \"*/archived-extensions*\" ! -path \"*/npm/projects*\" -exec chown openclaw:openclaw {} + 2>/dev/null || true`, { stdio: 'ignore' });\n } catch {}\n }\n}\n\nfunction readConfig() {\n try {\n if (fs.existsSync(CONFIG_FILE)) {\n return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));\n }\n } catch (e) {\n console.error(`${C.red}读取配置失败:${C.reset} ${e.message}`);\n }\n return {};\n}\n\n/**\n * 写入 JSON 配置文件\n */\nfunction writeConfig(config) {\n const dir = path.dirname(CONFIG_FILE);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${dir}\"`, { stdio: \"ignore\" }); } catch {}\n }\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));\n try {\n execSync(`chown openclaw:openclaw \"${CONFIG_FILE}\"`, { stdio: 'ignore' });\n fixStatePermissions();\n } catch {}\n}\n\n/**\n * 获取 JSON 配置值 (对应 json_get)\n */\nfunction jsonGet(keyPath) {\n const config = readConfig();\n const keys = keyPath.split('.');\n let value = config;\n for (const key of keys) {\n if (value && typeof value === 'object' && key in value) {\n value = value[key];\n } else {\n return null;\n }\n }\n return value;\n}\n\n/**\n * 设置 JSON 配置值 (对应 json_set)\n */\nfunction jsonSet(keyPath, value) {\n const config = readConfig();\n const keys = keyPath.split('.');\n let obj = config;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!obj[keys[i]]) obj[keys[i]] = {};\n obj = obj[keys[i]];\n }\n obj[keys[keys.length - 1]] = value;\n writeConfig(config);\n}\n\n/**\n * 获取当前活跃模型\n */\nfunction getCurrentModel() {\n const primary = jsonGet('agents.defaults.model.primary');\n if (primary) return primary;\n const config = readConfig();\n if (config.models?.defaultModel) return config.models.defaultModel;\n return null;\n}\n\n/**\n * 查找 OpenClaw 入口文件\n */\nfunction findOpenClawEntry() {\n const searchPaths = [\n `${OC_GLOBAL}/lib/node_modules/openclaw/openclaw.mjs`,\n `${OC_GLOBAL}/lib/node_modules/openclaw/dist/cli.js`,\n `${OC_GLOBAL}/node_modules/openclaw/openclaw.mjs`,\n `${OC_GLOBAL}/node_modules/openclaw/dist/cli.js`,\n ];\n for (const p of searchPaths) {\n if (fs.existsSync(p)) return p;\n }\n try {\n const dirs = fs.readdirSync(OC_GLOBAL);\n for (const dir of dirs) {\n const p = `${OC_GLOBAL}/${dir}/node_modules/openclaw/openclaw.mjs`;\n if (fs.existsSync(p)) return p;\n }\n } catch {}\n return null;\n}\n\n/**\n * 执行 OpenClaw CLI 命令 (对应 oc_cmd)\n */\nasync function ocCmd(...args) {\n const ocEntry = findOpenClawEntry();\n if (!ocEntry) throw new Error('OpenClaw 未安装');\n return runCommand(NODE_BIN, [ocEntry, ...args], {\n env: {\n OPENCLAW_HOME: OC_DATA,\n OPENCLAW_CONFIG_PATH: CONFIG_FILE,\n OPENCLAW_STATE_DIR: OC_STATE_DIR,\n HOME: OC_DATA,\n }\n });\n}\n\n/**\n * 注册模型并设为默认 (对应 register_and_set_model)\n */\nfunction registerAndSetModel(modelId) {\n const config = readConfig();\n if (!config.agents) config.agents = {};\n if (!config.agents.defaults) config.agents.defaults = {};\n if (!config.agents.defaults.models) config.agents.defaults.models = {};\n if (!config.agents.defaults.model) config.agents.defaults.model = {};\n config.agents.defaults.models[modelId] = {};\n config.agents.defaults.model.primary = modelId;\n writeConfig(config);\n}\n\n/**\n * 写入 API Key 到 auth-profiles.json (对应 auth_set_apikey)\n */\nfunction authSetApikey(provider, apiKey, profileId) {\n const authDir = `${OC_STATE_DIR}/agents/main/agent`;\n const authFile = `${authDir}/auth-profiles.json`;\n try {\n fs.mkdirSync(authDir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${authDir}\"`, { stdio: \"ignore\" }); } catch {}\n } catch {}\n\n let authData = { version: 1, profiles: {}, usageStats: {} };\n try {\n if (fs.existsSync(authFile)) {\n authData = JSON.parse(fs.readFileSync(authFile, 'utf8'));\n }\n } catch {}\n\n if (!authData.profiles) authData.profiles = {};\n authData.profiles[profileId || `${provider}:manual`] = {\n type: 'api_key',\n provider: provider,\n key: apiKey,\n };\n\n fs.writeFileSync(authFile, JSON.stringify(authData, null, 2));\n try {\n execSync(`chown openclaw:openclaw \"${authFile}\"`, { stdio: 'ignore' });\n fixStatePermissions();\n } catch {}\n}\n\n/**\n * 注册自定义提供商 (对应 register_custom_provider)\n */\nfunction registerCustomProvider(providerName, baseUrl, apiKey, modelId, modelDisplay, ctxWindow, maxTok) {\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers[providerName] = {\n baseUrl: baseUrl,\n apiKey: apiKey,\n api: 'openai-completions',\n models: [{\n id: modelId,\n name: modelDisplay || modelId,\n reasoning: false,\n input: ['text', 'image'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: parseInt(ctxWindow) || 128000,\n maxTokens: parseInt(maxTok) || 32000,\n }]\n };\n writeConfig(config);\n}\n\n/**\n * 注册 Coding Plan 提供商 (对应 register_codingplan_provider)\n */\nfunction registerCodingPlanProvider(apiKey) {\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['bailian'] = {\n baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',\n apiKey: apiKey,\n api: 'openai-completions',\n models: [\n { id: 'qwen3.5-plus', name: 'qwen3.5-plus', reasoning: false, input: ['text', 'image'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1000000, maxTokens: 65536 },\n { id: 'qwen3-coder-plus', name: 'qwen3-coder-plus', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1000000, maxTokens: 65536 },\n { id: 'qwen3-coder-next', name: 'qwen3-coder-next', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 65536 },\n { id: 'qwen3-max-2026-01-23', name: 'qwen3-max-2026-01-23', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 65536 },\n { id: 'MiniMax-M2.5', name: 'MiniMax-M2.5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204800, maxTokens: 131072 },\n { id: 'glm-5', name: 'glm-5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202752, maxTokens: 16384 },\n { id: 'glm-4.7', name: 'glm-4.7', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202752, maxTokens: 16384 },\n { id: 'kimi-k2.5', name: 'kimi-k2.5', reasoning: false, input: ['text', 'image'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 32768 },\n ]\n };\n\n if (!config.agents) config.agents = {};\n if (!config.agents.defaults) config.agents.defaults = {};\n if (!config.agents.defaults.models) config.agents.defaults.models = {};\n ['qwen3.5-plus', 'qwen3-coder-plus', 'qwen3-coder-next', 'qwen3-max-2026-01-23', 'MiniMax-M2.5', 'glm-5', 'glm-4.7', 'kimi-k2.5'].forEach(m => {\n config.agents.defaults.models[`bailian/${m}`] = {};\n });\n\n writeConfig(config);\n}\n\n/**\n * 注册腾讯云 Coding Plan 提供商 (对应 register_lkeap_codingplan_provider)\n */\nfunction registerLkeapCodingPlanProvider(apiKey) {\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['lkeap'] = {\n baseUrl: 'https://api.lkeap.cloud.tencent.com/coding/v3',\n apiKey: apiKey,\n api: 'openai-completions',\n models: [\n { id: 'tc-code-latest', name: 'Auto (智能匹配最优模型)', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 8192 },\n { id: 'hunyuan-2.0-instruct', name: 'Tencent HY 2.0 Instruct', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 16000 },\n { id: 'hunyuan-2.0-thinking', name: 'Tencent HY 2.0 Think', reasoning: true, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 64000 },\n { id: 'hunyuan-t1', name: 'Hunyuan-T1', reasoning: true, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 32000, maxTokens: 64000 },\n { id: 'hunyuan-turbos', name: 'Hunyuan-TurboS', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 32000, maxTokens: 16000 },\n { id: 'minimax-m2.5', name: 'MiniMax-M2.5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204800, maxTokens: 131072 },\n { id: 'kimi-k2.5', name: 'Kimi-K2.5', reasoning: false, input: ['text', 'image'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 32768 },\n { id: 'glm-5', name: 'GLM-5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202752, maxTokens: 8192 },\n ]\n };\n\n if (!config.agents) config.agents = {};\n if (!config.agents.defaults) config.agents.defaults = {};\n if (!config.agents.defaults.models) config.agents.defaults.models = {};\n ['tc-code-latest', 'hunyuan-2.0-instruct', 'hunyuan-2.0-thinking', 'hunyuan-t1', 'hunyuan-turbos', 'minimax-m2.5', 'kimi-k2.5', 'glm-5'].forEach(m => {\n config.agents.defaults.models[`lkeap/${m}`] = {};\n });\n\n writeConfig(config);\n}\n\n/**\n * 重启 Gateway\n */\nasync function restartGateway() {\n resetRenderCount();\n console.log(`\\n${C.yellow}正在重启 Gateway...${C.reset}`);\n\n try {\n await runCommand('/bin/sh', ['-c', [\n \"if [ \\\"$(uci -q get openclaw.main.enabled 2>/dev/null || echo 0)\\\" != \\\"1\\\" ]; then\",\n \" uci -q set openclaw.main.enabled='1';\",\n \" uci -q commit openclaw;\",\n \" /etc/init.d/openclaw enable >/dev/null 2>&1 || true;\",\n 'fi;',\n '/etc/init.d/openclaw restart_gateway >/dev/null 2>&1 || true;',\n '/etc/init.d/openclaw start >/dev/null 2>&1 || true'\n ].join(' ')]);\n } catch {}\n\n const spin = spinner({ text: 'Gateway 启动中,请稍候 (约 15-30 秒)...' });\n spin.start();\n\n const gwPort = jsonGet('gateway.port') || '18789';\n let waited = 0;\n const maxWait = 30;\n\n while (waited < maxWait) {\n await new Promise(r => setTimeout(r, 3000));\n waited += 3;\n try {\n // 兼容 OpenWrt: 优先 ss,回退 netstat\n let stdout = '';\n try {\n stdout = execSync('ss -tulnp 2>/dev/null', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n } catch {\n // ss 不存在,使用 netstat\n try {\n stdout = execSync('netstat -tulnp 2>/dev/null', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n } catch {}\n }\n if (stdout && stdout.includes(`:${gwPort} `)) {\n spin.succeed(`Gateway 已重启成功 (${waited}秒)`);\n return;\n }\n } catch {}\n }\n\n spin.stop(`${C.yellow}Gateway 仍在启动中,请稍后确认${C.reset}`);\n console.log(`${C.cyan} 查看日志: logread -e openclaw${C.reset}\\n`);\n}\n\n/**\n * 询问是否重启\n */\nasync function askRestart() {\n const ok = await confirm({ prompt: '是否立即重启服务以应用配置?', defaultYes: true });\n if (ok) {\n await restartGateway();\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 主菜单 (对应 main_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showMainMenu() {\n const currentModel = getCurrentModel();\n const statusLine = currentModel\n ? `${C.green}当前模型: ${currentModel}${C.reset}`\n : `${C.yellow}未配置模型${C.reset}`;\n\n const result = await select({\n title: 'OpenClaw AI Gateway — OpenWrt 配置管理',\n header: statusLine,\n showSearch: false,\n items: [\n { label: `${C.dim}━━━ AI 模型配置 ━━━${C.reset}`, disabled: true },\n { key: '1', label: '配置 AI 模型和提供商', desc: '', value: 'model' },\n { key: '2', label: '设置活动模型', desc: '', value: 'set-active-model' },\n\n { label: `${C.dim}━━━ 消息渠道 ━━━${C.reset}`, disabled: true },\n { key: '3', label: '配置消息渠道', desc: '电报/QQ/飞书', value: 'channels' },\n\n { label: `${C.dim}━━━ 系统管理 ━━━${C.reset}`, disabled: true },\n { key: '4', label: '健康检查与状态', desc: '', value: 'health' },\n { key: '5', label: '查看日志', desc: '', value: 'logs' },\n { key: '6', label: '重启 Gateway', desc: '', value: 'restart' },\n\n { label: `${C.dim}━━━ 高级选项 ━━━${C.reset}`, disabled: true },\n { key: '7', label: '高级配置', desc: '', value: 'advanced' },\n { key: '8', label: '重置配置', desc: '', value: 'reset' },\n { key: '9', label: '显示当前配置概览', desc: '', value: 'show-config' },\n { key: '10', label: '备份/还原配置', desc: '', value: 'backup' },\n\n { label: '', disabled: true },\n { key: '0', label: '退出', desc: '', value: 'quit' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 模型配置菜单 (对应 configure_model)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showModelMenu() {\n const result = await select({\n title: '配置 AI 模型和提供商',\n showSearch: true,\n items: [\n { label: `${C.green}${C.bold}── 推荐 ──${C.reset}`, disabled: true },\n { key: 'w', label: '官方完整模型配置向导', desc: `${C.green}(推荐,支持所有提供商)${C.reset}`, value: 'wizard' },\n\n { label: `${C.bold}── 国外模型提供商 ──${C.reset}`, disabled: true },\n { key: 'a', label: 'OpenAI', desc: 'GPT-5.2, GPT-5 mini, GPT-4.1', value: 'openai' },\n { key: 'b', label: 'Anthropic', desc: 'Claude Sonnet 4, Opus 4, Haiku', value: 'anthropic' },\n { key: 'c', label: 'Google Gemini', desc: 'Gemini 2.5 Pro/Flash, Gemini 3', value: 'google' },\n { key: 'd', label: 'OpenRouter', desc: '聚合多家模型', value: 'openrouter' },\n { key: 'e', label: 'GitHub Copilot', desc: '需要 Copilot 订阅', value: 'copilot' },\n { key: 'f', label: 'xAI Grok', desc: 'Grok-4/3', value: 'xai' },\n\n { label: `${C.bold}── 国内模型提供商 ──${C.reset}`, disabled: true },\n { key: 'g', label: '阿里云通义千问 Qwen', desc: 'Portal/API/Coding Plan', value: 'qwen' },\n { key: 'h', label: '硅基流动 SiliconFlow', desc: '', value: 'siliconflow' },\n { key: 'i', label: '腾讯云 Coding Plan', desc: 'HY T1/TurboS/GLM-5/Kimi', value: 'tencent' },\n { key: 'j', label: '百度千帆', desc: 'ERNIE-4.0, ERNIE-3.5', value: 'baidu' },\n { key: 'k', label: '智谱 GLM / Z.AI', desc: 'GLM-5.1, GLM-5, GLM-4.7', value: 'zhipu' },\n\n { label: `${C.bold}── 本地模型 / 自定义 API ──${C.reset}`, disabled: true },\n { key: 'l', label: 'Ollama', desc: '本地模型,无需 API Key', value: 'ollama' },\n { key: 'm', label: '自定义 OpenAI 兼容 API', desc: '', value: 'custom' },\n { key: 'n', label: '自定义 Anthropic 兼容 API', desc: '', value: 'custom-anthropic' },\n { key: 'o', label: '一万AI分享 粉丝专享 API', desc: '', value: 'yiwanai-fan' },\n\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 消息渠道菜单 (对应 configure_channels)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showChannelsMenu() {\n const result = await select({\n title: '配置消息渠道',\n showSearch: false,\n items: [\n { label: `${C.dim}提示: 微信配置请使用 LuCI 界面「微信配置」菜单${C.reset}`, disabled: true },\n { label: '', disabled: true },\n { key: '1', label: 'QQ 机器人', desc: '腾讯QQ', value: 'qq' },\n { key: '2', label: 'Telegram', desc: `${C.green}最常用${C.reset}`, value: 'telegram' },\n { key: '3', label: 'Discord', desc: '', value: 'discord' },\n { key: '4', label: '飞书 (Feishu)', desc: '', value: 'feishu' },\n { key: '5', label: 'Slack', desc: '', value: 'slack' },\n { key: '6', label: 'WhatsApp', desc: '需通过 Web 控制台扫码', value: 'whatsapp' },\n { key: '7', label: 'Telegram 配对助手', desc: '', value: 'telegram-pairing' },\n { key: '8', label: '官方完整渠道配置向导', desc: '', value: 'wizard' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 高级配置菜单 (对应 advanced_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showAdvancedMenu() {\n const gwPort = jsonGet('gateway.port') || '18789';\n const gwBind = jsonGet('gateway.bind') || 'lan';\n const gwMode = jsonGet('gateway.mode') || 'local';\n const logLevel = jsonGet('gateway.logLevel') || '未设置';\n const acpDispatch = jsonGet('acp.dispatch.enabled') || 'false';\n\n const result = await select({\n title: '高级配置',\n showSearch: false,\n items: [\n { key: '1', label: 'Gateway 端口', desc: `当前: ${gwPort}`, value: 'port' },\n { key: '2', label: 'Gateway 绑定地址', desc: `当前: ${gwBind}`, value: 'bind' },\n { key: '3', label: 'Gateway 运行模式', desc: `当前: ${gwMode}`, value: 'mode' },\n { key: '4', label: '日志级别', desc: `当前: ${logLevel}`, value: 'loglevel' },\n { key: '5', label: 'ACP Dispatch 设置', desc: `当前: ${acpDispatch}`, value: 'acp' },\n { key: '6', label: '官方完整配置向导', desc: 'oc configure', value: 'wizard' },\n { key: '7', label: '查看原始配置 JSON', desc: '', value: 'view-json' },\n { key: '8', label: '编辑配置文件', desc: 'vi / nano', value: 'edit' },\n { key: '9', label: '导出配置备份', desc: '', value: 'backup' },\n { key: '10', label: '导入配置', desc: '', value: 'import' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 重置配置菜单 (对应 reset_to_defaults)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showResetMenu() {\n const result = await select({\n title: '恢复默认配置',\n showSearch: false,\n items: [\n { label: `${C.yellow}请选择恢复级别:${C.reset}`, disabled: true },\n { label: '', disabled: true },\n { key: '1', label: '仅重置网关设置', desc: '端口/绑定/模式恢复默认,保留模型和渠道', value: 'gateway' },\n { key: '2', label: '清除模型配置', desc: '移除所有 AI 模型和 API Key', value: 'models' },\n { key: '3', label: '清除渠道配置', desc: '移除所有消息渠道配置', value: 'channels' },\n { key: '4', label: '完全恢复出厂', desc: '删除所有配置,重新初始化', value: 'full' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 备份/还原菜单 (对应 backup_restore_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showBackupMenu() {\n const result = await select({\n title: '备份/还原配置',\n showSearch: false,\n items: [\n { key: '1', label: '创建配置备份', desc: '仅配置文件', value: 'create-config' },\n { key: '2', label: '创建完整备份', desc: '配置 + 状态数据', value: 'create-full' },\n { key: '3', label: '验证最新备份', desc: '', value: 'verify' },\n { key: '4', label: '查看备份列表', desc: '', value: 'list' },\n { key: '5', label: '从最新备份恢复配置', desc: '', value: 'restore' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 模型配置处理函数 (与 oc-config.sh 完全对应)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function configureOpenAI() {\n resetRenderCount();\n console.log(`\\n${C.bold}OpenAI 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://platform.openai.com/api-keys${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 OpenAI API Key (sk-...)', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'OpenAI 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'gpt-5.2', desc: '最强编程与代理旗舰 (推荐)', value: 'gpt-5.2' },\n { key: 'b', label: 'gpt-5-mini', desc: '高性价比推理', value: 'gpt-5-mini' },\n { key: 'c', label: 'gpt-5-nano', desc: '极速低成本', value: 'gpt-5-nano' },\n { key: 'd', label: 'gpt-4.1', desc: '最强非推理模型', value: 'gpt-4.1' },\n { key: 'e', label: 'o3', desc: '推理模型', value: 'o3' },\n { key: 'f', label: 'o4-mini', desc: '推理轻量', value: 'o4-mini' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'gpt-5.2' });\n if (!modelName) return false;\n }\n\n authSetApikey('openai', apiKey);\n registerAndSetModel(`openai/${modelName}`);\n console.log(`\\n${C.green}✅ OpenAI 已配置,活跃模型: openai/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureAnthropic() {\n resetRenderCount();\n console.log(`\\n${C.bold}Anthropic 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://console.anthropic.com/settings/keys${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Anthropic API Key (sk-ant-...)', placeholder: 'sk-ant-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Anthropic 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'claude-sonnet-4-20250514', desc: 'Claude Sonnet 4 (推荐)', value: 'claude-sonnet-4-20250514' },\n { key: 'b', label: 'claude-opus-4-20250514', desc: 'Claude Opus 4 顶级推理', value: 'claude-opus-4-20250514' },\n { key: 'c', label: 'claude-haiku-4-5', desc: 'Claude Haiku 4.5 轻量快速', value: 'claude-haiku-4-5' },\n { key: 'd', label: 'claude-sonnet-4.5', desc: 'Claude Sonnet 4.5', value: 'claude-sonnet-4.5' },\n { key: 'e', label: 'claude-sonnet-4.6', desc: 'Claude Sonnet 4.6', value: 'claude-sonnet-4.6' },\n { key: 'f', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'claude-sonnet-4-20250514' });\n if (!modelName) return false;\n }\n\n authSetApikey('anthropic', apiKey);\n registerAndSetModel(`anthropic/${modelName}`);\n console.log(`\\n${C.green}✅ Anthropic 已配置,活跃模型: anthropic/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureGoogle() {\n resetRenderCount();\n console.log(`\\n${C.bold}Google Gemini 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://aistudio.google.com/apikey${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Google AI API Key', placeholder: 'AIza...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Google Gemini 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'gemini-2.5-pro', desc: '旗舰推理 (推荐)', value: 'gemini-2.5-pro' },\n { key: 'b', label: 'gemini-2.5-flash', desc: '快速均衡', value: 'gemini-2.5-flash' },\n { key: 'c', label: 'gemini-2.5-flash-lite', desc: '极速低成本', value: 'gemini-2.5-flash-lite' },\n { key: 'd', label: 'gemini-3-flash-preview', desc: 'Gemini 3 Flash 预览', value: 'gemini-3-flash-preview' },\n { key: 'e', label: 'gemini-3-pro-preview', desc: 'Gemini 3 Pro 预览', value: 'gemini-3-pro-preview' },\n { key: 'f', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'gemini-2.5-pro' });\n if (!modelName) return false;\n }\n\n authSetApikey('google', apiKey);\n registerAndSetModel(`google/${modelName}`);\n console.log(`\\n${C.green}✅ Google Gemini 已配置,活跃模型: google/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureOpenRouter() {\n resetRenderCount();\n console.log(`\\n${C.bold}OpenRouter 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://openrouter.ai/keys${C.reset}`);\n console.log(`${C.dim}聚合多家模型,一个 Key 可调用所有主流模型${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 OpenRouter API Key', placeholder: 'sk-or-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'OpenRouter 常用模型',\n showSearch: false,\n items: [\n { key: 'a', label: 'anthropic/claude-sonnet-4', desc: 'Claude Sonnet 4 (推荐)', value: 'anthropic/claude-sonnet-4' },\n { key: 'b', label: 'anthropic/claude-opus-4', desc: 'Claude Opus 4', value: 'anthropic/claude-opus-4' },\n { key: 'c', label: 'openai/gpt-5.2', desc: 'GPT-5.2', value: 'openai/gpt-5.2' },\n { key: 'd', label: 'google/gemini-2.5-pro', desc: 'Gemini 2.5 Pro', value: 'google/gemini-2.5-pro' },\n { key: 'e', label: 'deepseek/deepseek-r1', desc: 'DeepSeek R1', value: 'deepseek/deepseek-r1' },\n { key: 'f', label: 'meta-llama/llama-4-maverick', desc: 'Meta Llama 4', value: 'meta-llama/llama-4-maverick' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称 (格式: provider/model)', defaultValue: 'anthropic/claude-sonnet-4' });\n if (!modelName) return false;\n }\n\n authSetApikey('openrouter', apiKey);\n registerCustomProvider('openrouter', 'https://openrouter.ai/api/v1', apiKey, modelName, modelName);\n registerAndSetModel(`openrouter/${modelName}`);\n console.log(`\\n${C.green}✅ OpenRouter 已配置,活跃模型: openrouter/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureCopilot() {\n resetRenderCount();\n console.log(`\\n${C.bold}GitHub Copilot 配置${C.reset}`);\n console.log(`${C.yellow}需要有效的 GitHub Copilot 订阅 (Free/Pro/Business 均可)${C.reset}\\n`);\n\n console.log(`${C.cyan}启动 GitHub Copilot OAuth 登录...${C.reset}`);\n console.log(`${C.dim}请在浏览器中打开显示的 URL,输入授权码完成登录${C.reset}\\n`);\n\n try {\n await ocCmd('models', 'auth', 'login-github-copilot', '--yes');\n console.log(`\\n${C.green}✅ GitHub Copilot OAuth 认证成功${C.reset}\\n`);\n\n const modelChoice = await select({\n title: 'GitHub Copilot 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'gpt-4.1', desc: 'GPT-4.1 (推荐)', value: 'gpt-4.1' },\n { key: 'b', label: 'gpt-4o', desc: 'GPT-4o', value: 'gpt-4o' },\n { key: 'c', label: 'gpt-5', desc: 'GPT-5', value: 'gpt-5' },\n { key: 'd', label: 'gpt-5-mini', desc: 'GPT-5 mini', value: 'gpt-5-mini' },\n { key: 'e', label: 'gpt-5.1', desc: 'GPT-5.1', value: 'gpt-5.1' },\n { key: 'f', label: 'gpt-5.2', desc: 'GPT-5.2', value: 'gpt-5.2' },\n { key: 'g', label: 'gpt-5.2-codex', desc: 'GPT-5.2 Codex', value: 'gpt-5.2-codex' },\n { key: 'h', label: 'claude-sonnet-4', desc: 'Claude Sonnet 4', value: 'claude-sonnet-4' },\n { key: 'i', label: 'claude-sonnet-4.5', desc: 'Claude Sonnet 4.5', value: 'claude-sonnet-4.5' },\n { key: 'j', label: 'claude-sonnet-4.6', desc: 'Claude Sonnet 4.6', value: 'claude-sonnet-4.6' },\n { key: 'k', label: 'gemini-2.5-pro', desc: 'Gemini 2.5 Pro', value: 'gemini-2.5-pro' },\n { key: 'm', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n\n if (modelChoice) {\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'gpt-4.1' });\n if (!modelName) return false;\n }\n registerAndSetModel(`github-copilot/${modelName}`);\n console.log(`\\n${C.green}✅ 活跃模型已设置: github-copilot/${modelName}${C.reset}\\n`);\n }\n return true;\n } catch (e) {\n console.log(`\\n${C.yellow}OAuth 授权已退出或失败${C.reset}\\n`);\n return false;\n }\n}\n\nasync function configureXAI() {\n resetRenderCount();\n console.log(`\\n${C.bold}xAI Grok 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://console.x.ai${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 xAI API Key', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'xAI Grok 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'grok-4', desc: 'Grok 4 旗舰 (推荐)', value: 'grok-4' },\n { key: 'b', label: 'grok-4-fast', desc: 'Grok 4 Fast', value: 'grok-4-fast' },\n { key: 'c', label: 'grok-3', desc: 'Grok 3', value: 'grok-3' },\n { key: 'd', label: 'grok-3-fast', desc: 'Grok 3 Fast', value: 'grok-3-fast' },\n { key: 'e', label: 'grok-3-mini', desc: 'Grok 3 Mini', value: 'grok-3-mini' },\n { key: 'f', label: 'grok-3-mini-fast', desc: 'Grok 3 Mini Fast', value: 'grok-3-mini-fast' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'grok-4' });\n if (!modelName) return false;\n }\n\n authSetApikey('xai', apiKey);\n registerAndSetModel(`xai/${modelName}`);\n console.log(`\\n${C.green}✅ xAI Grok 已配置,活跃模型: xai/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureQwen() {\n resetRenderCount();\n console.log(`\\n${C.bold}阿里云通义千问 Qwen 配置${C.reset}\\n`);\n\n const modeChoice = await select({\n title: '配置方式',\n showSearch: false,\n items: [\n { key: 'a', label: '通过官方向导配置', desc: 'Qwen Portal OAuth', value: 'portal' },\n { key: 'b', label: '百炼按量付费 API Key', desc: 'sk-xxx, 按 token 计费', value: 'bailian' },\n { key: 'c', label: 'Coding Plan 套餐', desc: `${C.green}★ 推荐${C.reset}`, value: 'codingplan' },\n ],\n });\n if (!modeChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n if (modeChoice.value === 'portal') {\n console.log(`\\n${C.cyan}启动 Qwen OAuth 授权...${C.reset}\\n`);\n try {\n await ocCmd('models', 'auth', 'login', '--provider', 'qwen-portal', '--set-default');\n } catch {}\n console.log(`\\n${C.yellow}OAuth 授权已退出${C.reset}\\n`);\n return true;\n }\n\n if (modeChoice.value === 'bailian') {\n console.log(`\\n${C.bold}百炼按量付费配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://dashscope.console.aliyun.com/apiKey${C.reset}`);\n console.log(`${C.dim}Base URL: https://dashscope.aliyuncs.com/compatible-mode/v1${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入百炼 API Key (sk-...)', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '百炼模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'qwen-max', desc: '千问Max 旗舰模型 (推荐)', value: 'qwen-max' },\n { key: 'b', label: 'qwen-plus', desc: '千问Plus 均衡之选', value: 'qwen-plus' },\n { key: 'c', label: 'qwen-flash', desc: '千问Flash 速度最快', value: 'qwen-flash' },\n { key: 'd', label: 'qwen-turbo', desc: '千问Turbo 经济实惠', value: 'qwen-turbo' },\n { key: 'e', label: 'qwen-long', desc: '千问Long 超长上下文', value: 'qwen-long' },\n { key: 'f', label: 'qwen3-coder-plus', desc: '代码专用旗舰', value: 'qwen3-coder-plus' },\n { key: 'g', label: 'qwq-plus', desc: 'QwQ推理模型', value: 'qwq-plus' },\n { key: 'h', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'qwen-max' });\n if (!modelName) return false;\n }\n\n authSetApikey('dashscope', apiKey);\n registerCustomProvider('dashscope', 'https://dashscope.aliyuncs.com/compatible-mode/v1', apiKey, modelName, modelName);\n registerAndSetModel(`dashscope/${modelName}`);\n console.log(`\\n${C.green}✅ 通义千问已配置 (按量付费),活跃模型: dashscope/${modelName}${C.reset}\\n`);\n return true;\n }\n\n if (modeChoice.value === 'codingplan') {\n console.log(`\\n${C.bold}Coding Plan 套餐配置${C.reset}`);\n console.log(`${C.yellow}订阅套餐: https://bailian.console.aliyun.com/cn-beijing/?tab=model#/efm/coding_plan${C.reset}`);\n console.log(`${C.dim}Base URL: https://coding.dashscope.aliyuncs.com/v1${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Coding Plan 专属 API Key (sk-sp-...)', placeholder: 'sk-sp-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Coding Plan 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'qwen3.5-plus', desc: 'Qwen3.5 Plus (推荐, 100万上下文)', value: 'qwen3.5-plus' },\n { key: 'b', label: 'qwen3-coder-plus', desc: 'Qwen3 Coder Plus', value: 'qwen3-coder-plus' },\n { key: 'c', label: 'qwen3-coder-next', desc: 'Qwen3 Coder Next', value: 'qwen3-coder-next' },\n { key: 'd', label: 'qwen3-max-2026-01-23', desc: 'Qwen3 Max', value: 'qwen3-max-2026-01-23' },\n { key: 'e', label: 'MiniMax-M2.5', desc: 'MiniMax M2.5', value: 'MiniMax-M2.5' },\n { key: 'f', label: 'glm-5', desc: '智谱 GLM-5', value: 'glm-5' },\n { key: 'g', label: 'kimi-k2.5', desc: 'Kimi K2.5', value: 'kimi-k2.5' },\n { key: 'h', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'qwen3.5-plus' });\n if (!modelName) return false;\n }\n\n console.log(`\\n${C.cyan}正在注册 Coding Plan 提供商 (含全部可用模型)...${C.reset}`);\n authSetApikey('bailian', apiKey);\n registerCodingPlanProvider(apiKey);\n registerAndSetModel(`bailian/${modelName}`);\n console.log(`\\n${C.green}✅ Coding Plan 已配置,活跃模型: bailian/${modelName}${C.reset}`);\n console.log(`${C.dim}提示: 套餐内全部模型已注册,可随时在 WebChat 中通过 /model 切换${C.reset}\\n`);\n return true;\n }\n\n return false;\n}\n\nasync function configureSiliconFlow() {\n resetRenderCount();\n console.log(`\\n${C.bold}硅基流动 SiliconFlow 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://cloud.siliconflow.cn/account/ak${C.reset}`);\n console.log(`${C.yellow}国内推理平台,支持多种开源模型${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 SiliconFlow API Key', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n console.log(`\\n${C.cyan}可用模型分类说明:${C.reset}`);\n console.log(`${C.yellow}* Pro模型: 仅支持充值余额支付${C.reset}`);\n console.log(`${C.yellow}* 非Pro模型: 支持代金券/免费额度${C.reset}\\n`);\n\n const modelChoice = await select({\n title: 'SiliconFlow 模型选择',\n showSearch: false,\n items: [\n { label: `${C.cyan}── 非Pro模型 (支持代金券) ──${C.reset}`, disabled: true },\n { key: '1', label: 'deepseek-ai/DeepSeek-V3', desc: 'DeepSeek-V3 (推荐)', value: 'deepseek-ai/DeepSeek-V3' },\n { key: '2', label: 'deepseek-ai/DeepSeek-R1', desc: 'DeepSeek-R1 推理模型', value: 'deepseek-ai/DeepSeek-R1' },\n { key: '3', label: 'Qwen/Qwen2.5-72B-Instruct', desc: '通义千问 2.5 72B', value: 'Qwen/Qwen2.5-72B-Instruct' },\n { key: '4', label: 'Qwen/Qwen2.5-7B-Instruct', desc: '通义千问 2.5 7B', value: 'Qwen/Qwen2.5-7B-Instruct' },\n { key: '5', label: 'THUDM/glm-4-9b-chat', desc: '智谱 GLM-4 9B', value: 'THUDM/glm-4-9b-chat' },\n { key: '6', label: '01-ai/Yi-1.5-34B-Chat-16K', desc: '零一万物 Yi-1.5', value: '01-ai/Yi-1.5-34B-Chat-16K' },\n { label: `${C.cyan}── Pro模型 (仅支持充值余额) ──${C.reset}`, disabled: true },\n { key: '7', label: 'Pro/deepseek-ai/DeepSeek-V3', desc: 'DeepSeek-V3 (Pro)', value: 'Pro/deepseek-ai/DeepSeek-V3' },\n { key: '8', label: 'Pro/zai-org/GLM-5', desc: '智谱 GLM-5', value: 'Pro/zai-org/GLM-5' },\n { key: '0', label: '手动输入其他模型名称', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型详细名称', defaultValue: 'deepseek-ai/DeepSeek-V3' });\n if (!modelName) return false;\n }\n\n authSetApikey('siliconflow', apiKey);\n registerCustomProvider('siliconflow', 'https://api.siliconflow.cn/v1', apiKey, modelName, modelName);\n registerAndSetModel(`siliconflow/${modelName}`);\n console.log(`\\n${C.green}✅ SiliconFlow 已配置,活跃模型: siliconflow/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureTencent() {\n resetRenderCount();\n console.log(`\\n${C.bold}腾讯云大模型 Coding Plan 套餐配置${C.reset}`);\n console.log(`${C.yellow}订阅/管理套餐: https://hunyuan.cloud.tencent.com/#/app/subscription${C.reset}`);\n console.log(`${C.dim}文档: https://cloud.tencent.com/document/product/1772/128947${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Coding Plan API Key (sk-sp-...)', placeholder: 'sk-sp-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '腾讯云 Coding Plan 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'tc-code-latest', desc: '自动路由 (推荐)', value: 'tc-code-latest' },\n { key: 'b', label: 'hunyuan-t1', desc: '混元 T1 深度推理', value: 'hunyuan-t1' },\n { key: 'c', label: 'hunyuan-2.0-thinking', desc: '混元 2.0 Thinking', value: 'hunyuan-2.0-thinking' },\n { key: 'd', label: 'hunyuan-turbos', desc: '混元 TurboS 旗舰', value: 'hunyuan-turbos' },\n { key: 'e', label: 'hunyuan-2.0-instruct', desc: '混元 2.0 Instruct', value: 'hunyuan-2.0-instruct' },\n { key: 'f', label: 'glm-5', desc: '智谱 GLM-5', value: 'glm-5' },\n { key: 'g', label: 'kimi-k2.5', desc: 'Moonshot Kimi K2.5', value: 'kimi-k2.5' },\n { key: 'h', label: 'minimax-m2.5', desc: 'MiniMax M2.5', value: 'minimax-m2.5' },\n { key: 'z', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'tc-code-latest' });\n if (!modelName) return false;\n }\n\n console.log(`\\n${C.cyan}正在注册腾讯云 Coding Plan 提供商 (含全部套餐模型)...${C.reset}`);\n authSetApikey('lkeap', apiKey);\n registerLkeapCodingPlanProvider(apiKey);\n registerAndSetModel(`lkeap/${modelName}`);\n console.log(`\\n${C.green}✅ 腾讯云 Coding Plan 已配置,活跃模型: lkeap/${modelName}${C.reset}`);\n console.log(`${C.dim}提示: 套餐内全部模型已注册,可随时在 WebChat 中通过 /model 切换${C.reset}\\n`);\n return true;\n}\n\nasync function configureBaidu() {\n resetRenderCount();\n console.log(`\\n${C.bold}百度千帆大模型配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://console.bce.baidu.com/qianfan/ais/console/onlineService${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入百度千帆 API Key (Access Token)', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '百度千帆模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'ernie-4.0-8k', desc: '文心一言 4.0 (推荐)', value: 'ernie-4.0-8k' },\n { key: 'b', label: 'ernie-3.5-8k', desc: '文心一言 3.5', value: 'ernie-3.5-8k' },\n { key: 'c', label: 'ernie-4.0-turbo-8k', desc: '文心一言 4.0 Turbo', value: 'ernie-4.0-turbo-8k' },\n { key: 'd', label: 'ernie-speed-8k', desc: '文心一言 Speed 极速', value: 'ernie-speed-8k' },\n { key: 'e', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'ernie-4.0-8k' });\n if (!modelName) return false;\n }\n\n authSetApikey('qianfan', apiKey);\n registerCustomProvider('qianfan', 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop', apiKey, modelName, modelName);\n registerAndSetModel(`qianfan/${modelName}`);\n console.log(`\\n${C.green}✅ 百度千帆已配置,活跃模型: qianfan/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureZhipu() {\n resetRenderCount();\n console.log(`\\n${C.bold}智谱 GLM / Z.AI 配置${C.reset}\\n`);\n\n const methodChoice = await select({\n title: '认证方式',\n showSearch: false,\n items: [\n { key: 'a', label: 'CN (open.bigmodel.cn)', desc: `${C.green}★ 国内用户推荐${C.reset}`, value: 'cn' },\n { key: 'b', label: 'Coding-Plan-CN', desc: '智谱 Coding Plan 套餐', value: 'coding-plan-cn' },\n { key: 'c', label: 'Global (api.z.ai)', desc: '全球版', value: 'global' },\n { key: 'd', label: '手动输入', desc: '', value: 'custom' },\n ],\n });\n if (!methodChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let baseUrl = 'https://open.bigmodel.cn/api/paas/v4';\n switch (methodChoice.value) {\n case 'cn':\n console.log(`${C.yellow}获取 API Key: https://open.bigmodel.cn/api-key${C.reset}`);\n break;\n case 'coding-plan-cn':\n baseUrl = 'https://open.bigmodel.cn/api/coding/paas/v4';\n console.log(`${C.yellow}Coding Plan 套餐 API Key${C.reset}`);\n break;\n case 'global':\n baseUrl = 'https://api.z.ai/api/paas/v4';\n console.log(`${C.yellow}全球版 API Key${C.reset}`);\n break;\n case 'custom':\n const customUrl = await input({ prompt: '请输入 Base URL', defaultValue: 'https://open.bigmodel.cn/api/paas/v4' });\n if (customUrl) baseUrl = customUrl;\n break;\n }\n\n const apiKey = await input({ prompt: '请输入智谱 API Key', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '智谱 GLM 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'glm-5.1', desc: 'GLM-5.1 (推荐)', value: 'glm-5.1' },\n { key: 'b', label: 'glm-5', desc: 'GLM-5', value: 'glm-5' },\n { key: 'c', label: 'glm-4.7', desc: 'GLM-4.7', value: 'glm-4.7' },\n { key: 'd', label: 'glm-4.7-flash', desc: 'GLM-4.7 Flash', value: 'glm-4.7-flash' },\n { key: 'e', label: 'glm-4.5', desc: 'GLM-4.5', value: 'glm-4.5' },\n { key: 'f', label: 'glm-4.5-flash', desc: 'GLM-4.5 Flash (免费)', value: 'glm-4.5-flash' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'glm-5.1' });\n if (!modelName) return false;\n }\n\n // 智谱 GLM 使用原生 zai provider (OpenClaw 内置支持)\n registerCustomProvider('zai', baseUrl, apiKey, modelName, modelName, 128000, 4096);\n authSetApikey('zai', apiKey);\n registerAndSetModel(`zai/${modelName}`);\n console.log(`\\n${C.green}✅ 智谱 GLM 已配置,活跃模型: zai/${modelName}${C.reset}`);\n console.log(`${C.dim} Base URL: ${baseUrl}${C.reset}\\n`);\n return true;\n}\n\nasync function configureOllama() {\n resetRenderCount();\n console.log(`\\n${C.bold}Ollama 本地模型配置${C.reset}`);\n console.log(`${C.yellow}Ollama 在本地或局域网运行大模型,无需 API Key${C.reset}`);\n console.log(`${C.yellow}安装 Ollama: https://ollama.com${C.reset}\\n`);\n\n const modeChoice = await select({\n title: '连接方式',\n showSearch: false,\n items: [\n { key: 'a', label: '本机运行', desc: 'localhost:11434', value: 'local' },\n { key: 'b', label: '局域网其他设备', desc: '', value: 'remote' },\n ],\n });\n if (!modeChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let ollamaUrl = 'http://127.0.0.1:11434';\n if (modeChoice.value === 'remote') {\n const host = await input({ prompt: 'Ollama 地址 (如 192.168.1.100:11434)', placeholder: '' });\n if (!host) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n ollamaUrl = host.startsWith('http') ? host : `http://${host}`;\n ollamaUrl = ollamaUrl.replace(/\\/v1$/, '').replace(/\\/$/, '');\n }\n\n // 尝试检测 Ollama 连通性\n console.log(`\\n${C.cyan}检测 Ollama 连通性...${C.reset}`);\n let modelList = [];\n try {\n const stdout = execSync(`curl -sf --connect-timeout 3 --max-time 5 ${ollamaUrl}/api/tags`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n const data = JSON.parse(stdout);\n modelList = data.models || [];\n console.log(`${C.green}✅ Ollama 已连接${C.reset}`);\n } catch {\n console.log(`${C.yellow}⚠️ 无法连接 Ollama (${ollamaUrl})${C.reset}`);\n const continueAnyway = await confirm({ prompt: '仍要继续配置?', defaultYes: false });\n if (!continueAnyway) return false;\n }\n\n let modelName = '';\n if (modelList.length > 0) {\n const items = modelList.map((m, i) => ({\n key: String(i + 1),\n label: m.name,\n desc: '',\n value: m.name,\n }));\n items.push({ key: 'm', label: '手动输入模型名', desc: '', value: 'custom' });\n\n const modelChoice = await select({\n title: '已安装的模型',\n showSearch: false,\n items,\n });\n\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n modelName = modelChoice.value === 'custom'\n ? await input({ prompt: '请输入模型名称', defaultValue: 'llama3.3' })\n : modelChoice.value;\n } else {\n modelName = await input({ prompt: '请输入模型名称', placeholder: 'llama3.3, qwen2.5...' });\n }\n\n if (!modelName) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n // 注册 Ollama 提供商\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['ollama'] = {\n baseUrl: ollamaUrl,\n apiKey: 'ollama-local',\n api: 'ollama',\n models: [{\n id: modelName,\n name: modelName,\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 128000,\n maxTokens: 32000,\n }]\n };\n writeConfig(config);\n\n authSetApikey('ollama', 'ollama-local', 'ollama:local');\n registerAndSetModel(`ollama/${modelName}`);\n console.log(`\\n${C.green}✅ Ollama 已配置,活跃模型: ollama/${modelName}${C.reset}`);\n console.log(`${C.cyan} Ollama 地址: ${ollamaUrl}${C.reset}\\n`);\n return true;\n}\n\nasync function configureCustomAPI() {\n resetRenderCount();\n console.log(`\\n${C.bold}自定义 OpenAI 兼容 API 配置${C.reset}`);\n console.log(`${C.yellow}支持任何兼容 OpenAI API 格式的服务商${C.reset}\\n`);\n\n const baseUrl = await input({ prompt: 'API Base URL (如 https://api.example.com/v1)', placeholder: '' });\n if (!baseUrl) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const apiKey = await input({ prompt: 'API Key', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelName = await input({ prompt: '模型名称', placeholder: '' });\n if (!modelName) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n authSetApikey('openai-compatible', apiKey, 'openai-compatible:manual');\n registerCustomProvider('openai-compatible', baseUrl.replace(/\\/$/, ''), apiKey, modelName, modelName);\n registerAndSetModel(`openai-compatible/${modelName}`);\n console.log(`\\n${C.green}✅ 自定义 API 已配置,活跃模型: openai-compatible/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureCustomAnthropic() {\n resetRenderCount();\n console.log(`\\n${C.bold}自定义 Anthropic 兼容 API 配置${C.reset}`);\n console.log(`${C.yellow}支持任何兼容 Anthropic Messages API 格式的服务商${C.reset}\\n`);\n\n const baseUrl = await input({ prompt: 'API Base URL', placeholder: 'https://api.anthropic.com' });\n if (!baseUrl) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const apiKey = await input({ prompt: 'API Key', placeholder: 'sk-ant-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Anthropic 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'claude-sonnet-4-20250514', desc: 'Claude Sonnet 4 (推荐)', value: 'claude-sonnet-4-20250514' },\n { key: 'b', label: 'claude-opus-4-20250514', desc: 'Claude Opus 4', value: 'claude-opus-4-20250514' },\n { key: 'c', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'claude-sonnet-4-20250514' });\n if (!modelName) return false;\n }\n\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['anthropic-compatible'] = {\n baseUrl: baseUrl.replace(/\\/$/, ''),\n apiKey: apiKey,\n api: 'anthropic-messages',\n models: [{\n id: modelName,\n name: modelName,\n reasoning: false,\n input: ['text', 'image'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 200000,\n maxTokens: 16000,\n }]\n };\n writeConfig(config);\n\n authSetApikey('anthropic-compatible', apiKey, 'anthropic-compatible:manual');\n registerAndSetModel(`anthropic-compatible/${modelName}`);\n console.log(`\\n${C.green}✅ 自定义 Anthropic API 已配置,活跃模型: anthropic-compatible/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureYiwanAIFanAPI() {\n resetRenderCount();\n const providerName = 'yiwanai';\n const baseUrl = 'https://api.910501.xyz/v1';\n const modelName = 'gpt-5.5';\n\n console.log(`\\n${C.bold}一万AI分享 粉丝专享 API 配置${C.reset}`);\n console.log(`${C.yellow}OpenAI 兼容模式;Base URL 和模型已内置,只需要填写 API Key。${C.reset}`);\n console.log(`${C.dim}Base URL: ${baseUrl}${C.reset}`);\n console.log(`${C.dim}Model: ${modelName}${C.reset}\\n`);\n\n const apiKey = await input({ prompt: 'API Key', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n authSetApikey(providerName, apiKey, `${providerName}:fan`);\n registerCustomProvider(providerName, baseUrl, apiKey, modelName, modelName, 1000000, 32000);\n const config = readConfig();\n const provider = config.models?.providers?.[providerName];\n if (provider?.models?.[0]) {\n provider.models[0].reasoning = true;\n }\n writeConfig(config);\n registerAndSetModel(`${providerName}/${modelName}`);\n console.log(`\\n${C.green}✅ 一万AI分享粉丝专享 API 已配置,活跃模型: ${providerName}/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function launchWizard() {\n resetRenderCount();\n console.log(`\\n${C.cyan}启动官方完整模型配置向导...${C.reset}\\n`);\n console.log(`${C.yellow}提示: ↑↓ 移动, Tab/空格 选中, 回车 确认${C.reset}\\n`);\n try {\n await ocCmd('configure', '--section', 'model');\n } catch (e) {\n console.log(`${C.yellow}配置向导已退出${C.reset}\\n`);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 模型配置处理入口\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleModelConfig() {\n while (true) {\n const choice = await showModelMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n let configured = false;\n\n switch (choice.value) {\n case 'wizard': await launchWizard(); break;\n case 'openai': configured = await configureOpenAI(); break;\n case 'anthropic': configured = await configureAnthropic(); break;\n case 'google': configured = await configureGoogle(); break;\n case 'openrouter': configured = await configureOpenRouter(); break;\n case 'copilot': configured = await configureCopilot(); break;\n case 'xai': configured = await configureXAI(); break;\n case 'qwen': configured = await configureQwen(); break;\n case 'siliconflow': configured = await configureSiliconFlow(); break;\n case 'tencent': configured = await configureTencent(); break;\n case 'baidu': configured = await configureBaidu(); break;\n case 'zhipu': configured = await configureZhipu(); break;\n case 'ollama': configured = await configureOllama(); break;\n case 'custom': configured = await configureCustomAPI(); break;\n case 'custom-anthropic': configured = await configureCustomAnthropic(); break;\n case 'yiwanai-fan': configured = await configureYiwanAIFanAPI(); break;\n }\n\n if (configured) await askRestart();\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 设置活动模型 (对应 set_active_model)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleSetActiveModel() {\n resetRenderCount();\n console.log(`\\n${C.bold}设定当前活跃模型${C.reset}\\n`);\n\n const currentModel = getCurrentModel();\n console.log(` 当前活跃模型: ${C.green}${C.bold}${currentModel || '未设置'}${C.reset}\\n`);\n\n try {\n const result = await ocCmd('models', 'list', '--json');\n const modelsData = JSON.parse(result.stdout);\n const models = modelsData.models || [];\n\n if (models.length > 0) {\n const items = models.map((m, i) => ({\n key: String(i + 1),\n label: m.key,\n desc: m.name && m.name !== m.key ? `(${m.name})` : '',\n value: m.key,\n selected: m.key === currentModel,\n }));\n\n items.push({ label: '', disabled: true });\n items.push({ key: 'm', label: '手动输入模型 ID', desc: '', value: 'manual' });\n items.push({ key: '0', label: '返回', desc: '', value: 'back' });\n\n const choice = await select({\n title: '已配置的模型',\n showSearch: true,\n items,\n });\n\n if (!choice || choice.value === 'back') return;\n\n if (choice.value === 'manual') {\n const manualModel = await input({\n prompt: '请输入模型 ID',\n placeholder: 'openai/gpt-4o',\n defaultValue: currentModel || '',\n });\n if (manualModel) {\n registerAndSetModel(manualModel);\n console.log(`\\n${C.green}✅ 活跃模型已设为: ${manualModel}${C.reset}\\n`);\n await askRestart();\n }\n } else {\n registerAndSetModel(choice.value);\n console.log(`\\n${C.green}✅ 活跃模型已切换为: ${choice.value}${C.reset}\\n`);\n await askRestart();\n }\n } else {\n console.log(`${C.yellow}尚未配置任何模型。${C.reset}`);\n console.log(`${C.yellow}请先通过「配置 AI 模型提供商」添加模型。${C.reset}\\n`);\n\n const manualModel = await input({\n prompt: '直接输入模型 ID 设置 (留空返回)',\n defaultValue: '',\n });\n if (manualModel) {\n registerAndSetModel(manualModel);\n console.log(`\\n${C.green}✅ 活跃模型已设为: ${manualModel}${C.reset}\\n`);\n await askRestart();\n }\n }\n } catch (e) {\n console.log(`${C.red}获取模型列表失败: ${e.message}${C.reset}\\n`);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 消息渠道处理 (对应 configure_channels 等函数)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleChannels() {\n while (true) {\n const choice = await showChannelsMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n\n switch (choice.value) {\n case 'qq': {\n console.log(`\\n${C.bold}QQ 机器人配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 App ID 和 App Secret:${C.reset}`);\n console.log(` 1. 前往 ${C.cyan}https://q.qq.com/qqbot/openclaw/login.html${C.reset}`);\n console.log(` 2. 用手机 QQ 扫码注册/登录`);\n console.log(` 3. 创建机器人后复制 App ID 和 App Secret\\n`);\n\n const appId = await input({ prompt: '请输入 QQ 机器人 App ID', placeholder: '' });\n if (!appId) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n const appSecret = await input({ prompt: '请输入 QQ 机器人 App Secret', placeholder: '' });\n if (!appSecret) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n // 写入配置\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.qqbot) cfg.channels.qqbot = {};\n cfg.channels.qqbot.appId = appId;\n cfg.channels.qqbot.clientSecret = appSecret;\n cfg.channels.qqbot.enabled = true;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ QQ 机器人配置已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'telegram': {\n console.log(`\\n${C.bold}Telegram Bot 配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 Bot Token:${C.reset}`);\n console.log(` 1. 打开 Telegram → 搜索 ${C.cyan}@BotFather${C.reset}`);\n console.log(` 2. 发送 ${C.cyan}/newbot${C.reset} → 按提示创建`);\n console.log(` 3. 复制生成的 Token\\n`);\n\n const token = await input({ prompt: '请输入 Telegram Bot Token', placeholder: '123456:ABC...' });\n if (!token) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n // 验证格式\n if (!/^[0-9]+:[A-Za-z0-9_-]+$/.test(token)) {\n console.log(`${C.red}✗ Token 格式错误${C.reset}`);\n console.log(`${C.yellow}正确格式: 123456789:ABCdefGHIjklMNOpqr${C.reset}\\n`);\n break;\n }\n\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.telegram) cfg.channels.telegram = {};\n cfg.channels.telegram.botToken = token;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ Telegram Bot Token 已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'discord': {\n console.log(`\\n${C.bold}Discord Bot 配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 Bot Token:${C.cyan} https://discord.com/developers/applications${C.reset}\\n`);\n\n const token = await input({ prompt: '请输入 Discord Bot Token', placeholder: '' });\n if (!token) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.discord) cfg.channels.discord = {};\n cfg.channels.discord.botToken = token;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ Discord Bot Token 已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'feishu': {\n console.log(`\\n${C.bold}飞书 Bot 配置${C.reset}\\n`);\n console.log(`${C.cyan}即将执行飞书官方安装向导...${C.reset}\\n`);\n\n const doInstall = await confirm({ prompt: '是否开始安装?', defaultYes: true });\n if (!doInstall) break;\n\n console.log(`\\n${C.cyan}正在启动飞书安装向导...${C.reset}\\n`);\n try {\n await runCommand('npx', ['-y', '@larksuite/openclaw-lark-tools', 'install'], { env: { HOME: OC_DATA } });\n } catch (e) {\n console.log(`${C.yellow}安装向导已退出${C.reset}\\n`);\n }\n break;\n }\n case 'slack': {\n console.log(`\\n${C.bold}Slack Bot 配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 Bot Token:${C.cyan} https://api.slack.com/apps${C.reset}\\n`);\n\n const token = await input({ prompt: '请输入 Slack Bot Token (xoxb-...)', placeholder: '' });\n if (!token) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.slack) cfg.channels.slack = {};\n cfg.channels.slack.botToken = token;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ Slack Bot Token 已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'whatsapp': {\n const gwToken = jsonGet('gateway.auth.token') || '';\n const gwPort = jsonGet('gateway.port') || '18789';\n console.log(`\\n${C.yellow}WhatsApp 需要通过 Web 控制台扫码配对:${C.reset}`);\n console.log(`${C.cyan}http://<你的路由器IP>:${gwPort}/?token=${gwToken}${C.reset}`);\n console.log(`打开后进入 Channels → WhatsApp 扫码即可。\\n`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'telegram-pairing': {\n console.log(`\\n${C.cyan}启动 Telegram 配对助手...${C.reset}\\n`);\n try {\n await ocCmd('models', 'auth', 'login-telegram-bot');\n } catch (e) {}\n break;\n }\n case 'wizard': {\n console.log(`\\n${C.cyan}启动官方渠道配置向导...${C.reset}\\n`);\n try {\n await ocCmd('configure', '--section', 'channels');\n } catch (e) {}\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 健康检查 (对应 health_check)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleHealthCheck() {\n resetRenderCount();\n console.log(`\\n${C.bold}健康检查${C.reset}\\n`);\n\n // 验证配置文件\n console.log(`${C.cyan}验证配置文件格式...${C.reset}`);\n try {\n const result = await ocCmd('config', 'validate', '--json');\n const validateData = JSON.parse(result.stdout);\n if (validateData.valid) {\n console.log(`${C.green}✅ 配置文件格式有效${C.reset}\\n`);\n } else if (validateData.errors && validateData.errors.length > 0) {\n console.log(`${C.red}❌ 配置文件存在错误:${C.reset}`);\n validateData.errors.forEach(e => console.log(` ${C.yellow}• ${e.message}${C.reset}`));\n console.log('');\n }\n } catch (e) {\n console.log(`${C.yellow}⚠️ 无法验证配置文件${C.reset}\\n`);\n }\n\n // 检查端口\n console.log(`${C.cyan}检查服务状态...${C.reset}`);\n try {\n await runCommand('/etc/init.d/openclaw', ['status_service']);\n } catch (e) {}\n\n console.log(`\\n${C.cyan}提示: 查看详细日志请运行 logread -e openclaw${C.reset}`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 显示当前配置 (对应 show_current_config)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleShowConfig() {\n resetRenderCount();\n\n const gwPort = jsonGet('gateway.port') || '18789';\n const gwBind = jsonGet('gateway.bind') || 'lan';\n const gwMode = jsonGet('gateway.mode') || 'local';\n const currentModel = getCurrentModel();\n\n console.log(`\\n${C.green}┌──────────────────────────────────────────────────────────┐${C.reset}`);\n console.log(`${C.green}│${C.reset} 📋 ${C.bold}当前配置概览${C.reset}`);\n console.log(`${C.green}├──────────────────────────────────────────────────────────┤${C.reset}`);\n console.log(`${C.green}│${C.reset} 网关端口 ............ ${C.cyan}${gwPort}${C.reset}`);\n console.log(`${C.green}│${C.reset} 绑定模式 ............ ${C.cyan}${gwBind}${C.reset}`);\n console.log(`${C.green}│${C.reset} 运行模式 ............ ${C.cyan}${gwMode}${C.reset}`);\n console.log(`${C.green}│${C.reset} 活跃模型 ............ ${currentModel ? C.cyan + currentModel : C.yellow + '未配置'}${C.reset}`);\n\n // 渠道状态\n console.log(`${C.green}├──────────────────────────────────────────────────────────┤${C.reset}`);\n console.log(`${C.green}│${C.reset} ${C.bold}渠道配置状态${C.reset}`);\n\n const tgToken = jsonGet('channels.telegram.botToken');\n const dcToken = jsonGet('channels.discord.botToken');\n const fsAppId = jsonGet('channels.feishu.appId');\n const skToken = jsonGet('channels.slack.botToken');\n const qqAppId = jsonGet('channels.qqbot.appId');\n\n if (qqAppId) {\n console.log(`${C.green}│${C.reset} QQ (qqbot) ......... ${C.green}✅ 已配置${C.reset} (AppID: ${qqAppId.slice(0, 8)}...)`);\n } else {\n console.log(`${C.green}│${C.reset} QQ (qqbot) ......... ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (tgToken) {\n console.log(`${C.green}│${C.reset} Telegram ........... ${C.green}✅ 已配置${C.reset} (${tgToken.slice(0, 12)}...)`);\n } else {\n console.log(`${C.green}│${C.reset} Telegram ........... ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (dcToken) {\n console.log(`${C.green}│${C.reset} Discord ............ ${C.green}✅ 已配置${C.reset}`);\n } else {\n console.log(`${C.green}│${C.reset} Discord ............ ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (fsAppId) {\n console.log(`${C.green}│${C.reset} 飞书 ............... ${C.green}✅ 已配置${C.reset} (AppID: ${fsAppId.slice(0, 6)}...)`);\n } else {\n console.log(`${C.green}│${C.reset} 飞书 ............... ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (skToken) {\n console.log(`${C.green}│${C.reset} Slack .............. ${C.green}✅ 已配置${C.reset}`);\n } else {\n console.log(`${C.green}│${C.reset} Slack .............. ${C.yellow}❌ 未配置${C.reset}`);\n }\n\n console.log(`${C.green}└──────────────────────────────────────────────────────────┘${C.reset}\\n`);\n\n await input({ prompt: '按回车继续', defaultValue: '' });\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 高级配置处理 (对应 advanced_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleAdvancedConfig() {\n while (true) {\n const choice = await showAdvancedMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n\n switch (choice.value) {\n case 'port': {\n const newPort = await input({\n prompt: '请输入 Gateway 端口',\n defaultValue: String(jsonGet('gateway.port') || '18789'),\n });\n if (newPort) {\n jsonSet('gateway.port', parseInt(newPort));\n try { execSync(`uci set openclaw.main.port=\"${newPort}\" && uci commit openclaw`, { stdio: 'ignore' }); } catch {}\n console.log(`\\n${C.green}✅ 端口已设置为 ${newPort}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'bind': {\n const bindChoice = await select({\n title: '绑定地址选项',\n showSearch: false,\n items: [\n { key: '1', label: 'lan', desc: '仅 LAN 接口 (推荐)', value: 'lan' },\n { key: '2', label: 'loopback', desc: '仅本机访问', value: 'loopback' },\n { key: '3', label: 'all', desc: '所有接口 (0.0.0.0)', value: 'all' },\n ],\n });\n if (bindChoice) {\n jsonSet('gateway.bind', bindChoice.value);\n try { execSync(`uci set openclaw.main.bind=\"${bindChoice.value}\" && uci commit openclaw`, { stdio: 'ignore' }); } catch {}\n console.log(`\\n${C.green}✅ 绑定地址已设置为 ${bindChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'mode': {\n const modeChoice = await select({\n title: '运行模式选项',\n showSearch: false,\n items: [\n { key: '1', label: 'local', desc: '本地模式 (推荐)', value: 'local' },\n { key: '2', label: 'remote', desc: '远程模式', value: 'remote' },\n ],\n });\n if (modeChoice) {\n jsonSet('gateway.mode', modeChoice.value);\n console.log(`\\n${C.green}✅ 运行模式已设置为 ${modeChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'loglevel': {\n const levelChoice = await select({\n title: '日志级别选项',\n showSearch: false,\n items: [\n { key: '1', label: 'debug', desc: '详细调试', value: 'debug' },\n { key: '2', label: 'info', desc: '常规信息', value: 'info' },\n { key: '3', label: 'warn', desc: '警告及以上', value: 'warn' },\n { key: '4', label: 'error', desc: '仅错误', value: 'error' },\n ],\n });\n if (levelChoice) {\n jsonSet('gateway.logLevel', levelChoice.value);\n console.log(`\\n${C.green}✅ 日志级别已设置为 ${levelChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'acp': {\n const acpChoice = await select({\n title: 'ACP Dispatch 选项',\n showSearch: false,\n items: [\n { key: '1', label: 'false', desc: '禁用 (推荐路由器使用)', value: 'false' },\n { key: '2', label: 'true', desc: '启用 (可能占用大量内存)', value: 'true' },\n ],\n });\n if (acpChoice) {\n jsonSet('acp.dispatch.enabled', acpChoice.value === 'true');\n console.log(`\\n${C.green}✅ ACP Dispatch 已设置为 ${acpChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'wizard':\n console.log(`\\n${C.cyan}启动官方配置向导...${C.reset}\\n`);\n try {\n await ocCmd('configure');\n } catch (e) {}\n break;\n case 'view-json':\n console.log(`\\n${C.cyan}配置文件路径: ${CONFIG_FILE}${C.reset}\\n`);\n try {\n const content = fs.readFileSync(CONFIG_FILE, 'utf8');\n console.log(content);\n } catch (e) {\n console.log(`${C.red}无法读取配置文件${C.reset}`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n case 'edit':\n console.log(`\\n${C.yellow}请在 SSH 终端中手动编辑配置文件:${C.reset}`);\n console.log(` vi ${CONFIG_FILE}\\n`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n case 'backup':\n await handleBackup();\n break;\n case 'import': {\n const importPath = await input({\n prompt: '请输入备份文件路径',\n defaultValue: '',\n });\n if (importPath && fs.existsSync(importPath)) {\n try {\n fs.copyFileSync(importPath, CONFIG_FILE);\n console.log(`\\n${C.green}✅ 配置已导入${C.reset}\\n`);\n await askRestart();\n } catch (e) {\n console.log(`${C.red}导入失败: ${e.message}${C.reset}\\n`);\n }\n } else {\n console.log(`${C.yellow}文件不存在${C.reset}\\n`);\n }\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 重置配置处理 (对应 reset_to_defaults)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleReset() {\n while (true) {\n const choice = await showResetMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n\n switch (choice.value) {\n case 'gateway': {\n console.log(`\\n${C.yellow}将重置: 网关端口→18789, 绑定→lan, 模式→local${C.reset}`);\n console.log(`${C.yellow}保留: 认证令牌、模型配置、消息渠道${C.reset}\\n`);\n const ok = await confirm({ prompt: '确认恢复网关默认设置?', defaultYes: false });\n if (ok) {\n jsonSet('gateway.port', 18789);\n jsonSet('gateway.bind', 'lan');\n jsonSet('gateway.mode', 'local');\n jsonSet('gateway.controlUi.allowInsecureAuth', true);\n jsonSet('gateway.controlUi.dangerouslyDisableDeviceAuth', true);\n jsonSet('gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback', true);\n jsonSet('gateway.tailscale.mode', 'off');\n console.log(`\\n${C.green}✅ 网关设置已恢复默认${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'models': {\n console.log(`\\n${C.red}⚠️ 将清除: 所有模型配置、API Key、活跃模型设置${C.reset}\\n`);\n const ok = await confirm({ prompt: '确认清除所有模型配置?', defaultYes: false });\n if (ok) {\n const cfg = readConfig();\n delete cfg.models;\n if (cfg.agents?.defaults?.model) delete cfg.agents.defaults.model;\n if (cfg.agents?.defaults?.models) delete cfg.agents.defaults.models;\n writeConfig(cfg);\n // 清除 auth-profiles.json\n const authFile = `${OC_STATE_DIR}/agents/main/agent/auth-profiles.json`;\n try {\n fs.writeFileSync(authFile, JSON.stringify({ version: 1, profiles: {}, usageStats: {} }, null, 2));\n } catch {}\n console.log(`\\n${C.green}✅ 模型配置已清除${C.reset}`);\n console.log(`${C.yellow}请通过菜单 [1] 重新配置 AI 模型${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'channels': {\n console.log(`\\n${C.red}⚠️ 将清除: 所有消息渠道配置 (Telegram/Discord/飞书等)${C.reset}\\n`);\n const ok = await confirm({ prompt: '确认清除所有渠道配置?', defaultYes: false });\n if (ok) {\n const cfg = readConfig();\n delete cfg.channels;\n writeConfig(cfg);\n console.log(`\\n${C.green}✅ 渠道配置已清除${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'full': {\n console.log(`\\n${C.red}╔══════════════════════════════════════════════════════╗${C.reset}`);\n console.log(`${C.red}║ ⚠️ 完全恢复出厂设置 ║${C.reset}`);\n console.log(`${C.red}║ 此操作将删除所有配置并重新初始化 ║${C.reset}`);\n console.log(`${C.red}╚══════════════════════════════════════════════════════╝${C.reset}\\n`);\n\n const confirmStr = await input({ prompt: '输入 RESET 确认恢复出厂设置', defaultValue: '' });\n if (confirmStr !== 'RESET') {\n console.log(`${C.cyan}已取消${C.reset}\\n`);\n break;\n }\n\n // 执行恢复\n console.log(`\\n${C.cyan}[1/5] 停止 Gateway...${C.reset}`);\n try { await runCommand('/etc/init.d/openclaw', ['stop']); } catch {}\n\n console.log(`${C.cyan}[2/5] 备份当前配置...${C.reset}`);\n const backupDir = `${OC_STATE_DIR}/backups`;\n const backupTs = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n try {\n if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${backupDir}\"`, { stdio: \"ignore\" }); } catch {}\n fs.copyFileSync(CONFIG_FILE, `${backupDir}/openclaw_${backupTs}.json`);\n console.log(`${C.green} 备份已保存: backups/openclaw_${backupTs}.json${C.reset}`);\n } catch {}\n\n console.log(`${C.cyan}[3/5] 重置配置...${C.reset}`);\n writeConfig({});\n\n console.log(`${C.cyan}[4/5] 重新初始化...${C.reset}`);\n // 生成新 token\n const crypto = require('crypto');\n const newToken = crypto.randomBytes(24).toString('hex');\n\n console.log(`${C.cyan}[5/5] 应用 OpenWrt 适配配置...${C.reset}`);\n jsonSet('gateway.port', 18789);\n jsonSet('gateway.bind', 'lan');\n jsonSet('gateway.mode', 'local');\n jsonSet('gateway.auth.mode', 'token');\n jsonSet('gateway.auth.token', newToken);\n jsonSet('gateway.controlUi.allowInsecureAuth', true);\n jsonSet('gateway.controlUi.dangerouslyDisableDeviceAuth', true);\n jsonSet('gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback', true);\n jsonSet('gateway.tailscale.mode', 'off');\n jsonSet('acp.dispatch.enabled', false);\n jsonSet('tools.profile', 'coding');\n\n try { execSync(`uci set openclaw.main.token=\"${newToken}\" && uci commit openclaw`, { stdio: 'ignore' }); } catch {}\n\n console.log(`\\n${C.green}✅ 出厂设置已恢复!${C.reset}\\n`);\n console.log(`${C.cyan}新认证令牌: ${newToken}${C.reset}\\n`);\n\n await restartGateway();\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 备份/还原处理 (对应 backup_restore_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleBackup() {\n while (true) {\n const choice = await showBackupMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n const backupDir = `${OC_STATE_DIR}/backups`;\n try { if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${backupDir}\"`, { stdio: \"ignore\" }); } catch {} } catch {}\n\n switch (choice.value) {\n case 'create-config': {\n console.log(`\\n${C.cyan}正在创建配置备份...${C.reset}`);\n try {\n await ocCmd('backup', 'create', '--only-config', '--no-include-workspace');\n console.log(`${C.green}✅ 配置备份已创建${C.reset}\\n`);\n } catch (e) {\n console.log(`${C.yellow}⚠️ 备份功能需要 OpenClaw v2026.3.8+${C.reset}\\n`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'create-full': {\n console.log(`\\n${C.cyan}正在创建完整备份...${C.reset}`);\n try {\n await ocCmd('backup', 'create', '--no-include-workspace');\n console.log(`${C.green}✅ 完整备份已创建${C.reset}\\n`);\n } catch (e) {\n console.log(`${C.yellow}⚠️ 备份失败${C.reset}\\n`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'verify': {\n const files = fs.readdirSync(backupDir).filter(f => f.endsWith('.tar.gz')).sort().reverse();\n if (files.length === 0) {\n console.log(`${C.yellow}未找到备份文件${C.reset}\\n`);\n } else {\n const latest = `${backupDir}/${files[0]}`;\n console.log(`${C.cyan}验证备份: ${latest}${C.reset}`);\n try {\n await ocCmd('backup', 'verify', latest);\n } catch {}\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'list': {\n const files = fs.readdirSync(backupDir).filter(f => f.endsWith('.tar.gz') || f.endsWith('.json')).sort().reverse();\n if (files.length === 0) {\n console.log(`${C.yellow}暂无备份文件${C.reset}\\n`);\n } else {\n console.log(`\\n${C.bold}备份文件列表:${C.reset}`);\n files.slice(0, 10).forEach(f => {\n const stat = fs.statSync(`${backupDir}/${f}`);\n console.log(` ${C.dim}${f} (${(stat.size / 1024).toFixed(1)} KB)${C.reset}`);\n });\n console.log(`${C.dim}\\n备份目录: ${backupDir}${C.reset}\\n`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'restore': {\n const files = fs.readdirSync(backupDir).filter(f => f.endsWith('.json')).sort().reverse();\n if (files.length === 0) {\n console.log(`${C.yellow}未找到配置备份文件${C.reset}\\n`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n\n const fileChoice = await select({\n title: '选择备份文件',\n showSearch: false,\n items: files.slice(0, 10).map((f, i) => ({\n key: String(i + 1),\n label: f,\n desc: '',\n value: f,\n })),\n });\n\n if (!fileChoice) break;\n\n const ok = await confirm({ prompt: '确认恢复此备份?', defaultYes: false });\n if (ok) {\n try {\n fs.copyFileSync(`${backupDir}/${fileChoice.value}`, CONFIG_FILE);\n console.log(`\\n${C.green}✅ 配置已恢复${C.reset}\\n`);\n await askRestart();\n } catch (e) {\n console.log(`${C.red}恢复失败: ${e.message}${C.reset}\\n`);\n }\n }\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 主函数\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function main() {\n const command = process.argv[2];\n\n if (command === 'model') {\n await handleModelConfig();\n return;\n }\n\n if (command === 'status') {\n await runCommand('/etc/init.d/openclaw', ['status_service']);\n return;\n }\n\n if (command === 'restart') {\n const ok = await confirm({ prompt: '确认重启 OpenClaw 服务?' });\n if (ok) await runCommand('/etc/init.d/openclaw', ['restart']);\n return;\n }\n\n // 交互式主菜单\n while (true) {\n const choice = await showMainMenu();\n\n if (!choice || choice.value === 'quit') {\n console.log(`\\n${C.green}再见!${C.reset}\\n`);\n break;\n }\n\n switch (choice.value) {\n case 'model':\n await handleModelConfig();\n break;\n case 'set-active-model':\n await handleSetActiveModel();\n break;\n case 'channels':\n await handleChannels();\n break;\n case 'health':\n await handleHealthCheck();\n break;\n case 'logs':\n resetRenderCount();\n console.log(`\\n${C.cyan}=== OpenClaw 日志 ===${C.reset}\\n`);\n await runCommand('logread', ['-e', 'openclaw']);\n console.log('');\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n case 'restart':\n resetRenderCount();\n await restartGateway();\n break;\n case 'advanced':\n await handleAdvancedConfig();\n break;\n case 'reset':\n await handleReset();\n break;\n case 'show-config':\n await handleShowConfig();\n break;\n case 'backup':\n await handleBackup();\n break;\n }\n }\n}\n\nmain().catch(e => {\n console.error(`${C.red}错误:${C.reset}`, e.message);\n process.exit(1);\n});\n"}, "3088e055156af3d63745a99508dc1c7455a17591": {"t": 1785846962.1448758, "n": 82439, "h": "31994a356171acd1", "c": "#!/usr/bin/env node\n/**\n * ============================================================================\n * OpenClaw 配置工具 — 交互式菜单前端\n * ============================================================================\n *\n * 功能特性:\n * - 方向键 (↑↓) 导航菜单选项 (使用 oc-menu-engine 引擎)\n * - 回车/空格 确认选择\n * - 实时搜索过滤\n * - 与 oc-config.sh 业务逻辑完全一致\n * - 纯 Node.js 实现,零外部依赖\n * - 完全兼容 xterm.js / Web PTY 环境\n */\n\nconst path = require('path');\nconst fs = require('fs');\nconst { spawn, execSync, execFileSync } = require('child_process');\nconst menu = require('./oc-menu-engine');\nconst { C, select, input, confirm, spinner, resetRenderCount } = menu;\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 配置路径 (与 oc-config.sh 保持一致)\n// ═══════════════════════════════════════════════════════════════════════════\n\nconst OC_BASE_PATH = process.env.OC_BASE_PATH || '/opt';\nconst OC_INSTALL_PATH = process.env.OC_INSTALL_PATH || `${OC_BASE_PATH}/openclaw`;\nconst NODE_BASE = process.env.NODE_BASE || `${OC_INSTALL_PATH}/node`;\nconst OC_GLOBAL = process.env.OC_GLOBAL || `${OC_INSTALL_PATH}/global`;\nconst OC_DATA = process.env.OC_DATA || `${OC_INSTALL_PATH}/data`;\nconst OC_STATE_DIR = process.env.OPENCLAW_STATE_DIR || `${OC_DATA}/.openclaw`;\nconst CONFIG_FILE = process.env.OPENCLAW_CONFIG_PATH || `${OC_STATE_DIR}/openclaw.json`;\nconst NODE_BIN = `${NODE_BASE}/bin/node`;\nconst PERMISSIONS_HELPER = '/usr/libexec/openclaw-permissions.sh';\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 辅助函数 (与 oc-config.sh 逻辑对应)\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * 执行命令并返回结果\n */\nfunction runCommand(cmd, args = [], options = {}) {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, {\n stdio: ['inherit', 'pipe', 'pipe'],\n shell: true,\n env: { ...process.env, ...options.env },\n });\n\n let stdout = '';\n let stderr = '';\n\n child.stdout.on('data', (data) => {\n stdout += data;\n process.stdout.write(data);\n });\n\n child.stderr.on('data', (data) => {\n stderr += data;\n process.stderr.write(data);\n });\n\n child.on('close', (code) => {\n if (code === 0) {\n resolve({ stdout, stderr, code });\n } else {\n reject(new Error(`Command failed with code ${code}: ${stderr}`));\n }\n });\n });\n}\n\n/**\n * 读取 JSON 配置文件\n */\nfunction fixStatePermissions() {\n try {\n execFileSync(PERMISSIONS_HELPER, ['fix-state', OC_STATE_DIR], { stdio: 'ignore', timeout: 10000 });\n } catch {\n try {\n execSync(`find \"${OC_STATE_DIR}\" -user root ! -path \"*/extensions*\" ! -path \"*/archived-extensions*\" ! -path \"*/npm/projects*\" -exec chown openclaw:openclaw {} + 2>/dev/null || true`, { stdio: 'ignore' });\n } catch {}\n }\n}\n\nfunction readConfig() {\n try {\n if (fs.existsSync(CONFIG_FILE)) {\n return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));\n }\n } catch (e) {\n console.error(`${C.red}读取配置失败:${C.reset} ${e.message}`);\n }\n return {};\n}\n\n/**\n * 写入 JSON 配置文件\n */\nfunction writeConfig(config) {\n const dir = path.dirname(CONFIG_FILE);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${dir}\"`, { stdio: \"ignore\" }); } catch {}\n }\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));\n try {\n execSync(`chown openclaw:openclaw \"${CONFIG_FILE}\"`, { stdio: 'ignore' });\n fixStatePermissions();\n } catch {}\n}\n\n/**\n * 获取 JSON 配置值 (对应 json_get)\n */\nfunction jsonGet(keyPath) {\n const config = readConfig();\n const keys = keyPath.split('.');\n let value = config;\n for (const key of keys) {\n if (value && typeof value === 'object' && key in value) {\n value = value[key];\n } else {\n return null;\n }\n }\n return value;\n}\n\n/**\n * 设置 JSON 配置值 (对应 json_set)\n */\nfunction jsonSet(keyPath, value) {\n const config = readConfig();\n const keys = keyPath.split('.');\n let obj = config;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!obj[keys[i]]) obj[keys[i]] = {};\n obj = obj[keys[i]];\n }\n obj[keys[keys.length - 1]] = value;\n writeConfig(config);\n}\n\n/**\n * 获取当前活跃模型\n */\nfunction getCurrentModel() {\n const primary = jsonGet('agents.defaults.model.primary');\n if (primary) return primary;\n const config = readConfig();\n if (config.models?.defaultModel) return config.models.defaultModel;\n return null;\n}\n\n/**\n * 查找 OpenClaw 入口文件\n */\nfunction findOpenClawEntry() {\n const searchPaths = [\n `${OC_GLOBAL}/lib/node_modules/openclaw/openclaw.mjs`,\n `${OC_GLOBAL}/lib/node_modules/openclaw/dist/cli.js`,\n `${OC_GLOBAL}/node_modules/openclaw/openclaw.mjs`,\n `${OC_GLOBAL}/node_modules/openclaw/dist/cli.js`,\n ];\n for (const p of searchPaths) {\n if (fs.existsSync(p)) return p;\n }\n try {\n const dirs = fs.readdirSync(OC_GLOBAL);\n for (const dir of dirs) {\n const p = `${OC_GLOBAL}/${dir}/node_modules/openclaw/openclaw.mjs`;\n if (fs.existsSync(p)) return p;\n }\n } catch {}\n return null;\n}\n\n/**\n * 执行 OpenClaw CLI 命令 (对应 oc_cmd)\n */\nasync function ocCmd(...args) {\n const ocEntry = findOpenClawEntry();\n if (!ocEntry) throw new Error('OpenClaw 未安装');\n return runCommand(NODE_BIN, [ocEntry, ...args], {\n env: {\n OPENCLAW_HOME: OC_DATA,\n OPENCLAW_CONFIG_PATH: CONFIG_FILE,\n OPENCLAW_STATE_DIR: OC_STATE_DIR,\n HOME: OC_DATA,\n }\n });\n}\n\n/**\n * 注册模型并设为默认 (对应 register_and_set_model)\n */\nfunction registerAndSetModel(modelId) {\n const config = readConfig();\n if (!config.agents) config.agents = {};\n if (!config.agents.defaults) config.agents.defaults = {};\n if (!config.agents.defaults.models) config.agents.defaults.models = {};\n if (!config.agents.defaults.model) config.agents.defaults.model = {};\n config.agents.defaults.models[modelId] = {};\n config.agents.defaults.model.primary = modelId;\n writeConfig(config);\n}\n\n/**\n * 写入 API Key 到 auth-profiles.json (对应 auth_set_apikey)\n */\nfunction authSetApikey(provider, apiKey, profileId) {\n const authDir = `${OC_STATE_DIR}/agents/main/agent`;\n const authFile = `${authDir}/auth-profiles.json`;\n try {\n fs.mkdirSync(authDir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${authDir}\"`, { stdio: \"ignore\" }); } catch {}\n } catch {}\n\n let authData = { version: 1, profiles: {}, usageStats: {} };\n try {\n if (fs.existsSync(authFile)) {\n authData = JSON.parse(fs.readFileSync(authFile, 'utf8'));\n }\n } catch {}\n\n if (!authData.profiles) authData.profiles = {};\n authData.profiles[profileId || `${provider}:manual`] = {\n type: 'api_key',\n provider: provider,\n key: apiKey,\n };\n\n fs.writeFileSync(authFile, JSON.stringify(authData, null, 2));\n try {\n execSync(`chown openclaw:openclaw \"${authFile}\"`, { stdio: 'ignore' });\n fixStatePermissions();\n } catch {}\n}\n\n/**\n * 注册自定义提供商 (对应 register_custom_provider)\n */\nfunction registerCustomProvider(providerName, baseUrl, apiKey, modelId, modelDisplay, ctxWindow, maxTok) {\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers[providerName] = {\n baseUrl: baseUrl,\n apiKey: apiKey,\n api: 'openai-completions',\n models: [{\n id: modelId,\n name: modelDisplay || modelId,\n reasoning: false,\n input: ['text', 'image'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: parseInt(ctxWindow) || 128000,\n maxTokens: parseInt(maxTok) || 32000,\n }]\n };\n writeConfig(config);\n}\n\n/**\n * 注册 Coding Plan 提供商 (对应 register_codingplan_provider)\n */\nfunction registerCodingPlanProvider(apiKey) {\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['bailian'] = {\n baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',\n apiKey: apiKey,\n api: 'openai-completions',\n models: [\n { id: 'qwen3.5-plus', name: 'qwen3.5-plus', reasoning: false, input: ['text', 'image'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1000000, maxTokens: 65536 },\n { id: 'qwen3-coder-plus', name: 'qwen3-coder-plus', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1000000, maxTokens: 65536 },\n { id: 'qwen3-coder-next', name: 'qwen3-coder-next', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 65536 },\n { id: 'qwen3-max-2026-01-23', name: 'qwen3-max-2026-01-23', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 65536 },\n { id: 'MiniMax-M2.5', name: 'MiniMax-M2.5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204800, maxTokens: 131072 },\n { id: 'glm-5', name: 'glm-5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202752, maxTokens: 16384 },\n { id: 'glm-4.7', name: 'glm-4.7', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202752, maxTokens: 16384 },\n { id: 'kimi-k2.5', name: 'kimi-k2.5', reasoning: false, input: ['text', 'image'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 32768 },\n ]\n };\n\n if (!config.agents) config.agents = {};\n if (!config.agents.defaults) config.agents.defaults = {};\n if (!config.agents.defaults.models) config.agents.defaults.models = {};\n ['qwen3.5-plus', 'qwen3-coder-plus', 'qwen3-coder-next', 'qwen3-max-2026-01-23', 'MiniMax-M2.5', 'glm-5', 'glm-4.7', 'kimi-k2.5'].forEach(m => {\n config.agents.defaults.models[`bailian/${m}`] = {};\n });\n\n writeConfig(config);\n}\n\n/**\n * 注册腾讯云 Coding Plan 提供商 (对应 register_lkeap_codingplan_provider)\n */\nfunction registerLkeapCodingPlanProvider(apiKey) {\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['lkeap'] = {\n baseUrl: 'https://api.lkeap.cloud.tencent.com/coding/v3',\n apiKey: apiKey,\n api: 'openai-completions',\n models: [\n { id: 'tc-code-latest', name: 'Auto (智能匹配最优模型)', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 8192 },\n { id: 'hunyuan-2.0-instruct', name: 'Tencent HY 2.0 Instruct', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 16000 },\n { id: 'hunyuan-2.0-thinking', name: 'Tencent HY 2.0 Think', reasoning: true, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 64000 },\n { id: 'hunyuan-t1', name: 'Hunyuan-T1', reasoning: true, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 32000, maxTokens: 64000 },\n { id: 'hunyuan-turbos', name: 'Hunyuan-TurboS', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 32000, maxTokens: 16000 },\n { id: 'minimax-m2.5', name: 'MiniMax-M2.5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204800, maxTokens: 131072 },\n { id: 'kimi-k2.5', name: 'Kimi-K2.5', reasoning: false, input: ['text', 'image'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 32768 },\n { id: 'glm-5', name: 'GLM-5', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202752, maxTokens: 8192 },\n ]\n };\n\n if (!config.agents) config.agents = {};\n if (!config.agents.defaults) config.agents.defaults = {};\n if (!config.agents.defaults.models) config.agents.defaults.models = {};\n ['tc-code-latest', 'hunyuan-2.0-instruct', 'hunyuan-2.0-thinking', 'hunyuan-t1', 'hunyuan-turbos', 'minimax-m2.5', 'kimi-k2.5', 'glm-5'].forEach(m => {\n config.agents.defaults.models[`lkeap/${m}`] = {};\n });\n\n writeConfig(config);\n}\n\n/**\n * 重启 Gateway\n */\nasync function restartGateway() {\n resetRenderCount();\n console.log(`\\n${C.yellow}正在重启 Gateway...${C.reset}`);\n\n try {\n await runCommand('/bin/sh', ['-c', [\n \"if [ \\\"$(uci -q get openclaw.main.enabled 2>/dev/null || echo 0)\\\" != \\\"1\\\" ]; then\",\n \" uci -q set openclaw.main.enabled='1';\",\n \" uci -q commit openclaw;\",\n \" /etc/init.d/openclaw enable >/dev/null 2>&1 || true;\",\n 'fi;',\n '/etc/init.d/openclaw restart_gateway >/dev/null 2>&1 || true;',\n '/etc/init.d/openclaw start >/dev/null 2>&1 || true'\n ].join(' ')]);\n } catch {}\n\n const spin = spinner({ text: 'Gateway 启动中,请稍候 (约 15-30 秒)...' });\n spin.start();\n\n const gwPort = jsonGet('gateway.port') || '18789';\n let waited = 0;\n const maxWait = 30;\n\n while (waited < maxWait) {\n await new Promise(r => setTimeout(r, 3000));\n waited += 3;\n try {\n // 兼容 OpenWrt: 优先 ss,回退 netstat\n let stdout = '';\n try {\n stdout = execSync('ss -tulnp 2>/dev/null', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n } catch {\n // ss 不存在,使用 netstat\n try {\n stdout = execSync('netstat -tulnp 2>/dev/null', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n } catch {}\n }\n if (stdout && stdout.includes(`:${gwPort} `)) {\n spin.succeed(`Gateway 已重启成功 (${waited}秒)`);\n return;\n }\n } catch {}\n }\n\n spin.stop(`${C.yellow}Gateway 仍在启动中,请稍后确认${C.reset}`);\n console.log(`${C.cyan} 查看日志: logread -e openclaw${C.reset}\\n`);\n}\n\n/**\n * 询问是否重启\n */\nasync function askRestart() {\n const ok = await confirm({ prompt: '是否立即重启服务以应用配置?', defaultYes: true });\n if (ok) {\n await restartGateway();\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 主菜单 (对应 main_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showMainMenu() {\n const currentModel = getCurrentModel();\n const statusLine = currentModel\n ? `${C.green}当前模型: ${currentModel}${C.reset}`\n : `${C.yellow}未配置模型${C.reset}`;\n\n const result = await select({\n title: 'OpenClaw AI Gateway — OpenWrt 配置管理',\n header: statusLine,\n showSearch: false,\n items: [\n { label: `${C.dim}━━━ AI 模型配置 ━━━${C.reset}`, disabled: true },\n { key: '1', label: '配置 AI 模型和提供商', desc: '', value: 'model' },\n { key: '2', label: '设置活动模型', desc: '', value: 'set-active-model' },\n\n { label: `${C.dim}━━━ 消息渠道 ━━━${C.reset}`, disabled: true },\n { key: '3', label: '配置消息渠道', desc: '电报/QQ/飞书', value: 'channels' },\n\n { label: `${C.dim}━━━ 系统管理 ━━━${C.reset}`, disabled: true },\n { key: '4', label: '健康检查与状态', desc: '', value: 'health' },\n { key: '5', label: '查看日志', desc: '', value: 'logs' },\n { key: '6', label: '重启 Gateway', desc: '', value: 'restart' },\n\n { label: `${C.dim}━━━ 高级选项 ━━━${C.reset}`, disabled: true },\n { key: '7', label: '高级配置', desc: '', value: 'advanced' },\n { key: '8', label: '重置配置', desc: '', value: 'reset' },\n { key: '9', label: '显示当前配置概览', desc: '', value: 'show-config' },\n { key: '10', label: '备份/还原配置', desc: '', value: 'backup' },\n\n { label: '', disabled: true },\n { key: '0', label: '退出', desc: '', value: 'quit' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 模型配置菜单 (对应 configure_model)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showModelMenu() {\n const result = await select({\n title: '配置 AI 模型和提供商',\n showSearch: true,\n items: [\n { label: `${C.green}${C.bold}── 推荐 ──${C.reset}`, disabled: true },\n { key: 'w', label: '官方完整模型配置向导', desc: `${C.green}(推荐,支持所有提供商)${C.reset}`, value: 'wizard' },\n\n { label: `${C.bold}── 国外模型提供商 ──${C.reset}`, disabled: true },\n { key: 'a', label: 'OpenAI', desc: 'GPT-5.2, GPT-5 mini, GPT-4.1', value: 'openai' },\n { key: 'b', label: 'Anthropic', desc: 'Claude Sonnet 4, Opus 4, Haiku', value: 'anthropic' },\n { key: 'c', label: 'Google Gemini', desc: 'Gemini 2.5 Pro/Flash, Gemini 3', value: 'google' },\n { key: 'd', label: 'OpenRouter', desc: '聚合多家模型', value: 'openrouter' },\n { key: 'e', label: 'GitHub Copilot', desc: '需要 Copilot 订阅', value: 'copilot' },\n { key: 'f', label: 'xAI Grok', desc: 'Grok-4/3', value: 'xai' },\n\n { label: `${C.bold}── 国内模型提供商 ──${C.reset}`, disabled: true },\n { key: 'g', label: '阿里云通义千问 Qwen', desc: 'Portal/API/Coding Plan', value: 'qwen' },\n { key: 'h', label: '硅基流动 SiliconFlow', desc: '', value: 'siliconflow' },\n { key: 'i', label: '腾讯云 Coding Plan', desc: 'HY T1/TurboS/GLM-5/Kimi', value: 'tencent' },\n { key: 'j', label: '百度千帆', desc: 'ERNIE-4.0, ERNIE-3.5', value: 'baidu' },\n { key: 'k', label: '智谱 GLM / Z.AI', desc: 'GLM-5.1, GLM-5, GLM-4.7', value: 'zhipu' },\n\n { label: `${C.bold}── 本地模型 / 自定义 API ──${C.reset}`, disabled: true },\n { key: 'l', label: 'Ollama', desc: '本地模型,无需 API Key', value: 'ollama' },\n { key: 'm', label: '自定义 OpenAI 兼容 API', desc: '', value: 'custom' },\n { key: 'n', label: '自定义 Anthropic 兼容 API', desc: '', value: 'custom-anthropic' },\n { key: 'o', label: '一万AI分享 粉丝专享 API', desc: '', value: 'yiwanai-fan' },\n\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 消息渠道菜单 (对应 configure_channels)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showChannelsMenu() {\n const result = await select({\n title: '配置消息渠道',\n showSearch: false,\n items: [\n { label: `${C.dim}提示: 微信配置请使用 LuCI 界面「微信配置」菜单${C.reset}`, disabled: true },\n { label: '', disabled: true },\n { key: '1', label: 'QQ 机器人', desc: '腾讯QQ', value: 'qq' },\n { key: '2', label: 'Telegram', desc: `${C.green}最常用${C.reset}`, value: 'telegram' },\n { key: '3', label: 'Discord', desc: '', value: 'discord' },\n { key: '4', label: '飞书 (Feishu)', desc: '', value: 'feishu' },\n { key: '5', label: 'Slack', desc: '', value: 'slack' },\n { key: '6', label: 'WhatsApp', desc: '需通过 Web 控制台扫码', value: 'whatsapp' },\n { key: '7', label: 'Telegram 配对助手', desc: '', value: 'telegram-pairing' },\n { key: '8', label: '官方完整渠道配置向导', desc: '', value: 'wizard' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 高级配置菜单 (对应 advanced_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showAdvancedMenu() {\n const gwPort = jsonGet('gateway.port') || '18789';\n const gwBind = jsonGet('gateway.bind') || 'lan';\n const gwMode = jsonGet('gateway.mode') || 'local';\n const logLevel = jsonGet('gateway.logLevel') || '未设置';\n const acpDispatch = jsonGet('acp.dispatch.enabled') || 'false';\n\n const result = await select({\n title: '高级配置',\n showSearch: false,\n items: [\n { key: '1', label: 'Gateway 端口', desc: `当前: ${gwPort}`, value: 'port' },\n { key: '2', label: 'Gateway 绑定地址', desc: `当前: ${gwBind}`, value: 'bind' },\n { key: '3', label: 'Gateway 运行模式', desc: `当前: ${gwMode}`, value: 'mode' },\n { key: '4', label: '日志级别', desc: `当前: ${logLevel}`, value: 'loglevel' },\n { key: '5', label: 'ACP Dispatch 设置', desc: `当前: ${acpDispatch}`, value: 'acp' },\n { key: '6', label: '官方完整配置向导', desc: 'oc configure', value: 'wizard' },\n { key: '7', label: '查看原始配置 JSON', desc: '', value: 'view-json' },\n { key: '8', label: '编辑配置文件', desc: 'vi / nano', value: 'edit' },\n { key: '9', label: '导出配置备份', desc: '', value: 'backup' },\n { key: '10', label: '导入配置', desc: '', value: 'import' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 重置配置菜单 (对应 reset_to_defaults)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showResetMenu() {\n const result = await select({\n title: '恢复默认配置',\n showSearch: false,\n items: [\n { label: `${C.yellow}请选择恢复级别:${C.reset}`, disabled: true },\n { label: '', disabled: true },\n { key: '1', label: '仅重置网关设置', desc: '端口/绑定/模式恢复默认,保留模型和渠道', value: 'gateway' },\n { key: '2', label: '清除模型配置', desc: '移除所有 AI 模型和 API Key', value: 'models' },\n { key: '3', label: '清除渠道配置', desc: '移除所有消息渠道配置', value: 'channels' },\n { key: '4', label: '完全恢复出厂', desc: '删除所有配置,重新初始化', value: 'full' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 备份/还原菜单 (对应 backup_restore_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function showBackupMenu() {\n const result = await select({\n title: '备份/还原配置',\n showSearch: false,\n items: [\n { key: '1', label: '创建配置备份', desc: '仅配置文件', value: 'create-config' },\n { key: '2', label: '创建完整备份', desc: '配置 + 状态数据', value: 'create-full' },\n { key: '3', label: '验证最新备份', desc: '', value: 'verify' },\n { key: '4', label: '查看备份列表', desc: '', value: 'list' },\n { key: '5', label: '从最新备份恢复配置', desc: '', value: 'restore' },\n { label: '', disabled: true },\n { key: '0', label: '返回', desc: '', value: 'back' },\n ],\n });\n\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 模型配置处理函数 (与 oc-config.sh 完全对应)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function configureOpenAI() {\n resetRenderCount();\n console.log(`\\n${C.bold}OpenAI 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://platform.openai.com/api-keys${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 OpenAI API Key (sk-...)', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'OpenAI 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'gpt-5.2', desc: '最强编程与代理旗舰 (推荐)', value: 'gpt-5.2' },\n { key: 'b', label: 'gpt-5-mini', desc: '高性价比推理', value: 'gpt-5-mini' },\n { key: 'c', label: 'gpt-5-nano', desc: '极速低成本', value: 'gpt-5-nano' },\n { key: 'd', label: 'gpt-4.1', desc: '最强非推理模型', value: 'gpt-4.1' },\n { key: 'e', label: 'o3', desc: '推理模型', value: 'o3' },\n { key: 'f', label: 'o4-mini', desc: '推理轻量', value: 'o4-mini' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'gpt-5.2' });\n if (!modelName) return false;\n }\n\n authSetApikey('openai', apiKey);\n registerAndSetModel(`openai/${modelName}`);\n console.log(`\\n${C.green}✅ OpenAI 已配置,活跃模型: openai/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureAnthropic() {\n resetRenderCount();\n console.log(`\\n${C.bold}Anthropic 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://console.anthropic.com/settings/keys${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Anthropic API Key (sk-ant-...)', placeholder: 'sk-ant-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Anthropic 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'claude-sonnet-4-20250514', desc: 'Claude Sonnet 4 (推荐)', value: 'claude-sonnet-4-20250514' },\n { key: 'b', label: 'claude-opus-4-20250514', desc: 'Claude Opus 4 顶级推理', value: 'claude-opus-4-20250514' },\n { key: 'c', label: 'claude-haiku-4-5', desc: 'Claude Haiku 4.5 轻量快速', value: 'claude-haiku-4-5' },\n { key: 'd', label: 'claude-sonnet-4.5', desc: 'Claude Sonnet 4.5', value: 'claude-sonnet-4.5' },\n { key: 'e', label: 'claude-sonnet-4.6', desc: 'Claude Sonnet 4.6', value: 'claude-sonnet-4.6' },\n { key: 'f', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'claude-sonnet-4-20250514' });\n if (!modelName) return false;\n }\n\n authSetApikey('anthropic', apiKey);\n registerAndSetModel(`anthropic/${modelName}`);\n console.log(`\\n${C.green}✅ Anthropic 已配置,活跃模型: anthropic/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureGoogle() {\n resetRenderCount();\n console.log(`\\n${C.bold}Google Gemini 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://aistudio.google.com/apikey${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Google AI API Key', placeholder: 'AIza...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Google Gemini 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'gemini-2.5-pro', desc: '旗舰推理 (推荐)', value: 'gemini-2.5-pro' },\n { key: 'b', label: 'gemini-2.5-flash', desc: '快速均衡', value: 'gemini-2.5-flash' },\n { key: 'c', label: 'gemini-2.5-flash-lite', desc: '极速低成本', value: 'gemini-2.5-flash-lite' },\n { key: 'd', label: 'gemini-3-flash-preview', desc: 'Gemini 3 Flash 预览', value: 'gemini-3-flash-preview' },\n { key: 'e', label: 'gemini-3-pro-preview', desc: 'Gemini 3 Pro 预览', value: 'gemini-3-pro-preview' },\n { key: 'f', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'gemini-2.5-pro' });\n if (!modelName) return false;\n }\n\n authSetApikey('google', apiKey);\n registerAndSetModel(`google/${modelName}`);\n console.log(`\\n${C.green}✅ Google Gemini 已配置,活跃模型: google/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureOpenRouter() {\n resetRenderCount();\n console.log(`\\n${C.bold}OpenRouter 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://openrouter.ai/keys${C.reset}`);\n console.log(`${C.dim}聚合多家模型,一个 Key 可调用所有主流模型${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 OpenRouter API Key', placeholder: 'sk-or-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'OpenRouter 常用模型',\n showSearch: false,\n items: [\n { key: 'a', label: 'anthropic/claude-sonnet-4', desc: 'Claude Sonnet 4 (推荐)', value: 'anthropic/claude-sonnet-4' },\n { key: 'b', label: 'anthropic/claude-opus-4', desc: 'Claude Opus 4', value: 'anthropic/claude-opus-4' },\n { key: 'c', label: 'openai/gpt-5.2', desc: 'GPT-5.2', value: 'openai/gpt-5.2' },\n { key: 'd', label: 'google/gemini-2.5-pro', desc: 'Gemini 2.5 Pro', value: 'google/gemini-2.5-pro' },\n { key: 'e', label: 'deepseek/deepseek-r1', desc: 'DeepSeek R1', value: 'deepseek/deepseek-r1' },\n { key: 'f', label: 'meta-llama/llama-4-maverick', desc: 'Meta Llama 4', value: 'meta-llama/llama-4-maverick' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称 (格式: provider/model)', defaultValue: 'anthropic/claude-sonnet-4' });\n if (!modelName) return false;\n }\n\n authSetApikey('openrouter', apiKey);\n registerCustomProvider('openrouter', 'https://openrouter.ai/api/v1', apiKey, modelName, modelName);\n registerAndSetModel(`openrouter/${modelName}`);\n console.log(`\\n${C.green}✅ OpenRouter 已配置,活跃模型: openrouter/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureCopilot() {\n resetRenderCount();\n console.log(`\\n${C.bold}GitHub Copilot 配置${C.reset}`);\n console.log(`${C.yellow}需要有效的 GitHub Copilot 订阅 (Free/Pro/Business 均可)${C.reset}\\n`);\n\n console.log(`${C.cyan}启动 GitHub Copilot OAuth 登录...${C.reset}`);\n console.log(`${C.dim}请在浏览器中打开显示的 URL,输入授权码完成登录${C.reset}\\n`);\n\n try {\n await ocCmd('models', 'auth', 'login-github-copilot', '--yes');\n console.log(`\\n${C.green}✅ GitHub Copilot OAuth 认证成功${C.reset}\\n`);\n\n const modelChoice = await select({\n title: 'GitHub Copilot 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'gpt-4.1', desc: 'GPT-4.1 (推荐)', value: 'gpt-4.1' },\n { key: 'b', label: 'gpt-4o', desc: 'GPT-4o', value: 'gpt-4o' },\n { key: 'c', label: 'gpt-5', desc: 'GPT-5', value: 'gpt-5' },\n { key: 'd', label: 'gpt-5-mini', desc: 'GPT-5 mini', value: 'gpt-5-mini' },\n { key: 'e', label: 'gpt-5.1', desc: 'GPT-5.1', value: 'gpt-5.1' },\n { key: 'f', label: 'gpt-5.2', desc: 'GPT-5.2', value: 'gpt-5.2' },\n { key: 'g', label: 'gpt-5.2-codex', desc: 'GPT-5.2 Codex', value: 'gpt-5.2-codex' },\n { key: 'h', label: 'claude-sonnet-4', desc: 'Claude Sonnet 4', value: 'claude-sonnet-4' },\n { key: 'i', label: 'claude-sonnet-4.5', desc: 'Claude Sonnet 4.5', value: 'claude-sonnet-4.5' },\n { key: 'j', label: 'claude-sonnet-4.6', desc: 'Claude Sonnet 4.6', value: 'claude-sonnet-4.6' },\n { key: 'k', label: 'gemini-2.5-pro', desc: 'Gemini 2.5 Pro', value: 'gemini-2.5-pro' },\n { key: 'm', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n\n if (modelChoice) {\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'gpt-4.1' });\n if (!modelName) return false;\n }\n registerAndSetModel(`github-copilot/${modelName}`);\n console.log(`\\n${C.green}✅ 活跃模型已设置: github-copilot/${modelName}${C.reset}\\n`);\n }\n return true;\n } catch (e) {\n console.log(`\\n${C.yellow}OAuth 授权已退出或失败${C.reset}\\n`);\n return false;\n }\n}\n\nasync function configureXAI() {\n resetRenderCount();\n console.log(`\\n${C.bold}xAI Grok 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://console.x.ai${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 xAI API Key', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'xAI Grok 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'grok-4', desc: 'Grok 4 旗舰 (推荐)', value: 'grok-4' },\n { key: 'b', label: 'grok-4-fast', desc: 'Grok 4 Fast', value: 'grok-4-fast' },\n { key: 'c', label: 'grok-3', desc: 'Grok 3', value: 'grok-3' },\n { key: 'd', label: 'grok-3-fast', desc: 'Grok 3 Fast', value: 'grok-3-fast' },\n { key: 'e', label: 'grok-3-mini', desc: 'Grok 3 Mini', value: 'grok-3-mini' },\n { key: 'f', label: 'grok-3-mini-fast', desc: 'Grok 3 Mini Fast', value: 'grok-3-mini-fast' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'grok-4' });\n if (!modelName) return false;\n }\n\n authSetApikey('xai', apiKey);\n registerAndSetModel(`xai/${modelName}`);\n console.log(`\\n${C.green}✅ xAI Grok 已配置,活跃模型: xai/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureQwen() {\n resetRenderCount();\n console.log(`\\n${C.bold}阿里云通义千问 Qwen 配置${C.reset}\\n`);\n\n const modeChoice = await select({\n title: '配置方式',\n showSearch: false,\n items: [\n { key: 'a', label: '通过官方向导配置', desc: 'Qwen Portal OAuth', value: 'portal' },\n { key: 'b', label: '百炼按量付费 API Key', desc: 'sk-xxx, 按 token 计费', value: 'bailian' },\n { key: 'c', label: 'Coding Plan 套餐', desc: `${C.green}★ 推荐${C.reset}`, value: 'codingplan' },\n ],\n });\n if (!modeChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n if (modeChoice.value === 'portal') {\n console.log(`\\n${C.cyan}启动 Qwen OAuth 授权...${C.reset}\\n`);\n try {\n await ocCmd('models', 'auth', 'login', '--provider', 'qwen-portal', '--set-default');\n } catch {}\n console.log(`\\n${C.yellow}OAuth 授权已退出${C.reset}\\n`);\n return true;\n }\n\n if (modeChoice.value === 'bailian') {\n console.log(`\\n${C.bold}百炼按量付费配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://dashscope.console.aliyun.com/apiKey${C.reset}`);\n console.log(`${C.dim}Base URL: https://dashscope.aliyuncs.com/compatible-mode/v1${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入百炼 API Key (sk-...)', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '百炼模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'qwen-max', desc: '千问Max 旗舰模型 (推荐)', value: 'qwen-max' },\n { key: 'b', label: 'qwen-plus', desc: '千问Plus 均衡之选', value: 'qwen-plus' },\n { key: 'c', label: 'qwen-flash', desc: '千问Flash 速度最快', value: 'qwen-flash' },\n { key: 'd', label: 'qwen-turbo', desc: '千问Turbo 经济实惠', value: 'qwen-turbo' },\n { key: 'e', label: 'qwen-long', desc: '千问Long 超长上下文', value: 'qwen-long' },\n { key: 'f', label: 'qwen3-coder-plus', desc: '代码专用旗舰', value: 'qwen3-coder-plus' },\n { key: 'g', label: 'qwq-plus', desc: 'QwQ推理模型', value: 'qwq-plus' },\n { key: 'h', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'qwen-max' });\n if (!modelName) return false;\n }\n\n authSetApikey('dashscope', apiKey);\n registerCustomProvider('dashscope', 'https://dashscope.aliyuncs.com/compatible-mode/v1', apiKey, modelName, modelName);\n registerAndSetModel(`dashscope/${modelName}`);\n console.log(`\\n${C.green}✅ 通义千问已配置 (按量付费),活跃模型: dashscope/${modelName}${C.reset}\\n`);\n return true;\n }\n\n if (modeChoice.value === 'codingplan') {\n console.log(`\\n${C.bold}Coding Plan 套餐配置${C.reset}`);\n console.log(`${C.yellow}订阅套餐: https://bailian.console.aliyun.com/cn-beijing/?tab=model#/efm/coding_plan${C.reset}`);\n console.log(`${C.dim}Base URL: https://coding.dashscope.aliyuncs.com/v1${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Coding Plan 专属 API Key (sk-sp-...)', placeholder: 'sk-sp-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Coding Plan 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'qwen3.5-plus', desc: 'Qwen3.5 Plus (推荐, 100万上下文)', value: 'qwen3.5-plus' },\n { key: 'b', label: 'qwen3-coder-plus', desc: 'Qwen3 Coder Plus', value: 'qwen3-coder-plus' },\n { key: 'c', label: 'qwen3-coder-next', desc: 'Qwen3 Coder Next', value: 'qwen3-coder-next' },\n { key: 'd', label: 'qwen3-max-2026-01-23', desc: 'Qwen3 Max', value: 'qwen3-max-2026-01-23' },\n { key: 'e', label: 'MiniMax-M2.5', desc: 'MiniMax M2.5', value: 'MiniMax-M2.5' },\n { key: 'f', label: 'glm-5', desc: '智谱 GLM-5', value: 'glm-5' },\n { key: 'g', label: 'kimi-k2.5', desc: 'Kimi K2.5', value: 'kimi-k2.5' },\n { key: 'h', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'qwen3.5-plus' });\n if (!modelName) return false;\n }\n\n console.log(`\\n${C.cyan}正在注册 Coding Plan 提供商 (含全部可用模型)...${C.reset}`);\n authSetApikey('bailian', apiKey);\n registerCodingPlanProvider(apiKey);\n registerAndSetModel(`bailian/${modelName}`);\n console.log(`\\n${C.green}✅ Coding Plan 已配置,活跃模型: bailian/${modelName}${C.reset}`);\n console.log(`${C.dim}提示: 套餐内全部模型已注册,可随时在 WebChat 中通过 /model 切换${C.reset}\\n`);\n return true;\n }\n\n return false;\n}\n\nasync function configureSiliconFlow() {\n resetRenderCount();\n console.log(`\\n${C.bold}硅基流动 SiliconFlow 配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://cloud.siliconflow.cn/account/ak${C.reset}`);\n console.log(`${C.yellow}国内推理平台,支持多种开源模型${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 SiliconFlow API Key', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n console.log(`\\n${C.cyan}可用模型分类说明:${C.reset}`);\n console.log(`${C.yellow}* Pro模型: 仅支持充值余额支付${C.reset}`);\n console.log(`${C.yellow}* 非Pro模型: 支持代金券/免费额度${C.reset}\\n`);\n\n const modelChoice = await select({\n title: 'SiliconFlow 模型选择',\n showSearch: false,\n items: [\n { label: `${C.cyan}── 非Pro模型 (支持代金券) ──${C.reset}`, disabled: true },\n { key: '1', label: 'deepseek-ai/DeepSeek-V3', desc: 'DeepSeek-V3 (推荐)', value: 'deepseek-ai/DeepSeek-V3' },\n { key: '2', label: 'deepseek-ai/DeepSeek-R1', desc: 'DeepSeek-R1 推理模型', value: 'deepseek-ai/DeepSeek-R1' },\n { key: '3', label: 'Qwen/Qwen2.5-72B-Instruct', desc: '通义千问 2.5 72B', value: 'Qwen/Qwen2.5-72B-Instruct' },\n { key: '4', label: 'Qwen/Qwen2.5-7B-Instruct', desc: '通义千问 2.5 7B', value: 'Qwen/Qwen2.5-7B-Instruct' },\n { key: '5', label: 'THUDM/glm-4-9b-chat', desc: '智谱 GLM-4 9B', value: 'THUDM/glm-4-9b-chat' },\n { key: '6', label: '01-ai/Yi-1.5-34B-Chat-16K', desc: '零一万物 Yi-1.5', value: '01-ai/Yi-1.5-34B-Chat-16K' },\n { label: `${C.cyan}── Pro模型 (仅支持充值余额) ──${C.reset}`, disabled: true },\n { key: '7', label: 'Pro/deepseek-ai/DeepSeek-V3', desc: 'DeepSeek-V3 (Pro)', value: 'Pro/deepseek-ai/DeepSeek-V3' },\n { key: '8', label: 'Pro/zai-org/GLM-5', desc: '智谱 GLM-5', value: 'Pro/zai-org/GLM-5' },\n { key: '0', label: '手动输入其他模型名称', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型详细名称', defaultValue: 'deepseek-ai/DeepSeek-V3' });\n if (!modelName) return false;\n }\n\n authSetApikey('siliconflow', apiKey);\n registerCustomProvider('siliconflow', 'https://api.siliconflow.cn/v1', apiKey, modelName, modelName);\n registerAndSetModel(`siliconflow/${modelName}`);\n console.log(`\\n${C.green}✅ SiliconFlow 已配置,活跃模型: siliconflow/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureTencent() {\n resetRenderCount();\n console.log(`\\n${C.bold}腾讯云大模型 Coding Plan 套餐配置${C.reset}`);\n console.log(`${C.yellow}订阅/管理套餐: https://hunyuan.cloud.tencent.com/#/app/subscription${C.reset}`);\n console.log(`${C.dim}文档: https://cloud.tencent.com/document/product/1772/128947${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入 Coding Plan API Key (sk-sp-...)', placeholder: 'sk-sp-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '腾讯云 Coding Plan 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'tc-code-latest', desc: '自动路由 (推荐)', value: 'tc-code-latest' },\n { key: 'b', label: 'hunyuan-t1', desc: '混元 T1 深度推理', value: 'hunyuan-t1' },\n { key: 'c', label: 'hunyuan-2.0-thinking', desc: '混元 2.0 Thinking', value: 'hunyuan-2.0-thinking' },\n { key: 'd', label: 'hunyuan-turbos', desc: '混元 TurboS 旗舰', value: 'hunyuan-turbos' },\n { key: 'e', label: 'hunyuan-2.0-instruct', desc: '混元 2.0 Instruct', value: 'hunyuan-2.0-instruct' },\n { key: 'f', label: 'glm-5', desc: '智谱 GLM-5', value: 'glm-5' },\n { key: 'g', label: 'kimi-k2.5', desc: 'Moonshot Kimi K2.5', value: 'kimi-k2.5' },\n { key: 'h', label: 'minimax-m2.5', desc: 'MiniMax M2.5', value: 'minimax-m2.5' },\n { key: 'z', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'tc-code-latest' });\n if (!modelName) return false;\n }\n\n console.log(`\\n${C.cyan}正在注册腾讯云 Coding Plan 提供商 (含全部套餐模型)...${C.reset}`);\n authSetApikey('lkeap', apiKey);\n registerLkeapCodingPlanProvider(apiKey);\n registerAndSetModel(`lkeap/${modelName}`);\n console.log(`\\n${C.green}✅ 腾讯云 Coding Plan 已配置,活跃模型: lkeap/${modelName}${C.reset}`);\n console.log(`${C.dim}提示: 套餐内全部模型已注册,可随时在 WebChat 中通过 /model 切换${C.reset}\\n`);\n return true;\n}\n\nasync function configureBaidu() {\n resetRenderCount();\n console.log(`\\n${C.bold}百度千帆大模型配置${C.reset}`);\n console.log(`${C.yellow}获取 API Key: https://console.bce.baidu.com/qianfan/ais/console/onlineService${C.reset}\\n`);\n\n const apiKey = await input({ prompt: '请输入百度千帆 API Key (Access Token)', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '百度千帆模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'ernie-4.0-8k', desc: '文心一言 4.0 (推荐)', value: 'ernie-4.0-8k' },\n { key: 'b', label: 'ernie-3.5-8k', desc: '文心一言 3.5', value: 'ernie-3.5-8k' },\n { key: 'c', label: 'ernie-4.0-turbo-8k', desc: '文心一言 4.0 Turbo', value: 'ernie-4.0-turbo-8k' },\n { key: 'd', label: 'ernie-speed-8k', desc: '文心一言 Speed 极速', value: 'ernie-speed-8k' },\n { key: 'e', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'ernie-4.0-8k' });\n if (!modelName) return false;\n }\n\n authSetApikey('qianfan', apiKey);\n registerCustomProvider('qianfan', 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop', apiKey, modelName, modelName);\n registerAndSetModel(`qianfan/${modelName}`);\n console.log(`\\n${C.green}✅ 百度千帆已配置,活跃模型: qianfan/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureZhipu() {\n resetRenderCount();\n console.log(`\\n${C.bold}智谱 GLM / Z.AI 配置${C.reset}\\n`);\n\n const methodChoice = await select({\n title: '认证方式',\n showSearch: false,\n items: [\n { key: 'a', label: 'CN (open.bigmodel.cn)', desc: `${C.green}★ 国内用户推荐${C.reset}`, value: 'cn' },\n { key: 'b', label: 'Coding-Plan-CN', desc: '智谱 Coding Plan 套餐', value: 'coding-plan-cn' },\n { key: 'c', label: 'Global (api.z.ai)', desc: '全球版', value: 'global' },\n { key: 'd', label: '手动输入', desc: '', value: 'custom' },\n ],\n });\n if (!methodChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let baseUrl = 'https://open.bigmodel.cn/api/paas/v4';\n switch (methodChoice.value) {\n case 'cn':\n console.log(`${C.yellow}获取 API Key: https://open.bigmodel.cn/api-key${C.reset}`);\n break;\n case 'coding-plan-cn':\n baseUrl = 'https://open.bigmodel.cn/api/coding/paas/v4';\n console.log(`${C.yellow}Coding Plan 套餐 API Key${C.reset}`);\n break;\n case 'global':\n baseUrl = 'https://api.z.ai/api/paas/v4';\n console.log(`${C.yellow}全球版 API Key${C.reset}`);\n break;\n case 'custom':\n const customUrl = await input({ prompt: '请输入 Base URL', defaultValue: 'https://open.bigmodel.cn/api/paas/v4' });\n if (customUrl) baseUrl = customUrl;\n break;\n }\n\n const apiKey = await input({ prompt: '请输入智谱 API Key', placeholder: '' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: '智谱 GLM 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'glm-5.1', desc: 'GLM-5.1 (推荐)', value: 'glm-5.1' },\n { key: 'b', label: 'glm-5', desc: 'GLM-5', value: 'glm-5' },\n { key: 'c', label: 'glm-4.7', desc: 'GLM-4.7', value: 'glm-4.7' },\n { key: 'd', label: 'glm-4.7-flash', desc: 'GLM-4.7 Flash', value: 'glm-4.7-flash' },\n { key: 'e', label: 'glm-4.5', desc: 'GLM-4.5', value: 'glm-4.5' },\n { key: 'f', label: 'glm-4.5-flash', desc: 'GLM-4.5 Flash (免费)', value: 'glm-4.5-flash' },\n { key: 'g', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'glm-5.1' });\n if (!modelName) return false;\n }\n\n // 智谱 GLM 使用原生 zai provider (OpenClaw 内置支持)\n registerCustomProvider('zai', baseUrl, apiKey, modelName, modelName, 128000, 4096);\n authSetApikey('zai', apiKey);\n registerAndSetModel(`zai/${modelName}`);\n console.log(`\\n${C.green}✅ 智谱 GLM 已配置,活跃模型: zai/${modelName}${C.reset}`);\n console.log(`${C.dim} Base URL: ${baseUrl}${C.reset}\\n`);\n return true;\n}\n\nasync function configureOllama() {\n resetRenderCount();\n console.log(`\\n${C.bold}Ollama 本地模型配置${C.reset}`);\n console.log(`${C.yellow}Ollama 在本地或局域网运行大模型,无需 API Key${C.reset}`);\n console.log(`${C.yellow}安装 Ollama: https://ollama.com${C.reset}\\n`);\n\n const modeChoice = await select({\n title: '连接方式',\n showSearch: false,\n items: [\n { key: 'a', label: '本机运行', desc: 'localhost:11434', value: 'local' },\n { key: 'b', label: '局域网其他设备', desc: '', value: 'remote' },\n ],\n });\n if (!modeChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let ollamaUrl = 'http://127.0.0.1:11434';\n if (modeChoice.value === 'remote') {\n const host = await input({ prompt: 'Ollama 地址 (如 192.168.1.100:11434)', placeholder: '' });\n if (!host) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n ollamaUrl = host.startsWith('http') ? host : `http://${host}`;\n ollamaUrl = ollamaUrl.replace(/\\/v1$/, '').replace(/\\/$/, '');\n }\n\n // 尝试检测 Ollama 连通性\n console.log(`\\n${C.cyan}检测 Ollama 连通性...${C.reset}`);\n let modelList = [];\n try {\n const stdout = execSync(`curl -sf --connect-timeout 3 --max-time 5 ${ollamaUrl}/api/tags`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n const data = JSON.parse(stdout);\n modelList = data.models || [];\n console.log(`${C.green}✅ Ollama 已连接${C.reset}`);\n } catch {\n console.log(`${C.yellow}⚠️ 无法连接 Ollama (${ollamaUrl})${C.reset}`);\n const continueAnyway = await confirm({ prompt: '仍要继续配置?', defaultYes: false });\n if (!continueAnyway) return false;\n }\n\n let modelName = '';\n if (modelList.length > 0) {\n const items = modelList.map((m, i) => ({\n key: String(i + 1),\n label: m.name,\n desc: '',\n value: m.name,\n }));\n items.push({ key: 'm', label: '手动输入模型名', desc: '', value: 'custom' });\n\n const modelChoice = await select({\n title: '已安装的模型',\n showSearch: false,\n items,\n });\n\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n modelName = modelChoice.value === 'custom'\n ? await input({ prompt: '请输入模型名称', defaultValue: 'llama3.3' })\n : modelChoice.value;\n } else {\n modelName = await input({ prompt: '请输入模型名称', placeholder: 'llama3.3, qwen2.5...' });\n }\n\n if (!modelName) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n // 注册 Ollama 提供商\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['ollama'] = {\n baseUrl: ollamaUrl,\n apiKey: 'ollama-local',\n api: 'ollama',\n models: [{\n id: modelName,\n name: modelName,\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 128000,\n maxTokens: 32000,\n }]\n };\n writeConfig(config);\n\n authSetApikey('ollama', 'ollama-local', 'ollama:local');\n registerAndSetModel(`ollama/${modelName}`);\n console.log(`\\n${C.green}✅ Ollama 已配置,活跃模型: ollama/${modelName}${C.reset}`);\n console.log(`${C.cyan} Ollama 地址: ${ollamaUrl}${C.reset}\\n`);\n return true;\n}\n\nasync function configureCustomAPI() {\n resetRenderCount();\n console.log(`\\n${C.bold}自定义 OpenAI 兼容 API 配置${C.reset}`);\n console.log(`${C.yellow}支持任何兼容 OpenAI API 格式的服务商${C.reset}\\n`);\n\n const baseUrl = await input({ prompt: 'API Base URL (如 https://api.example.com/v1)', placeholder: '' });\n if (!baseUrl) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const apiKey = await input({ prompt: 'API Key', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelName = await input({ prompt: '模型名称', placeholder: '' });\n if (!modelName) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n authSetApikey('openai-compatible', apiKey, 'openai-compatible:manual');\n registerCustomProvider('openai-compatible', baseUrl.replace(/\\/$/, ''), apiKey, modelName, modelName);\n registerAndSetModel(`openai-compatible/${modelName}`);\n console.log(`\\n${C.green}✅ 自定义 API 已配置,活跃模型: openai-compatible/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureCustomAnthropic() {\n resetRenderCount();\n console.log(`\\n${C.bold}自定义 Anthropic 兼容 API 配置${C.reset}`);\n console.log(`${C.yellow}支持任何兼容 Anthropic Messages API 格式的服务商${C.reset}\\n`);\n\n const baseUrl = await input({ prompt: 'API Base URL', placeholder: 'https://api.anthropic.com' });\n if (!baseUrl) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const apiKey = await input({ prompt: 'API Key', placeholder: 'sk-ant-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n const modelChoice = await select({\n title: 'Anthropic 模型选择',\n showSearch: false,\n items: [\n { key: 'a', label: 'claude-sonnet-4-20250514', desc: 'Claude Sonnet 4 (推荐)', value: 'claude-sonnet-4-20250514' },\n { key: 'b', label: 'claude-opus-4-20250514', desc: 'Claude Opus 4', value: 'claude-opus-4-20250514' },\n { key: 'c', label: '手动输入模型名', desc: '', value: 'custom' },\n ],\n });\n if (!modelChoice) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n let modelName = modelChoice.value;\n if (modelChoice.value === 'custom') {\n modelName = await input({ prompt: '请输入模型名称', defaultValue: 'claude-sonnet-4-20250514' });\n if (!modelName) return false;\n }\n\n const config = readConfig();\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.mode = 'merge';\n config.models.providers['anthropic-compatible'] = {\n baseUrl: baseUrl.replace(/\\/$/, ''),\n apiKey: apiKey,\n api: 'anthropic-messages',\n models: [{\n id: modelName,\n name: modelName,\n reasoning: false,\n input: ['text', 'image'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 200000,\n maxTokens: 16000,\n }]\n };\n writeConfig(config);\n\n authSetApikey('anthropic-compatible', apiKey, 'anthropic-compatible:manual');\n registerAndSetModel(`anthropic-compatible/${modelName}`);\n console.log(`\\n${C.green}✅ 自定义 Anthropic API 已配置,活跃模型: anthropic-compatible/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function configureYiwanAIFanAPI() {\n resetRenderCount();\n const providerName = 'yiwanai';\n const baseUrl = 'https://api.910501.xyz/v1';\n const modelName = 'gpt-5.5';\n\n console.log(`\\n${C.bold}一万AI分享 粉丝专享 API 配置${C.reset}`);\n console.log(`${C.yellow}OpenAI 兼容模式;Base URL 和模型已内置,只需要填写 API Key。${C.reset}`);\n console.log(`${C.dim}Base URL: ${baseUrl}${C.reset}`);\n console.log(`${C.dim}Model: ${modelName}${C.reset}\\n`);\n\n const apiKey = await input({ prompt: 'API Key', placeholder: 'sk-...' });\n if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }\n\n authSetApikey(providerName, apiKey, `${providerName}:fan`);\n registerCustomProvider(providerName, baseUrl, apiKey, modelName, modelName, 1000000, 32000);\n const config = readConfig();\n const provider = config.models?.providers?.[providerName];\n if (provider?.models?.[0]) {\n provider.models[0].reasoning = true;\n }\n writeConfig(config);\n registerAndSetModel(`${providerName}/${modelName}`);\n console.log(`\\n${C.green}✅ 一万AI分享粉丝专享 API 已配置,活跃模型: ${providerName}/${modelName}${C.reset}\\n`);\n return true;\n}\n\nasync function launchWizard() {\n resetRenderCount();\n console.log(`\\n${C.cyan}启动官方完整模型配置向导...${C.reset}\\n`);\n console.log(`${C.yellow}提示: ↑↓ 移动, Tab/空格 选中, 回车 确认${C.reset}\\n`);\n try {\n await ocCmd('configure', '--section', 'model');\n } catch (e) {\n console.log(`${C.yellow}配置向导已退出${C.reset}\\n`);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 模型配置处理入口\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleModelConfig() {\n while (true) {\n const choice = await showModelMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n let configured = false;\n\n switch (choice.value) {\n case 'wizard': await launchWizard(); break;\n case 'openai': configured = await configureOpenAI(); break;\n case 'anthropic': configured = await configureAnthropic(); break;\n case 'google': configured = await configureGoogle(); break;\n case 'openrouter': configured = await configureOpenRouter(); break;\n case 'copilot': configured = await configureCopilot(); break;\n case 'xai': configured = await configureXAI(); break;\n case 'qwen': configured = await configureQwen(); break;\n case 'siliconflow': configured = await configureSiliconFlow(); break;\n case 'tencent': configured = await configureTencent(); break;\n case 'baidu': configured = await configureBaidu(); break;\n case 'zhipu': configured = await configureZhipu(); break;\n case 'ollama': configured = await configureOllama(); break;\n case 'custom': configured = await configureCustomAPI(); break;\n case 'custom-anthropic': configured = await configureCustomAnthropic(); break;\n case 'yiwanai-fan': configured = await configureYiwanAIFanAPI(); break;\n }\n\n if (configured) await askRestart();\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 设置活动模型 (对应 set_active_model)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleSetActiveModel() {\n resetRenderCount();\n console.log(`\\n${C.bold}设定当前活跃模型${C.reset}\\n`);\n\n const currentModel = getCurrentModel();\n console.log(` 当前活跃模型: ${C.green}${C.bold}${currentModel || '未设置'}${C.reset}\\n`);\n\n try {\n const result = await ocCmd('models', 'list', '--json');\n const modelsData = JSON.parse(result.stdout);\n const models = modelsData.models || [];\n\n if (models.length > 0) {\n const items = models.map((m, i) => ({\n key: String(i + 1),\n label: m.key,\n desc: m.name && m.name !== m.key ? `(${m.name})` : '',\n value: m.key,\n selected: m.key === currentModel,\n }));\n\n items.push({ label: '', disabled: true });\n items.push({ key: 'm', label: '手动输入模型 ID', desc: '', value: 'manual' });\n items.push({ key: '0', label: '返回', desc: '', value: 'back' });\n\n const choice = await select({\n title: '已配置的模型',\n showSearch: true,\n items,\n });\n\n if (!choice || choice.value === 'back') return;\n\n if (choice.value === 'manual') {\n const manualModel = await input({\n prompt: '请输入模型 ID',\n placeholder: 'openai/gpt-4o',\n defaultValue: currentModel || '',\n });\n if (manualModel) {\n registerAndSetModel(manualModel);\n console.log(`\\n${C.green}✅ 活跃模型已设为: ${manualModel}${C.reset}\\n`);\n await askRestart();\n }\n } else {\n registerAndSetModel(choice.value);\n console.log(`\\n${C.green}✅ 活跃模型已切换为: ${choice.value}${C.reset}\\n`);\n await askRestart();\n }\n } else {\n console.log(`${C.yellow}尚未配置任何模型。${C.reset}`);\n console.log(`${C.yellow}请先通过「配置 AI 模型提供商」添加模型。${C.reset}\\n`);\n\n const manualModel = await input({\n prompt: '直接输入模型 ID 设置 (留空返回)',\n defaultValue: '',\n });\n if (manualModel) {\n registerAndSetModel(manualModel);\n console.log(`\\n${C.green}✅ 活跃模型已设为: ${manualModel}${C.reset}\\n`);\n await askRestart();\n }\n }\n } catch (e) {\n console.log(`${C.red}获取模型列表失败: ${e.message}${C.reset}\\n`);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 消息渠道处理 (对应 configure_channels 等函数)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleChannels() {\n while (true) {\n const choice = await showChannelsMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n\n switch (choice.value) {\n case 'qq': {\n console.log(`\\n${C.bold}QQ 机器人配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 App ID 和 App Secret:${C.reset}`);\n console.log(` 1. 前往 ${C.cyan}https://q.qq.com/qqbot/openclaw/login.html${C.reset}`);\n console.log(` 2. 用手机 QQ 扫码注册/登录`);\n console.log(` 3. 创建机器人后复制 App ID 和 App Secret\\n`);\n\n const appId = await input({ prompt: '请输入 QQ 机器人 App ID', placeholder: '' });\n if (!appId) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n const appSecret = await input({ prompt: '请输入 QQ 机器人 App Secret', placeholder: '' });\n if (!appSecret) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n // 写入配置\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.qqbot) cfg.channels.qqbot = {};\n cfg.channels.qqbot.appId = appId;\n cfg.channels.qqbot.clientSecret = appSecret;\n cfg.channels.qqbot.enabled = true;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ QQ 机器人配置已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'telegram': {\n console.log(`\\n${C.bold}Telegram Bot 配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 Bot Token:${C.reset}`);\n console.log(` 1. 打开 Telegram → 搜索 ${C.cyan}@BotFather${C.reset}`);\n console.log(` 2. 发送 ${C.cyan}/newbot${C.reset} → 按提示创建`);\n console.log(` 3. 复制生成的 Token\\n`);\n\n const token = await input({ prompt: '请输入 Telegram Bot Token', placeholder: '123456:ABC...' });\n if (!token) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n // 验证格式\n if (!/^[0-9]+:[A-Za-z0-9_-]+$/.test(token)) {\n console.log(`${C.red}✗ Token 格式错误${C.reset}`);\n console.log(`${C.yellow}正确格式: 123456789:ABCdefGHIjklMNOpqr${C.reset}\\n`);\n break;\n }\n\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.telegram) cfg.channels.telegram = {};\n cfg.channels.telegram.botToken = token;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ Telegram Bot Token 已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'discord': {\n console.log(`\\n${C.bold}Discord Bot 配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 Bot Token:${C.cyan} https://discord.com/developers/applications${C.reset}\\n`);\n\n const token = await input({ prompt: '请输入 Discord Bot Token', placeholder: '' });\n if (!token) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.discord) cfg.channels.discord = {};\n cfg.channels.discord.botToken = token;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ Discord Bot Token 已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'feishu': {\n console.log(`\\n${C.bold}飞书 Bot 配置${C.reset}\\n`);\n console.log(`${C.cyan}即将执行飞书官方安装向导...${C.reset}\\n`);\n\n const doInstall = await confirm({ prompt: '是否开始安装?', defaultYes: true });\n if (!doInstall) break;\n\n console.log(`\\n${C.cyan}正在启动飞书安装向导...${C.reset}\\n`);\n try {\n await runCommand('npx', ['-y', '@larksuite/openclaw-lark-tools', 'install'], { env: { HOME: OC_DATA } });\n } catch (e) {\n console.log(`${C.yellow}安装向导已退出${C.reset}\\n`);\n }\n break;\n }\n case 'slack': {\n console.log(`\\n${C.bold}Slack Bot 配置${C.reset}\\n`);\n console.log(`${C.yellow}获取 Bot Token:${C.cyan} https://api.slack.com/apps${C.reset}\\n`);\n\n const token = await input({ prompt: '请输入 Slack Bot Token (xoxb-...)', placeholder: '' });\n if (!token) { console.log(`${C.yellow}已取消${C.reset}`); break; }\n\n const cfg = readConfig();\n if (!cfg.channels) cfg.channels = {};\n if (!cfg.channels.slack) cfg.channels.slack = {};\n cfg.channels.slack.botToken = token;\n writeConfig(cfg);\n\n console.log(`\\n${C.green}✅ Slack Bot Token 已保存${C.reset}\\n`);\n await askRestart();\n break;\n }\n case 'whatsapp': {\n const gwToken = jsonGet('gateway.auth.token') || '';\n const gwPort = jsonGet('gateway.port') || '18789';\n console.log(`\\n${C.yellow}WhatsApp 需要通过 Web 控制台扫码配对:${C.reset}`);\n console.log(`${C.cyan}http://<你的路由器IP>:${gwPort}/?token=${gwToken}${C.reset}`);\n console.log(`打开后进入 Channels → WhatsApp 扫码即可。\\n`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'telegram-pairing': {\n console.log(`\\n${C.cyan}启动 Telegram 配对助手...${C.reset}\\n`);\n try {\n await ocCmd('models', 'auth', 'login-telegram-bot');\n } catch (e) {}\n break;\n }\n case 'wizard': {\n console.log(`\\n${C.cyan}启动官方渠道配置向导...${C.reset}\\n`);\n try {\n await ocCmd('configure', '--section', 'channels');\n } catch (e) {}\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 健康检查 (对应 health_check)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleHealthCheck() {\n resetRenderCount();\n console.log(`\\n${C.bold}健康检查${C.reset}\\n`);\n\n // 验证配置文件\n console.log(`${C.cyan}验证配置文件格式...${C.reset}`);\n try {\n const result = await ocCmd('config', 'validate', '--json');\n const validateData = JSON.parse(result.stdout);\n if (validateData.valid) {\n console.log(`${C.green}✅ 配置文件格式有效${C.reset}\\n`);\n } else if (validateData.errors && validateData.errors.length > 0) {\n console.log(`${C.red}❌ 配置文件存在错误:${C.reset}`);\n validateData.errors.forEach(e => console.log(` ${C.yellow}• ${e.message}${C.reset}`));\n console.log('');\n }\n } catch (e) {\n console.log(`${C.yellow}⚠️ 无法验证配置文件${C.reset}\\n`);\n }\n\n // 检查端口\n console.log(`${C.cyan}检查服务状态...${C.reset}`);\n try {\n await runCommand('/etc/init.d/openclaw', ['status_service']);\n } catch (e) {}\n\n console.log(`\\n${C.cyan}提示: 查看详细日志请运行 logread -e openclaw${C.reset}`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 显示当前配置 (对应 show_current_config)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleShowConfig() {\n resetRenderCount();\n\n const gwPort = jsonGet('gateway.port') || '18789';\n const gwBind = jsonGet('gateway.bind') || 'lan';\n const gwMode = jsonGet('gateway.mode') || 'local';\n const currentModel = getCurrentModel();\n\n console.log(`\\n${C.green}┌──────────────────────────────────────────────────────────┐${C.reset}`);\n console.log(`${C.green}│${C.reset} 📋 ${C.bold}当前配置概览${C.reset}`);\n console.log(`${C.green}├──────────────────────────────────────────────────────────┤${C.reset}`);\n console.log(`${C.green}│${C.reset} 网关端口 ............ ${C.cyan}${gwPort}${C.reset}`);\n console.log(`${C.green}│${C.reset} 绑定模式 ............ ${C.cyan}${gwBind}${C.reset}`);\n console.log(`${C.green}│${C.reset} 运行模式 ............ ${C.cyan}${gwMode}${C.reset}`);\n console.log(`${C.green}│${C.reset} 活跃模型 ............ ${currentModel ? C.cyan + currentModel : C.yellow + '未配置'}${C.reset}`);\n\n // 渠道状态\n console.log(`${C.green}├──────────────────────────────────────────────────────────┤${C.reset}`);\n console.log(`${C.green}│${C.reset} ${C.bold}渠道配置状态${C.reset}`);\n\n const tgToken = jsonGet('channels.telegram.botToken');\n const dcToken = jsonGet('channels.discord.botToken');\n const fsAppId = jsonGet('channels.feishu.appId');\n const skToken = jsonGet('channels.slack.botToken');\n const qqAppId = jsonGet('channels.qqbot.appId');\n\n if (qqAppId) {\n console.log(`${C.green}│${C.reset} QQ (qqbot) ......... ${C.green}✅ 已配置${C.reset} (AppID: ${qqAppId.slice(0, 8)}...)`);\n } else {\n console.log(`${C.green}│${C.reset} QQ (qqbot) ......... ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (tgToken) {\n console.log(`${C.green}│${C.reset} Telegram ........... ${C.green}✅ 已配置${C.reset} (${tgToken.slice(0, 12)}...)`);\n } else {\n console.log(`${C.green}│${C.reset} Telegram ........... ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (dcToken) {\n console.log(`${C.green}│${C.reset} Discord ............ ${C.green}✅ 已配置${C.reset}`);\n } else {\n console.log(`${C.green}│${C.reset} Discord ............ ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (fsAppId) {\n console.log(`${C.green}│${C.reset} 飞书 ............... ${C.green}✅ 已配置${C.reset} (AppID: ${fsAppId.slice(0, 6)}...)`);\n } else {\n console.log(`${C.green}│${C.reset} 飞书 ............... ${C.yellow}❌ 未配置${C.reset}`);\n }\n if (skToken) {\n console.log(`${C.green}│${C.reset} Slack .............. ${C.green}✅ 已配置${C.reset}`);\n } else {\n console.log(`${C.green}│${C.reset} Slack .............. ${C.yellow}❌ 未配置${C.reset}`);\n }\n\n console.log(`${C.green}└──────────────────────────────────────────────────────────┘${C.reset}\\n`);\n\n await input({ prompt: '按回车继续', defaultValue: '' });\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 高级配置处理 (对应 advanced_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleAdvancedConfig() {\n while (true) {\n const choice = await showAdvancedMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n\n switch (choice.value) {\n case 'port': {\n const newPort = await input({\n prompt: '请输入 Gateway 端口',\n defaultValue: String(jsonGet('gateway.port') || '18789'),\n });\n if (newPort) {\n jsonSet('gateway.port', parseInt(newPort));\n try { execSync(`uci set openclaw.main.port=\"${newPort}\" && uci commit openclaw`, { stdio: 'ignore' }); } catch {}\n console.log(`\\n${C.green}✅ 端口已设置为 ${newPort}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'bind': {\n const bindChoice = await select({\n title: '绑定地址选项',\n showSearch: false,\n items: [\n { key: '1', label: 'lan', desc: '仅 LAN 接口 (推荐)', value: 'lan' },\n { key: '2', label: 'loopback', desc: '仅本机访问', value: 'loopback' },\n { key: '3', label: 'all', desc: '所有接口 (0.0.0.0)', value: 'all' },\n ],\n });\n if (bindChoice) {\n jsonSet('gateway.bind', bindChoice.value);\n try { execSync(`uci set openclaw.main.bind=\"${bindChoice.value}\" && uci commit openclaw`, { stdio: 'ignore' }); } catch {}\n console.log(`\\n${C.green}✅ 绑定地址已设置为 ${bindChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'mode': {\n const modeChoice = await select({\n title: '运行模式选项',\n showSearch: false,\n items: [\n { key: '1', label: 'local', desc: '本地模式 (推荐)', value: 'local' },\n { key: '2', label: 'remote', desc: '远程模式', value: 'remote' },\n ],\n });\n if (modeChoice) {\n jsonSet('gateway.mode', modeChoice.value);\n console.log(`\\n${C.green}✅ 运行模式已设置为 ${modeChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'loglevel': {\n const levelChoice = await select({\n title: '日志级别选项',\n showSearch: false,\n items: [\n { key: '1', label: 'debug', desc: '详细调试', value: 'debug' },\n { key: '2', label: 'info', desc: '常规信息', value: 'info' },\n { key: '3', label: 'warn', desc: '警告及以上', value: 'warn' },\n { key: '4', label: 'error', desc: '仅错误', value: 'error' },\n ],\n });\n if (levelChoice) {\n jsonSet('gateway.logLevel', levelChoice.value);\n console.log(`\\n${C.green}✅ 日志级别已设置为 ${levelChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'acp': {\n const acpChoice = await select({\n title: 'ACP Dispatch 选项',\n showSearch: false,\n items: [\n { key: '1', label: 'false', desc: '禁用 (推荐路由器使用)', value: 'false' },\n { key: '2', label: 'true', desc: '启用 (可能占用大量内存)', value: 'true' },\n ],\n });\n if (acpChoice) {\n jsonSet('acp.dispatch.enabled', acpChoice.value === 'true');\n console.log(`\\n${C.green}✅ ACP Dispatch 已设置为 ${acpChoice.value}${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'wizard':\n console.log(`\\n${C.cyan}启动官方配置向导...${C.reset}\\n`);\n try {\n await ocCmd('configure');\n } catch (e) {}\n break;\n case 'view-json':\n console.log(`\\n${C.cyan}配置文件路径: ${CONFIG_FILE}${C.reset}\\n`);\n try {\n const content = fs.readFileSync(CONFIG_FILE, 'utf8');\n console.log(content);\n } catch (e) {\n console.log(`${C.red}无法读取配置文件${C.reset}`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n case 'edit':\n console.log(`\\n${C.yellow}请在 SSH 终端中手动编辑配置文件:${C.reset}`);\n console.log(` vi ${CONFIG_FILE}\\n`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n case 'backup':\n await handleBackup();\n break;\n case 'import': {\n const importPath = await input({\n prompt: '请输入备份文件路径',\n defaultValue: '',\n });\n if (importPath && fs.existsSync(importPath)) {\n try {\n fs.copyFileSync(importPath, CONFIG_FILE);\n console.log(`\\n${C.green}✅ 配置已导入${C.reset}\\n`);\n await askRestart();\n } catch (e) {\n console.log(`${C.red}导入失败: ${e.message}${C.reset}\\n`);\n }\n } else {\n console.log(`${C.yellow}文件不存在${C.reset}\\n`);\n }\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 重置配置处理 (对应 reset_to_defaults)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleReset() {\n while (true) {\n const choice = await showResetMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n\n switch (choice.value) {\n case 'gateway': {\n console.log(`\\n${C.yellow}将重置: 网关端口→18789, 绑定→lan, 模式→local${C.reset}`);\n console.log(`${C.yellow}保留: 认证令牌、模型配置、消息渠道${C.reset}\\n`);\n const ok = await confirm({ prompt: '确认恢复网关默认设置?', defaultYes: false });\n if (ok) {\n jsonSet('gateway.port', 18789);\n jsonSet('gateway.bind', 'lan');\n jsonSet('gateway.mode', 'local');\n jsonSet('gateway.controlUi.allowInsecureAuth', true);\n jsonSet('gateway.controlUi.dangerouslyDisableDeviceAuth', true);\n jsonSet('gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback', true);\n jsonSet('gateway.tailscale.mode', 'off');\n console.log(`\\n${C.green}✅ 网关设置已恢复默认${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'models': {\n console.log(`\\n${C.red}⚠️ 将清除: 所有模型配置、API Key、活跃模型设置${C.reset}\\n`);\n const ok = await confirm({ prompt: '确认清除所有模型配置?', defaultYes: false });\n if (ok) {\n const cfg = readConfig();\n delete cfg.models;\n if (cfg.agents?.defaults?.model) delete cfg.agents.defaults.model;\n if (cfg.agents?.defaults?.models) delete cfg.agents.defaults.models;\n writeConfig(cfg);\n // 清除 auth-profiles.json\n const authFile = `${OC_STATE_DIR}/agents/main/agent/auth-profiles.json`;\n try {\n fs.writeFileSync(authFile, JSON.stringify({ version: 1, profiles: {}, usageStats: {} }, null, 2));\n } catch {}\n console.log(`\\n${C.green}✅ 模型配置已清除${C.reset}`);\n console.log(`${C.yellow}请通过菜单 [1] 重新配置 AI 模型${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'channels': {\n console.log(`\\n${C.red}⚠️ 将清除: 所有消息渠道配置 (Telegram/Discord/飞书等)${C.reset}\\n`);\n const ok = await confirm({ prompt: '确认清除所有渠道配置?', defaultYes: false });\n if (ok) {\n const cfg = readConfig();\n delete cfg.channels;\n writeConfig(cfg);\n console.log(`\\n${C.green}✅ 渠道配置已清除${C.reset}\\n`);\n await askRestart();\n }\n break;\n }\n case 'full': {\n console.log(`\\n${C.red}╔══════════════════════════════════════════════════════╗${C.reset}`);\n console.log(`${C.red}║ ⚠️ 完全恢复出厂设置 ║${C.reset}`);\n console.log(`${C.red}║ 此操作将删除所有配置并重新初始化 ║${C.reset}`);\n console.log(`${C.red}╚══════════════════════════════════════════════════════╝${C.reset}\\n`);\n\n const confirmStr = await input({ prompt: '输入 RESET 确认恢复出厂设置', defaultValue: '' });\n if (confirmStr !== 'RESET') {\n console.log(`${C.cyan}已取消${C.reset}\\n`);\n break;\n }\n\n // 执行恢复\n console.log(`\\n${C.cyan}[1/5] 停止 Gateway...${C.reset}`);\n try { await runCommand('/etc/init.d/openclaw', ['stop']); } catch {}\n\n console.log(`${C.cyan}[2/5] 备份当前配置...${C.reset}`);\n const backupDir = `${OC_STATE_DIR}/backups`;\n const backupTs = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n try {\n if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${backupDir}\"`, { stdio: \"ignore\" }); } catch {}\n fs.copyFileSync(CONFIG_FILE, `${backupDir}/openclaw_${backupTs}.json`);\n console.log(`${C.green} 备份已保存: backups/openclaw_${backupTs}.json${C.reset}`);\n } catch {}\n\n console.log(`${C.cyan}[3/5] 重置配置...${C.reset}`);\n writeConfig({});\n\n console.log(`${C.cyan}[4/5] 重新初始化...${C.reset}`);\n // 生成新 token\n const crypto = require('crypto');\n const newToken = crypto.randomBytes(24).toString('hex');\n\n console.log(`${C.cyan}[5/5] 应用 OpenWrt 适配配置...${C.reset}`);\n jsonSet('gateway.port', 18789);\n jsonSet('gateway.bind', 'lan');\n jsonSet('gateway.mode', 'local');\n jsonSet('gateway.auth.mode', 'token');\n jsonSet('gateway.auth.token', newToken);\n jsonSet('gateway.controlUi.allowInsecureAuth', true);\n jsonSet('gateway.controlUi.dangerouslyDisableDeviceAuth', true);\n jsonSet('gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback', true);\n jsonSet('gateway.tailscale.mode', 'off');\n jsonSet('acp.dispatch.enabled', false);\n jsonSet('tools.profile', 'coding');\n\n try { execSync(`uci set openclaw.main.token=\"${newToken}\" && uci commit openclaw`, { stdio: 'ignore' }); } catch {}\n\n console.log(`\\n${C.green}✅ 出厂设置已恢复!${C.reset}\\n`);\n console.log(`${C.cyan}新认证令牌: ${newToken}${C.reset}\\n`);\n\n await restartGateway();\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 备份/还原处理 (对应 backup_restore_menu)\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function handleBackup() {\n while (true) {\n const choice = await showBackupMenu();\n if (!choice || choice.value === 'back') break;\n\n resetRenderCount();\n const backupDir = `${OC_STATE_DIR}/backups`;\n try { if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true }); try { execSync(`chown openclaw:openclaw \"${backupDir}\"`, { stdio: \"ignore\" }); } catch {} } catch {}\n\n switch (choice.value) {\n case 'create-config': {\n console.log(`\\n${C.cyan}正在创建配置备份...${C.reset}`);\n try {\n await ocCmd('backup', 'create', '--only-config', '--no-include-workspace');\n console.log(`${C.green}✅ 配置备份已创建${C.reset}\\n`);\n } catch (e) {\n console.log(`${C.yellow}⚠️ 备份功能需要 OpenClaw v2026.3.8+${C.reset}\\n`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'create-full': {\n console.log(`\\n${C.cyan}正在创建完整备份...${C.reset}`);\n try {\n await ocCmd('backup', 'create', '--no-include-workspace');\n console.log(`${C.green}✅ 完整备份已创建${C.reset}\\n`);\n } catch (e) {\n console.log(`${C.yellow}⚠️ 备份失败${C.reset}\\n`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'verify': {\n const files = fs.readdirSync(backupDir).filter(f => f.endsWith('.tar.gz')).sort().reverse();\n if (files.length === 0) {\n console.log(`${C.yellow}未找到备份文件${C.reset}\\n`);\n } else {\n const latest = `${backupDir}/${files[0]}`;\n console.log(`${C.cyan}验证备份: ${latest}${C.reset}`);\n try {\n await ocCmd('backup', 'verify', latest);\n } catch {}\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'list': {\n const files = fs.readdirSync(backupDir).filter(f => f.endsWith('.tar.gz') || f.endsWith('.json')).sort().reverse();\n if (files.length === 0) {\n console.log(`${C.yellow}暂无备份文件${C.reset}\\n`);\n } else {\n console.log(`\\n${C.bold}备份文件列表:${C.reset}`);\n files.slice(0, 10).forEach(f => {\n const stat = fs.statSync(`${backupDir}/${f}`);\n console.log(` ${C.dim}${f} (${(stat.size / 1024).toFixed(1)} KB)${C.reset}`);\n });\n console.log(`${C.dim}\\n备份目录: ${backupDir}${C.reset}\\n`);\n }\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n case 'restore': {\n const files = fs.readdirSync(backupDir).filter(f => f.endsWith('.json')).sort().reverse();\n if (files.length === 0) {\n console.log(`${C.yellow}未找到配置备份文件${C.reset}\\n`);\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n }\n\n const fileChoice = await select({\n title: '选择备份文件',\n showSearch: false,\n items: files.slice(0, 10).map((f, i) => ({\n key: String(i + 1),\n label: f,\n desc: '',\n value: f,\n })),\n });\n\n if (!fileChoice) break;\n\n const ok = await confirm({ prompt: '确认恢复此备份?', defaultYes: false });\n if (ok) {\n try {\n fs.copyFileSync(`${backupDir}/${fileChoice.value}`, CONFIG_FILE);\n console.log(`\\n${C.green}✅ 配置已恢复${C.reset}\\n`);\n await askRestart();\n } catch (e) {\n console.log(`${C.red}恢复失败: ${e.message}${C.reset}\\n`);\n }\n }\n break;\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// 主函数\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function main() {\n const command = process.argv[2];\n\n if (command === 'model') {\n await handleModelConfig();\n return;\n }\n\n if (command === 'status') {\n await runCommand('/etc/init.d/openclaw', ['status_service']);\n return;\n }\n\n if (command === 'restart') {\n const ok = await confirm({ prompt: '确认重启 OpenClaw 服务?' });\n if (ok) await runCommand('/etc/init.d/openclaw', ['restart']);\n return;\n }\n\n // 交互式主菜单\n while (true) {\n const choice = await showMainMenu();\n\n if (!choice || choice.value === 'quit') {\n console.log(`\\n${C.green}再见!${C.reset}\\n`);\n break;\n }\n\n switch (choice.value) {\n case 'model':\n await handleModelConfig();\n break;\n case 'set-active-model':\n await handleSetActiveModel();\n break;\n case 'channels':\n await handleChannels();\n break;\n case 'health':\n await handleHealthCheck();\n break;\n case 'logs':\n resetRenderCount();\n console.log(`\\n${C.cyan}=== OpenClaw 日志 ===${C.reset}\\n`);\n await runCommand('logread', ['-e', 'openclaw']);\n console.log('');\n await input({ prompt: '按回车继续', defaultValue: '' });\n break;\n case 'restart':\n resetRenderCount();\n await restartGateway();\n break;\n case 'advanced':\n await handleAdvancedConfig();\n break;\n case 'reset':\n await handleReset();\n break;\n case 'show-config':\n await handleShowConfig();\n break;\n case 'backup':\n await handleBackup();\n break;\n }\n }\n}\n\nmain().catch(e => {\n console.error(`${C.red}错误:${C.reset}`, e.message);\n process.exit(1);\n});\n"}}}}