{"repo": "adrianandroid/deepseek-gui", "files": {"scripts/release.local.env.example": {"b5f8148ca0963086829edc0127fcc560d775a611": {"t": 1785746764.1917105, "n": 909, "h": "40dad8d692f2ac7d", "c": "# Copy to scripts/release.local.env.\n# This file is intentionally safe to commit; release.local.env is ignored.\n\n### Cloudflare R2 / S3 upload\nS3_BUCKET=deepseek-gui\nS3_ENDPOINT=https://.r2.cloudflarestorage.com\nS3_ACCESS_KEY_ID=\nS3_SECRET_ACCESS_KEY=\n\n# Public download domain used in latest.json and electron-updater metadata.\n# Use an R2 custom domain for production, not the authenticated S3 endpoint.\nR2_PUBLIC_BASE_URL=https://deepseek-gui.com/api/r2\nR2_RELEASE_PREFIX=deepseek-gui\n\n# Default release channel for scripts when --channel is not passed.\n# The project default is frontier; pass --stable or --channel stable for stable releases.\nRELEASE_CHANNEL=frontier\n\n### macOS signing and notarization\nP12_PATH=/path/to/develop-id.p12\nP12_PASSWORD=\nP8_PATH=/path/to/AuthKey_XXXXXXXXXX.p8\nKEY_ID=\nISSUER=\n"}, "2d82de3a7803f57a121eb6460bd0dccc2e098a31": {"t": 1785767583.9607637, "n": 909, "h": "40dad8d692f2ac7d", "c": "# Copy to scripts/release.local.env.\n# This file is intentionally safe to commit; release.local.env is ignored.\n\n### Cloudflare R2 / S3 upload\nS3_BUCKET=deepseek-gui\nS3_ENDPOINT=https://.r2.cloudflarestorage.com\nS3_ACCESS_KEY_ID=\nS3_SECRET_ACCESS_KEY=\n\n# Public download domain used in latest.json and electron-updater metadata.\n# Use an R2 custom domain for production, not the authenticated S3 endpoint.\nR2_PUBLIC_BASE_URL=https://deepseek-gui.com/api/r2\nR2_RELEASE_PREFIX=deepseek-gui\n\n# Default release channel for scripts when --channel is not passed.\n# The project default is frontier; pass --stable or --channel stable for stable releases.\nRELEASE_CHANNEL=frontier\n\n### macOS signing and notarization\nP12_PATH=/path/to/develop-id.p12\nP12_PASSWORD=\nP8_PATH=/path/to/AuthKey_XXXXXXXXXX.p8\nKEY_ID=\nISSUER=\n"}}, "src/main/settings-store.test.ts": {"b5f8148ca0963086829edc0127fcc560d775a611": {"t": 1786068454.170416, "n": 13328, "h": "33721f3ec26c8a4d", "c": "import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { describe, expect, it } from 'vitest'\nimport { DEFAULT_APPROVAL_POLICY } from '../shared/app-settings'\nimport { DEFAULT_GUI_UPDATE_CHANNEL } from '../shared/gui-update'\nimport { JsonSettingsStore } from './settings-store'\n\ndescribe('JsonSettingsStore', () => {\n it('defaults GUI updates to the stable channel for new settings', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.guiUpdate.channel).toBe(DEFAULT_GUI_UPDATE_CHANNEL)\n expect(loaded.agents.kun.approvalPolicy).toBe(DEFAULT_APPROVAL_POLICY)\n expect(loaded.appBehavior).toEqual({\n openAtLogin: false,\n startMinimized: false,\n closeToTray: false\n })\n })\n\n it('creates a default write workspace with welcome.md', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.write.defaultWorkspaceRoot).toContain('.deepseekgui')\n expect(loaded.write.workspaces).toContain(loaded.write.defaultWorkspaceRoot)\n expect(loaded.write.inlineCompletion.enabled).toBe(true)\n expect(loaded.write.inlineCompletion.retrievalEnabled).toBe(true)\n expect(loaded.write.inlineCompletion.longCompletionEnabled).toBe(true)\n expect(loaded.provider.baseUrl).toBe('https://api.deepseek.com')\n expect(loaded.write.inlineCompletion.apiKey).toBe('')\n expect(loaded.write.inlineCompletion.baseUrl).toBe('')\n expect(loaded.write.inlineCompletion.inheritModel).toBe(true)\n expect(loaded.write.inlineCompletion.model).toBe('deepseek-v4-flash')\n expect(loaded.write.inlineCompletion.longMaxTokens).toBe(256)\n expect(await readFile(join(loaded.write.defaultWorkspaceRoot, 'welcome.md'), 'utf8')).toContain('Welcome to Write')\n })\n\n it('preserves the pro write completion model', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n write: {\n inlineCompletion: {\n model: 'deepseek-v4-pro'\n }\n }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.write.inlineCompletion.inheritModel).toBe(false)\n expect(loaded.write.inlineCompletion.model).toBe('deepseek-v4-pro')\n })\n\n it('treats legacy flash defaults as inherited until the user explicitly overrides them', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n write: {\n inlineCompletion: {\n model: 'deepseek-v4-flash'\n }\n }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.write.inlineCompletion.inheritModel).toBe(true)\n expect(loaded.write.inlineCompletion.model).toBe('deepseek-v4-flash')\n })\n\n it('migrates legacy deepseek.autoStart=false into Kun', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n const workspaceRoot = join(userDataDir, 'workspace')\n await mkdir(workspaceRoot, { recursive: true })\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n workspaceRoot,\n deepseek: {\n autoStart: false\n }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.agents.kun.autoStart).toBe(false)\n })\n\n it('migrates existing Kun credentials into General provider settings', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n agents: {\n kun: {\n apiKey: 'sk-existing',\n baseUrl: 'https://runtime.example/v1'\n }\n }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.provider.apiKey).toBe('sk-existing')\n expect(loaded.provider.baseUrl).toBe('https://runtime.example/v1')\n expect(loaded.agents.kun.apiKey).toBe('')\n expect(loaded.agents.kun.baseUrl).toBe('')\n })\n\n it('loads settings from the legacy lowercase userData directory and writes them into the current path', async () => {\n const supportRoot = await mkdtemp(join(tmpdir(), 'ds-gui-settings-compat-'))\n const legacyUserDataDir = join(supportRoot, 'deepseek-gui')\n const currentUserDataDir = join(supportRoot, 'DeepSeek GUI')\n const currentSettingsPath = join(currentUserDataDir, 'deepseek-gui-settings.json')\n\n await mkdir(legacyUserDataDir, { recursive: true })\n await writeFile(\n join(legacyUserDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n provider: {\n apiKey: 'sk-legacy-provider'\n }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(currentUserDataDir)\n const loaded = await store.load()\n\n expect(loaded.provider.apiKey).toBe('sk-legacy-provider')\n expect(await readFile(currentSettingsPath, 'utf8')).toContain('sk-legacy-provider')\n })\n\n it('creates the configured code workspace on load', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n const workspaceRoot = join(userDataDir, 'missing-workspace')\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n workspaceRoot\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.workspaceRoot).toBe(workspaceRoot)\n expect((await stat(workspaceRoot)).isDirectory()).toBe(true)\n })\n\n it('migrates legacy deepseek-runtime agentProvider to Kun', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n agentProvider: 'deepseek-runtime',\n deepseek: { port: 8787 }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n\n expect(loaded.agents.kun.port).toBe(8787)\n })\n\n it('backs up invalid JSON and replaces it with defaults', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n const settingsPath = join(userDataDir, 'deepseek-gui-settings.json')\n await writeFile(settingsPath, '{ invalid json', 'utf8')\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n const files = await readdir(userDataDir)\n const backupName = files.find((file) => file.startsWith('deepseek-gui-settings.invalid-'))\n\n expect(loaded.workspaceRoot.length).toBeGreaterThan(0)\n expect(backupName).toBeTruthy()\n expect(await readFile(join(userDataDir, backupName ?? ''), 'utf8')).toBe('{ invalid json')\n const replaced = await readFile(settingsPath, 'utf8')\n expect(() => JSON.parse(replaced)).not.toThrow()\n })\n\n it('throws for non-recoverable read errors', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n const settingsPath = join(userDataDir, 'deepseek-gui-settings.json')\n await mkdir(settingsPath, { recursive: true })\n\n const store = new JsonSettingsStore(userDataDir)\n\n await expect(store.load()).rejects.toThrow(/Failed to read settings file/)\n })\n\n it('merges Kun settings patches', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n const store = new JsonSettingsStore(userDataDir)\n await store.load()\n\n const saved = await store.patch({\n agents: {\n kun: {\n model: 'deepseek-reasoner',\n approvalPolicy: 'on-request'\n }\n }\n })\n\n expect(saved.agents.kun.model).toBe('deepseek-reasoner')\n expect(saved.agents.kun.approvalPolicy).toBe('on-request')\n })\n\n it('merges desktop behavior patches without keeping invalid startup state', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n const store = new JsonSettingsStore(userDataDir)\n await store.load()\n\n const enabled = await store.patch({\n appBehavior: {\n openAtLogin: true,\n startMinimized: true,\n closeToTray: true\n }\n })\n const disabled = await store.patch({\n appBehavior: {\n openAtLogin: false\n }\n })\n\n expect(enabled.appBehavior).toEqual({\n openAtLogin: true,\n startMinimized: true,\n closeToTray: true\n })\n expect(disabled.appBehavior).toEqual({\n openAtLogin: false,\n startMinimized: false,\n closeToTray: true\n })\n })\n\n it('omits agentProvider when writing normalized settings to disk', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n const settingsPath = join(userDataDir, 'deepseek-gui-settings.json')\n const store = new JsonSettingsStore(userDataDir)\n await store.load()\n await store.patch({\n agents: {\n kun: {\n model: 'deepseek-chat'\n }\n }\n })\n\n const persisted = JSON.parse(await readFile(settingsPath, 'utf8')) as Record\n\n expect('agentProvider' in persisted).toBe(false)\n expect(persisted.agents).toEqual(\n expect.objectContaining({\n kun: expect.objectContaining({ model: 'deepseek-chat' })\n })\n )\n })\n\n it('folds legacy Claw thread ids into the single Kun mapping', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n claw: {\n channels: [\n {\n id: 'channel-1',\n provider: 'feishu',\n label: 'Feishu Agent',\n threadId: 'thr_codewhale',\n agentThreadIds: { reasonix: '2026-06-01T01:00:00.000Z' },\n conversations: [\n {\n id: 'conversation-1',\n chatId: 'chat-1',\n latestMessageId: 'message-1',\n localThreadId: 'thr_conversation_codewhale',\n agentThreadIds: { reasonix: '2026-06-01T02:00:00.000Z' }\n }\n ]\n }\n ]\n }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n const channel = loaded.claw.channels[0]\n const conversation = channel?.conversations[0]\n\n expect(channel?.threadId).toBe('thr_codewhale')\n expect(conversation?.localThreadId).toBe('thr_conversation_codewhale')\n })\n\n it('seeds Reasonix-only Claw conversations into the canonical thread id', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-'))\n\n await writeFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n JSON.stringify({\n version: 1,\n claw: {\n channels: [\n {\n id: 'channel-1',\n provider: 'feishu',\n label: 'Feishu Agent',\n agentThreadIds: { reasonix: 'reasonix-channel' },\n conversations: [\n {\n id: 'conversation-1',\n chatId: 'chat-1',\n latestMessageId: 'message-1',\n localThreadId: '',\n agentThreadIds: { reasonix: 'reasonix-conversation' }\n }\n ]\n }\n ]\n }\n }),\n 'utf8'\n )\n\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n const channel = loaded.claw.channels[0]\n const conversation = channel?.conversations[0]\n\n expect(channel?.threadId).toBe('reasonix-channel')\n expect(conversation?.localThreadId).toBe('reasonix-conversation')\n })\n\n it('saves settings atomically (no .tmp file left on success)', async () => {\n const userDataDir = await mkdtemp(join(tmpdir(), 'ds-gui-settings-atomic-'))\n\n try {\n const store = new JsonSettingsStore(userDataDir)\n const loaded = await store.load()\n await store.save(loaded)\n\n // Final file is present and non-empty.\n const finalContents = await readFile(\n join(userDataDir, 'deepseek-gui-settings.json'),\n 'utf8'\n )\n expect(finalContents.length).toBeGreaterThan(0)\n\n // No .tmp leftover from the atomic write.\n const entries = await readdir(userDataDir)\n expect(entries.filter((entry) => entry.includes('.tmp'))).toEqual([])\n } finally {\n await rm(userDataDir, { recursive: true, force: true })\n }\n })\n})\n"}}}}