Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
15 KiB
JSON

{"repo": "bernis-web/cline-pet", "files": {"src/app/main/config.ts": {"1017b846f2c10e90e708fb160054ef60bfa5f84c": {"t": 1785745602.2917325, "n": 4246, "h": "ee17e89eb09c3097", "c": "import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type DeepSeekConfig = {\n apiKey: string;\n baseUrl: string;\n model: string;\n};\n\nexport type DeepSeekConfigResult =\n | { ok: true; data: DeepSeekConfig }\n | { ok: false; errorCode: \"DEEPSEEK_API_KEY_MISSING\" | \"DEEPSEEK_CONFIG_INVALID\"; message: string };\n\nexport type DeepSeekSettings = {\n configured: boolean;\n baseUrl: string;\n model: string;\n apiKeySource: \"env\" | \"file\" | \"missing\";\n configPath: string;\n};\n\nexport type DeepSeekSettingsInput = {\n apiKey?: string;\n baseUrl?: string;\n model?: string;\n};\n\nexport type DeepSeekSettingsResult =\n | { ok: true; data: DeepSeekSettings }\n | { ok: false; errorCode: \"DEEPSEEK_API_KEY_MISSING\" | \"DEEPSEEK_CONFIG_INVALID\" | \"DEEPSEEK_CONFIG_WRITE_FAILED\"; message: string };\n\ntype ConfigFile = {\n deepseekApiKey?: string;\n deepseekBaseUrl?: string;\n deepseekModel?: string;\n [key: string]: unknown;\n};\n\nfunction configPath(configRoot: string) {\n return join(configRoot, \"config.json\");\n}\n\nfunction normalizeBaseUrl(baseUrl?: string) {\n return (baseUrl?.trim() || \"https://api.deepseek.com\").replace(/\\/+$/, \"\");\n}\n\nfunction normalizeModel(model?: string) {\n return model?.trim() || \"deepseek-chat\";\n}\n\nfunction apiKeySource(file: ConfigFile): DeepSeekSettings[\"apiKeySource\"] {\n if (file.deepseekApiKey?.trim()) return \"file\";\n if (process.env.CLINE_PET_DEEPSEEK_API_KEY?.trim()) return \"env\";\n return \"missing\";\n}\n\nfunction readConfigFile(configRoot: string): ConfigFile {\n const filePath = configPath(configRoot);\n if (!existsSync(filePath)) return {};\n try {\n return JSON.parse(readFileSync(filePath, \"utf8\").replace(/^\\uFEFF/, \"\")) as ConfigFile;\n } catch {\n return {};\n }\n}\n\nexport function loadDeepSeekConfig(configRoot: string): DeepSeekConfigResult {\n const file = readConfigFile(configRoot);\n const apiKey = file.deepseekApiKey || process.env.CLINE_PET_DEEPSEEK_API_KEY;\n const baseUrl = normalizeBaseUrl(file.deepseekBaseUrl || process.env.CLINE_PET_DEEPSEEK_BASE_URL);\n const model = normalizeModel(file.deepseekModel || process.env.CLINE_PET_DEEPSEEK_MODEL);\n\n if (!apiKey?.trim()) {\n return {\n ok: false,\n errorCode: \"DEEPSEEK_API_KEY_MISSING\",\n message: \"DeepSeek API key is missing. Set CLINE_PET_DEEPSEEK_API_KEY or %APPDATA%/cline-desktop-pet/config.json.\"\n };\n }\n\n return { ok: true, data: { apiKey: apiKey.trim(), baseUrl, model } };\n}\n\nexport function getDeepSeekSettings(configRoot: string): DeepSeekSettingsResult {\n const file = readConfigFile(configRoot);\n const source = apiKeySource(file);\n return {\n ok: true,\n data: {\n configured: source !== \"missing\",\n baseUrl: normalizeBaseUrl(file.deepseekBaseUrl || process.env.CLINE_PET_DEEPSEEK_BASE_URL),\n model: normalizeModel(file.deepseekModel || process.env.CLINE_PET_DEEPSEEK_MODEL),\n apiKeySource: source,\n configPath: configPath(configRoot)\n }\n };\n}\n\nexport function saveDeepSeekSettings(configRoot: string, input: DeepSeekSettingsInput): DeepSeekSettingsResult {\n const file = readConfigFile(configRoot);\n const nextApiKey = input.apiKey?.trim() || file.deepseekApiKey?.trim() || process.env.CLINE_PET_DEEPSEEK_API_KEY?.trim();\n if (!nextApiKey) {\n return { ok: false, errorCode: \"DEEPSEEK_API_KEY_MISSING\", message: \"请输入 DeepSeek API key。\" };\n }\n\n const nextBaseUrl = normalizeBaseUrl(input.baseUrl || file.deepseekBaseUrl);\n try {\n const parsed = new URL(nextBaseUrl);\n if (!/^https?:$/.test(parsed.protocol)) throw new Error(\"unsupported protocol\");\n } catch {\n return { ok: false, errorCode: \"DEEPSEEK_CONFIG_INVALID\", message: \"DeepSeek Base URL 需要是有效的 http(s) 地址。\" };\n }\n\n const nextFile: ConfigFile = {\n ...file,\n deepseekApiKey: nextApiKey,\n deepseekBaseUrl: nextBaseUrl,\n deepseekModel: normalizeModel(input.model || file.deepseekModel)\n };\n\n try {\n mkdirSync(configRoot, { recursive: true });\n writeFileSync(configPath(configRoot), `${JSON.stringify(nextFile, null, 2)}\\n`, \"utf8\");\n } catch {\n return { ok: false, errorCode: \"DEEPSEEK_CONFIG_WRITE_FAILED\", message: \"DeepSeek 配置保存失败,请检查配置目录权限。\" };\n }\n\n return getDeepSeekSettings(configRoot);\n}"}, "d1214ba3fdc832653f1a3a2b4db7ed4649f24941": {"t": 1785754676.825651, "n": 4246, "h": "ee17e89eb09c3097", "c": "import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type DeepSeekConfig = {\n apiKey: string;\n baseUrl: string;\n model: string;\n};\n\nexport type DeepSeekConfigResult =\n | { ok: true; data: DeepSeekConfig }\n | { ok: false; errorCode: \"DEEPSEEK_API_KEY_MISSING\" | \"DEEPSEEK_CONFIG_INVALID\"; message: string };\n\nexport type DeepSeekSettings = {\n configured: boolean;\n baseUrl: string;\n model: string;\n apiKeySource: \"env\" | \"file\" | \"missing\";\n configPath: string;\n};\n\nexport type DeepSeekSettingsInput = {\n apiKey?: string;\n baseUrl?: string;\n model?: string;\n};\n\nexport type DeepSeekSettingsResult =\n | { ok: true; data: DeepSeekSettings }\n | { ok: false; errorCode: \"DEEPSEEK_API_KEY_MISSING\" | \"DEEPSEEK_CONFIG_INVALID\" | \"DEEPSEEK_CONFIG_WRITE_FAILED\"; message: string };\n\ntype ConfigFile = {\n deepseekApiKey?: string;\n deepseekBaseUrl?: string;\n deepseekModel?: string;\n [key: string]: unknown;\n};\n\nfunction configPath(configRoot: string) {\n return join(configRoot, \"config.json\");\n}\n\nfunction normalizeBaseUrl(baseUrl?: string) {\n return (baseUrl?.trim() || \"https://api.deepseek.com\").replace(/\\/+$/, \"\");\n}\n\nfunction normalizeModel(model?: string) {\n return model?.trim() || \"deepseek-chat\";\n}\n\nfunction apiKeySource(file: ConfigFile): DeepSeekSettings[\"apiKeySource\"] {\n if (file.deepseekApiKey?.trim()) return \"file\";\n if (process.env.CLINE_PET_DEEPSEEK_API_KEY?.trim()) return \"env\";\n return \"missing\";\n}\n\nfunction readConfigFile(configRoot: string): ConfigFile {\n const filePath = configPath(configRoot);\n if (!existsSync(filePath)) return {};\n try {\n return JSON.parse(readFileSync(filePath, \"utf8\").replace(/^\\uFEFF/, \"\")) as ConfigFile;\n } catch {\n return {};\n }\n}\n\nexport function loadDeepSeekConfig(configRoot: string): DeepSeekConfigResult {\n const file = readConfigFile(configRoot);\n const apiKey = file.deepseekApiKey || process.env.CLINE_PET_DEEPSEEK_API_KEY;\n const baseUrl = normalizeBaseUrl(file.deepseekBaseUrl || process.env.CLINE_PET_DEEPSEEK_BASE_URL);\n const model = normalizeModel(file.deepseekModel || process.env.CLINE_PET_DEEPSEEK_MODEL);\n\n if (!apiKey?.trim()) {\n return {\n ok: false,\n errorCode: \"DEEPSEEK_API_KEY_MISSING\",\n message: \"DeepSeek API key is missing. Set CLINE_PET_DEEPSEEK_API_KEY or %APPDATA%/cline-desktop-pet/config.json.\"\n };\n }\n\n return { ok: true, data: { apiKey: apiKey.trim(), baseUrl, model } };\n}\n\nexport function getDeepSeekSettings(configRoot: string): DeepSeekSettingsResult {\n const file = readConfigFile(configRoot);\n const source = apiKeySource(file);\n return {\n ok: true,\n data: {\n configured: source !== \"missing\",\n baseUrl: normalizeBaseUrl(file.deepseekBaseUrl || process.env.CLINE_PET_DEEPSEEK_BASE_URL),\n model: normalizeModel(file.deepseekModel || process.env.CLINE_PET_DEEPSEEK_MODEL),\n apiKeySource: source,\n configPath: configPath(configRoot)\n }\n };\n}\n\nexport function saveDeepSeekSettings(configRoot: string, input: DeepSeekSettingsInput): DeepSeekSettingsResult {\n const file = readConfigFile(configRoot);\n const nextApiKey = input.apiKey?.trim() || file.deepseekApiKey?.trim() || process.env.CLINE_PET_DEEPSEEK_API_KEY?.trim();\n if (!nextApiKey) {\n return { ok: false, errorCode: \"DEEPSEEK_API_KEY_MISSING\", message: \"请输入 DeepSeek API key。\" };\n }\n\n const nextBaseUrl = normalizeBaseUrl(input.baseUrl || file.deepseekBaseUrl);\n try {\n const parsed = new URL(nextBaseUrl);\n if (!/^https?:$/.test(parsed.protocol)) throw new Error(\"unsupported protocol\");\n } catch {\n return { ok: false, errorCode: \"DEEPSEEK_CONFIG_INVALID\", message: \"DeepSeek Base URL 需要是有效的 http(s) 地址。\" };\n }\n\n const nextFile: ConfigFile = {\n ...file,\n deepseekApiKey: nextApiKey,\n deepseekBaseUrl: nextBaseUrl,\n deepseekModel: normalizeModel(input.model || file.deepseekModel)\n };\n\n try {\n mkdirSync(configRoot, { recursive: true });\n writeFileSync(configPath(configRoot), `${JSON.stringify(nextFile, null, 2)}\\n`, \"utf8\");\n } catch {\n return { ok: false, errorCode: \"DEEPSEEK_CONFIG_WRITE_FAILED\", message: \"DeepSeek 配置保存失败,请检查配置目录权限。\" };\n }\n\n return getDeepSeekSettings(configRoot);\n}"}, "eba7e9d9708ec351ae8a2e8c8dfe72ea1495d7f8": {"t": 1785754678.3517754, "n": 1494, "h": "2b82cb9f7e284f7f", "c": "import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type DeepSeekConfig = {\n apiKey: string;\n baseUrl: string;\n model: string;\n};\n\nexport type DeepSeekConfigResult =\n | { ok: true; data: DeepSeekConfig }\n | { ok: false; errorCode: \"DEEPSEEK_API_KEY_MISSING\" | \"DEEPSEEK_CONFIG_INVALID\"; message: string };\n\ntype ConfigFile = {\n deepseekApiKey?: string;\n deepseekBaseUrl?: string;\n deepseekModel?: string;\n};\n\nfunction readConfigFile(configRoot: string): ConfigFile {\n const configPath = join(configRoot, \"config.json\");\n if (!existsSync(configPath)) return {};\n try {\n return JSON.parse(readFileSync(configPath, \"utf8\").replace(/^\\uFEFF/, \"\")) as ConfigFile;\n } catch {\n return {};\n }\n}\n\nexport function loadDeepSeekConfig(configRoot: string): DeepSeekConfigResult {\n const file = readConfigFile(configRoot);\n const apiKey = process.env.CLINE_PET_DEEPSEEK_API_KEY || file.deepseekApiKey;\n const baseUrl = process.env.CLINE_PET_DEEPSEEK_BASE_URL || file.deepseekBaseUrl || \"https://api.deepseek.com\";\n const model = process.env.CLINE_PET_DEEPSEEK_MODEL || file.deepseekModel || \"deepseek-chat\";\n\n if (!apiKey?.trim()) {\n return {\n ok: false,\n errorCode: \"DEEPSEEK_API_KEY_MISSING\",\n message: \"DeepSeek API key is missing. Set CLINE_PET_DEEPSEEK_API_KEY or %APPDATA%/cline-desktop-pet/config.json.\"\n };\n }\n\n return { ok: true, data: { apiKey: apiKey.trim(), baseUrl: baseUrl.replace(/\\/$/, \"\"), model } };\n}"}}, "tests/app/main/config.test.ts": {"1017b846f2c10e90e708fb160054ef60bfa5f84c": {"t": 1786068279.1895945, "n": 4110, "h": "d0f78e0d23f6cd37", "c": "import { mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { getDeepSeekSettings, loadDeepSeekConfig, saveDeepSeekSettings } from \"../../../src/app/main/config\";\n\nconst originalEnv = { ...process.env };\n\ndescribe(\"DeepSeek config loader\", () => {\n afterEach(() => {\n process.env = { ...originalEnv };\n });\n\n it(\"uses local app settings before environment variables\", () => {\n const root = mkdtempSync(join(tmpdir(), \"cline-pet-config-\"));\n writeFileSync(join(root, \"config.json\"), JSON.stringify({\n deepseekApiKey: \"file-key\",\n deepseekBaseUrl: \"https://api.deepseek.com\",\n deepseekModel: \"deepseek-v4-pro\"\n }));\n process.env.CLINE_PET_DEEPSEEK_API_KEY = \"env-key\";\n process.env.CLINE_PET_DEEPSEEK_MODEL = \"deepseek-chat\";\n\n const config = loadDeepSeekConfig(root);\n\n expect(config).toEqual({\n ok: true,\n data: {\n apiKey: \"file-key\",\n baseUrl: \"https://api.deepseek.com\",\n model: \"deepseek-v4-pro\"\n }\n });\n rmSync(root, { recursive: true, force: true });\n });\n\n it(\"uses environment variables when local app settings are missing\", () => {\n const root = mkdtempSync(join(tmpdir(), \"cline-pet-config-\"));\n process.env.CLINE_PET_DEEPSEEK_API_KEY = \"env-key\";\n process.env.CLINE_PET_DEEPSEEK_MODEL = \"deepseek-chat\";\n\n const config = loadDeepSeekConfig(root);\n\n expect(config).toEqual({\n ok: true,\n data: {\n apiKey: \"env-key\",\n baseUrl: \"https://api.deepseek.com\",\n model: \"deepseek-chat\"\n }\n });\n rmSync(root, { recursive: true, force: true });\n });\n\n it(\"returns a typed error when API key is missing\", () => {\n const root = mkdtempSync(join(tmpdir(), \"cline-pet-config-\"));\n delete process.env.CLINE_PET_DEEPSEEK_API_KEY;\n\n const config = loadDeepSeekConfig(root);\n\n expect(config).toEqual({ ok: false, errorCode: \"DEEPSEEK_API_KEY_MISSING\", message: expect.stringContaining(\"CLINE_PET_DEEPSEEK_API_KEY\") });\n rmSync(root, { recursive: true, force: true });\n });\n\n it(\"saves DeepSeek settings to the local config file\", () => {\n const root = mkdtempSync(join(tmpdir(), \"cline-pet-config-\"));\n delete process.env.CLINE_PET_DEEPSEEK_API_KEY;\n\n const result = saveDeepSeekSettings(root, {\n apiKey: \" sk-local \",\n baseUrl: \"https://api.deepseek.com/\",\n model: \" deepseek-chat \"\n });\n\n expect(result).toEqual({\n ok: true,\n data: {\n configured: true,\n baseUrl: \"https://api.deepseek.com\",\n model: \"deepseek-chat\",\n apiKeySource: \"file\",\n configPath: join(root, \"config.json\")\n }\n });\n expect(JSON.parse(readFileSync(join(root, \"config.json\"), \"utf8\"))).toEqual({\n deepseekApiKey: \"sk-local\",\n deepseekBaseUrl: \"https://api.deepseek.com\",\n deepseekModel: \"deepseek-chat\"\n });\n expect(loadDeepSeekConfig(root)).toEqual({\n ok: true,\n data: { apiKey: \"sk-local\", baseUrl: \"https://api.deepseek.com\", model: \"deepseek-chat\" }\n });\n rmSync(root, { recursive: true, force: true });\n });\n\n it(\"returns safe settings without exposing the saved API key\", () => {\n const root = mkdtempSync(join(tmpdir(), \"cline-pet-config-\"));\n process.env.CLINE_PET_DEEPSEEK_API_KEY = \"env-secret-key\";\n process.env.CLINE_PET_DEEPSEEK_MODEL = \"deepseek-chat\";\n writeFileSync(join(root, \"config.json\"), JSON.stringify({ deepseekApiKey: \"secret-key\", deepseekModel: \"deepseek-v4-pro\" }));\n\n const settings = getDeepSeekSettings(root);\n\n expect(settings).toEqual({\n ok: true,\n data: {\n configured: true,\n baseUrl: \"https://api.deepseek.com\",\n model: \"deepseek-v4-pro\",\n apiKeySource: \"file\",\n configPath: join(root, \"config.json\")\n }\n });\n expect(JSON.stringify(settings)).not.toContain(\"secret-key\");\n expect(JSON.stringify(settings)).not.toContain(\"env-secret-key\");\n rmSync(root, { recursive: true, force: true });\n });\n});"}}}}