codeblog-app 2.8.2 → 2.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,222 @@
1
+ import fs from "fs/promises"
2
+ import os from "os"
3
+ import path from "path"
4
+ import { describe, test, expect, beforeAll, beforeEach, afterAll } from "bun:test"
5
+
6
+ describe("Config unified read/write", () => {
7
+ const testHome = path.join(os.tmpdir(), `codeblog-config-test-${process.pid}-${Date.now()}`)
8
+ const configDir = path.join(testHome, ".codeblog")
9
+ const configFile = path.join(configDir, "config.json")
10
+
11
+ let Config: (typeof import("../../config"))["Config"]
12
+ let Auth: (typeof import("../../auth"))["Auth"]
13
+
14
+ beforeAll(async () => {
15
+ process.env.CODEBLOG_TEST_HOME = testHome
16
+ await fs.mkdir(configDir, { recursive: true })
17
+ await fs.writeFile(configFile, "{}\n")
18
+ ;({ Config } = await import("../../config"))
19
+ ;({ Auth } = await import("../../auth"))
20
+ })
21
+
22
+ beforeEach(async () => {
23
+ await fs.writeFile(configFile, "{}\n")
24
+ })
25
+
26
+ afterAll(async () => {
27
+ await fs.rm(testHome, { recursive: true, force: true })
28
+ delete process.env.CODEBLOG_TEST_HOME
29
+ })
30
+
31
+ // --- Config.save / Config.load ---
32
+
33
+ test("save and load serverUrl at top level", async () => {
34
+ await Config.save({ serverUrl: "https://test.codeblog.ai" })
35
+ const cfg = await Config.load()
36
+ expect(cfg.serverUrl).toBe("https://test.codeblog.ai")
37
+ })
38
+
39
+ test("save and load dailyReportHour at top level", async () => {
40
+ await Config.save({ dailyReportHour: 18 })
41
+ const cfg = await Config.load()
42
+ expect(cfg.dailyReportHour).toBe(18)
43
+ })
44
+
45
+ test("save auth nested fields", async () => {
46
+ await Config.save({ auth: { apiKey: "cbk_test123", activeAgent: "my-agent", userId: "user123" } })
47
+ const cfg = await Config.load()
48
+ expect(cfg.auth?.apiKey).toBe("cbk_test123")
49
+ expect(cfg.auth?.activeAgent).toBe("my-agent")
50
+ expect(cfg.auth?.userId).toBe("user123")
51
+ })
52
+
53
+ test("save cli nested fields", async () => {
54
+ await Config.save({
55
+ cli: {
56
+ model: "anthropic/claude-sonnet-4",
57
+ defaultProvider: "anthropic",
58
+ providers: {
59
+ anthropic: { apiKey: "sk-ant-test", apiType: "anthropic", compatProfile: "anthropic" },
60
+ },
61
+ },
62
+ })
63
+ const cfg = await Config.load()
64
+ expect(cfg.cli?.model).toBe("anthropic/claude-sonnet-4")
65
+ expect(cfg.cli?.defaultProvider).toBe("anthropic")
66
+ expect(cfg.cli?.providers?.anthropic?.apiKey).toBe("sk-ant-test")
67
+ })
68
+
69
+ // --- Deep merge ---
70
+
71
+ test("deep merge preserves other sections when saving one", async () => {
72
+ await Config.save({ auth: { apiKey: "cbk_first" } })
73
+ await Config.save({ cli: { model: "gpt-5.2" } })
74
+ const cfg = await Config.load()
75
+ expect(cfg.auth?.apiKey).toBe("cbk_first")
76
+ expect(cfg.cli?.model).toBe("gpt-5.2")
77
+ })
78
+
79
+ test("deep merge preserves nested fields within same section", async () => {
80
+ await Config.save({ auth: { apiKey: "cbk_key", userId: "u1" } })
81
+ await Config.save({ auth: { activeAgent: "agent1" } })
82
+ const cfg = await Config.load()
83
+ expect(cfg.auth?.apiKey).toBe("cbk_key")
84
+ expect(cfg.auth?.userId).toBe("u1")
85
+ expect(cfg.auth?.activeAgent).toBe("agent1")
86
+ })
87
+
88
+ test("undefined deletes fields (for Auth.remove)", async () => {
89
+ await Config.save({ auth: { apiKey: "cbk_key", userId: "u1", activeAgent: "a1" } })
90
+ await Config.save({ auth: { apiKey: undefined, userId: undefined, activeAgent: undefined } })
91
+ const cfg = await Config.load()
92
+ expect(cfg.auth?.apiKey).toBeUndefined()
93
+ expect(cfg.auth?.userId).toBeUndefined()
94
+ expect(cfg.auth?.activeAgent).toBeUndefined()
95
+ })
96
+
97
+ test("undefined in auth does not affect cli", async () => {
98
+ await Config.save({ auth: { apiKey: "cbk_key" }, cli: { model: "gpt-5.2" } })
99
+ await Config.save({ auth: { apiKey: undefined } })
100
+ const cfg = await Config.load()
101
+ expect(cfg.auth?.apiKey).toBeUndefined()
102
+ expect(cfg.cli?.model).toBe("gpt-5.2")
103
+ })
104
+
105
+ // --- Auth proxy ---
106
+
107
+ test("Auth.set writes to config.auth", async () => {
108
+ await Auth.set({ type: "apikey", value: "cbk_authtest", username: "testuser" })
109
+ const cfg = await Config.load()
110
+ expect(cfg.auth?.apiKey).toBe("cbk_authtest")
111
+ expect(cfg.auth?.username).toBe("testuser")
112
+ })
113
+
114
+ test("Auth.get reads from config.auth", async () => {
115
+ await Config.save({ auth: { apiKey: "cbk_gettest", username: "user2" } })
116
+ const token = await Auth.get()
117
+ expect(token).not.toBeNull()
118
+ expect(token!.type).toBe("apikey")
119
+ expect(token!.value).toBe("cbk_gettest")
120
+ expect(token!.username).toBe("user2")
121
+ })
122
+
123
+ test("Auth.get returns null when no apiKey", async () => {
124
+ const token = await Auth.get()
125
+ expect(token).toBeNull()
126
+ })
127
+
128
+ test("Auth.remove clears auth but preserves cli", async () => {
129
+ await Config.save({ auth: { apiKey: "cbk_rm", userId: "u1" }, cli: { model: "gpt-5.2" } })
130
+ await Auth.remove()
131
+ const cfg = await Config.load()
132
+ expect(cfg.auth?.apiKey).toBeUndefined()
133
+ expect(cfg.auth?.userId).toBeUndefined()
134
+ expect(cfg.cli?.model).toBe("gpt-5.2")
135
+ })
136
+
137
+ test("Auth.authenticated returns true/false correctly", async () => {
138
+ expect(await Auth.authenticated()).toBe(false)
139
+ await Auth.set({ type: "apikey", value: "cbk_auth" })
140
+ expect(await Auth.authenticated()).toBe(true)
141
+ await Auth.remove()
142
+ expect(await Auth.authenticated()).toBe(false)
143
+ })
144
+
145
+ test("Auth.header returns correct Authorization header", async () => {
146
+ await Auth.set({ type: "apikey", value: "cbk_hdr" })
147
+ const header = await Auth.header()
148
+ expect(header).toEqual({ Authorization: "Bearer cbk_hdr" })
149
+ })
150
+
151
+ test("Auth.header returns empty object when not authenticated", async () => {
152
+ const header = await Auth.header()
153
+ expect(header).toEqual({})
154
+ })
155
+
156
+ // --- Config helper functions ---
157
+
158
+ test("Config.url reads serverUrl", async () => {
159
+ await Config.save({ serverUrl: "https://custom.codeblog.ai" })
160
+ const url = await Config.url()
161
+ expect(url).toBe("https://custom.codeblog.ai")
162
+ })
163
+
164
+ test("Config.url returns default when not set", async () => {
165
+ const url = await Config.url()
166
+ expect(url).toBe("https://codeblog.ai")
167
+ })
168
+
169
+ test("Config.key reads auth.apiKey", async () => {
170
+ await Config.save({ auth: { apiKey: "cbk_keytest" } })
171
+ const key = await Config.key()
172
+ expect(key).toBe("cbk_keytest")
173
+ })
174
+
175
+ test("Config.getActiveAgent reads auth.activeAgent", async () => {
176
+ await Config.save({ auth: { activeAgent: "my-bot" } })
177
+ const agent = await Config.getActiveAgent()
178
+ expect(agent).toBe("my-bot")
179
+ })
180
+
181
+ test("Config.saveActiveAgent writes auth.activeAgent", async () => {
182
+ await Config.saveActiveAgent("new-bot")
183
+ const cfg = await Config.load()
184
+ expect(cfg.auth?.activeAgent).toBe("new-bot")
185
+ })
186
+
187
+ test("Config.clearActiveAgent removes auth.activeAgent", async () => {
188
+ await Config.save({ auth: { activeAgent: "old-bot", apiKey: "cbk_keep" } })
189
+ await Config.clearActiveAgent()
190
+ const cfg = await Config.load()
191
+ expect(cfg.auth?.activeAgent).toBeUndefined()
192
+ expect(cfg.auth?.apiKey).toBe("cbk_keep")
193
+ })
194
+
195
+ test("Config.dailyReportHour returns saved value or default 22", async () => {
196
+ expect(await Config.dailyReportHour()).toBe(22)
197
+ await Config.save({ dailyReportHour: 8 })
198
+ expect(await Config.dailyReportHour()).toBe(8)
199
+ })
200
+
201
+ // --- JSON file format ---
202
+
203
+ test("config file is valid JSON with correct structure", async () => {
204
+ await Config.save({
205
+ serverUrl: "https://codeblog.ai",
206
+ dailyReportHour: 22,
207
+ auth: { apiKey: "cbk_json", activeAgent: "bot", userId: "u1" },
208
+ cli: { model: "gpt-5.2", providers: { openai: { apiKey: "sk-test" } } },
209
+ })
210
+ const raw = await fs.readFile(configFile, "utf-8")
211
+ const parsed = JSON.parse(raw)
212
+ expect(parsed.serverUrl).toBe("https://codeblog.ai")
213
+ expect(parsed.dailyReportHour).toBe(22)
214
+ expect(parsed.auth.apiKey).toBe("cbk_json")
215
+ expect(parsed.cli.model).toBe("gpt-5.2")
216
+ expect(parsed.cli.providers.openai.apiKey).toBe("sk-test")
217
+ // No old flat fields
218
+ expect(parsed.api_url).toBeUndefined()
219
+ expect(parsed.apiKey).toBeUndefined()
220
+ expect(parsed.providers).toBeUndefined()
221
+ })
222
+ })
@@ -5,105 +5,118 @@ import { Global } from "../global"
5
5
  const CONFIG_FILE = path.join(Global.Path.config, "config.json")
6
6
 
7
7
  export namespace Config {
8
- export type ModelApi = "anthropic" | "openai" | "google" | "openai-compatible"
8
+ export type ApiType = "anthropic" | "openai" | "google" | "openai-compatible"
9
9
  export type CompatProfile = "anthropic" | "openai" | "openai-compatible" | "google"
10
10
 
11
- export interface FeatureFlags {
12
- ai_provider_registry_v2?: boolean
13
- ai_onboarding_wizard_v2?: boolean
11
+ export interface AuthConfig {
12
+ apiKey?: string
13
+ activeAgent?: string
14
+ userId?: string
15
+ username?: string
14
16
  }
15
17
 
16
18
  export interface ProviderConfig {
17
- api_key: string
18
- base_url?: string
19
- api?: ModelApi
20
- compat_profile?: CompatProfile
19
+ apiKey: string
20
+ baseUrl?: string
21
+ apiType?: ApiType
22
+ compatProfile?: CompatProfile
21
23
  }
22
24
 
23
- export interface CodeblogConfig {
24
- api_url: string
25
- api_key?: string
26
- token?: string
25
+ export interface FeatureFlags {
26
+ aiProviderRegistryV2?: boolean
27
+ aiOnboardingWizardV2?: boolean
28
+ }
29
+
30
+ export interface CompanionConfig {
31
+ enabled?: boolean
32
+ intervalMinutes?: number // default 120
33
+ minSessionMessages?: number // default 10, skip sessions shorter than this
34
+ }
35
+
36
+ export interface CliConfig {
27
37
  model?: string
28
- default_provider?: string
29
- default_language?: string
30
- activeAgent?: string
31
- active_agents?: Record<string, string>
38
+ defaultProvider?: string
39
+ defaultLanguage?: string
40
+ dailyReportHour?: number
32
41
  providers?: Record<string, ProviderConfig>
33
- feature_flags?: FeatureFlags
42
+ featureFlags?: FeatureFlags
43
+ }
44
+
45
+ export interface CodeblogConfig {
46
+ serverUrl?: string
34
47
  dailyReportHour?: number
48
+ auth?: AuthConfig
49
+ cli?: CliConfig
50
+ companion?: CompanionConfig
35
51
  }
36
52
 
37
53
  const defaults: CodeblogConfig = {
38
- api_url: "https://codeblog.ai",
54
+ serverUrl: "https://codeblog.ai",
39
55
  }
40
56
 
41
57
  export const filepath = CONFIG_FILE
42
58
 
43
59
  const FEATURE_FLAG_ENV: Record<keyof FeatureFlags, string> = {
44
- ai_provider_registry_v2: "CODEBLOG_AI_PROVIDER_REGISTRY_V2",
45
- ai_onboarding_wizard_v2: "CODEBLOG_AI_ONBOARDING_WIZARD_V2",
60
+ aiProviderRegistryV2: "CODEBLOG_AI_PROVIDER_REGISTRY_V2",
61
+ aiOnboardingWizardV2: "CODEBLOG_AI_ONBOARDING_WIZARD_V2",
62
+ }
63
+
64
+ function deepMerge(target: Record<string, any>, source: Record<string, any>): Record<string, any> {
65
+ const result = { ...target }
66
+ for (const key of Object.keys(source)) {
67
+ const val = source[key]
68
+ if (val === undefined) {
69
+ delete result[key]
70
+ } else if (typeof val === "object" && !Array.isArray(val) && val !== null) {
71
+ result[key] = deepMerge((result[key] as Record<string, any>) || {}, val)
72
+ } else {
73
+ result[key] = val
74
+ }
75
+ }
76
+ return result
46
77
  }
47
78
 
48
79
  export async function load(): Promise<CodeblogConfig> {
49
80
  const file = Bun.file(CONFIG_FILE)
50
81
  const data = await file.json().catch(() => ({}))
51
- return { ...defaults, ...data }
82
+ return deepMerge(defaults, data) as CodeblogConfig
52
83
  }
53
84
 
54
85
  export async function save(config: Partial<CodeblogConfig>) {
55
86
  const current = await load()
56
- const merged = { ...current, ...config }
87
+ const merged = deepMerge(current, config as Record<string, any>)
57
88
  await writeFile(CONFIG_FILE, JSON.stringify(merged, null, 2))
58
89
  await chmod(CONFIG_FILE, 0o600).catch(() => {})
59
90
  }
60
91
 
61
- export async function getActiveAgent(username?: string) {
92
+ // --- Auth helpers ---
93
+
94
+ export async function getActiveAgent(_username?: string) {
62
95
  const cfg = await load()
63
- if (username) return cfg.active_agents?.[username] || ""
64
- return cfg.activeAgent || ""
96
+ return cfg.auth?.activeAgent || ""
65
97
  }
66
98
 
67
- export async function saveActiveAgent(agent: string, username?: string) {
99
+ export async function saveActiveAgent(agent: string, _username?: string) {
68
100
  if (!agent.trim()) return
69
- if (!username) {
70
- await save({ activeAgent: agent })
71
- return
72
- }
73
- const cfg = await load()
74
- await save({
75
- active_agents: {
76
- ...(cfg.active_agents || {}),
77
- [username]: agent,
78
- },
79
- })
80
- }
81
-
82
- export async function clearActiveAgent(username?: string) {
83
- if (!username) {
84
- await save({ activeAgent: "", active_agents: {} })
85
- return
86
- }
87
- const cfg = await load()
88
- const map = { ...(cfg.active_agents || {}) }
89
- delete map[username]
90
- await save({ active_agents: map })
101
+ await save({ auth: { activeAgent: agent } })
91
102
  }
92
103
 
93
- export async function url() {
94
- return process.env.CODEBLOG_URL || (await load()).api_url || "https://codeblog.ai"
104
+ export async function clearActiveAgent(_username?: string) {
105
+ await save({ auth: { activeAgent: undefined } })
95
106
  }
96
107
 
97
- export async function key() {
98
- return process.env.CODEBLOG_API_KEY || (await load()).api_key || ""
108
+ // --- Server helpers ---
109
+
110
+ export async function url() {
111
+ return process.env.CODEBLOG_URL || (await load()).serverUrl || "https://codeblog.ai"
99
112
  }
100
113
 
101
- export async function token() {
102
- return process.env.CODEBLOG_TOKEN || (await load()).token || ""
114
+ export async function key() {
115
+ return process.env.CODEBLOG_API_KEY || (await load()).auth?.apiKey || ""
103
116
  }
104
117
 
105
118
  export async function language() {
106
- return process.env.CODEBLOG_LANGUAGE || (await load()).default_language
119
+ return process.env.CODEBLOG_LANGUAGE || (await load()).cli?.defaultLanguage
107
120
  }
108
121
 
109
122
  export async function dailyReportHour(): Promise<number> {
@@ -111,6 +124,8 @@ export namespace Config {
111
124
  return val !== undefined ? val : 22
112
125
  }
113
126
 
127
+ // --- Feature flags ---
128
+
114
129
  function parseBool(raw: string | undefined): boolean | undefined {
115
130
  if (!raw) return undefined
116
131
  const v = raw.trim().toLowerCase()
@@ -126,6 +141,6 @@ export namespace Config {
126
141
  export async function featureEnabled(flag: keyof FeatureFlags): Promise<boolean> {
127
142
  const env = parseBool(process.env[FEATURE_FLAG_ENV[flag]])
128
143
  if (env !== undefined) return env
129
- return !!(await load()).feature_flags?.[flag]
144
+ return !!(await load()).cli?.featureFlags?.[flag]
130
145
  }
131
146
  }
@@ -12,7 +12,7 @@ const localappdata = process.env.LOCALAPPDATA || path.join(home, "AppData", "Loc
12
12
 
13
13
  const data = win ? path.join(localappdata, app) : path.join(xdgData || path.join(home, ".local", "share"), app)
14
14
  const cache = win ? path.join(localappdata, app, "cache") : path.join(xdgCache || path.join(home, ".cache"), app)
15
- const config = win ? path.join(appdata, app) : path.join(xdgConfig || path.join(home, ".config"), app)
15
+ const config = path.join(home, `.${app}`)
16
16
  const state = win ? path.join(localappdata, app, "state") : path.join(xdgState || path.join(home, ".local", "state"), app)
17
17
 
18
18
  export namespace Global {
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ import { ForumCommand } from "./cli/cmd/forum"
32
32
  import { UninstallCommand } from "./cli/cmd/uninstall"
33
33
  import { McpCommand } from "./cli/cmd/mcp"
34
34
  import { DailyCommand } from "./cli/cmd/daily"
35
+ import { CompanionCommand } from "./cli/cmd/companion"
35
36
 
36
37
  const VERSION = (await import("../package.json")).version
37
38
 
@@ -109,7 +110,8 @@ const cli = yargs(hideBin(process.argv))
109
110
  " Scan & Publish:\n" +
110
111
  " scan Scan local IDE sessions\n" +
111
112
  " publish Auto-generate and publish a post\n" +
112
- " daily Generate daily coding report (Day in Code)\n\n" +
113
+ " daily Generate daily coding report (Day in Code)\n" +
114
+ " companion Background AI companion (start/stop/status/run)\n\n" +
113
115
  " Personal & Social:\n" +
114
116
  " me Dashboard, posts, notifications, bookmarks, follow\n" +
115
117
  " agent Manage agents (list, create, delete)\n" +
@@ -149,6 +151,7 @@ const cli = yargs(hideBin(process.argv))
149
151
  .command({ ...UninstallCommand, describe: false })
150
152
  .command({ ...McpCommand, describe: false })
151
153
  .command({ ...DailyCommand, describe: false })
154
+ .command({ ...CompanionCommand, describe: false })
152
155
 
153
156
  .fail((msg, err) => {
154
157
  if (
package/src/tui/app.tsx CHANGED
@@ -195,13 +195,13 @@ function App() {
195
195
  const { Config } = await import("../config")
196
196
  const cfg = await Config.load()
197
197
  const model = resolveModelFromConfig(cfg) || AIProvider.DEFAULT_MODEL
198
- if (cfg.model !== model) await Config.save({ model })
198
+ if (cfg.cli?.model !== model) await Config.save({ cli: { model } })
199
199
  setModelName(model)
200
200
  const info = AIProvider.BUILTIN_MODELS[model]
201
201
  setAiProvider(info?.providerID || model.split("/")[0] || "ai")
202
202
 
203
203
  // Fetch credit balance if using codeblog provider
204
- if (cfg.default_provider === "codeblog") {
204
+ if (cfg.cli?.defaultProvider === "codeblog") {
205
205
  try {
206
206
  const { fetchCreditBalance } = await import("../ai/codeblog-provider")
207
207
  const balance = await fetchCreditBalance()
@@ -56,7 +56,7 @@ export function createCommands(deps: CommandDeps): CmdDef[] {
56
56
  }
57
57
 
58
58
  const saveId = picked.providerID === "openai-compatible" ? `openai-compatible/${picked.id}` : picked.id
59
- await Config.save({ model: saveId })
59
+ await Config.save({ cli: { model: saveId } })
60
60
  deps.onAIConfigured()
61
61
  deps.showMsg(`Model switched to ${saveId}`, deps.colors.success)
62
62
  }},
@@ -72,7 +72,11 @@ export function createCommands(deps: CommandDeps): CmdDef[] {
72
72
  { name: "/logout", description: "Sign out of CodeBlog", action: async () => {
73
73
  try {
74
74
  const { Auth } = await import("../auth")
75
+ const { McpBridge } = await import("../mcp/client")
76
+ const { clearChatToolsCache } = await import("../ai/tools")
75
77
  await Auth.remove()
78
+ await McpBridge.disconnect()
79
+ clearChatToolsCache()
76
80
  deps.showMsg("Logged out.", deps.colors.text)
77
81
  deps.onLogout()
78
82
  } catch (err) { deps.showMsg(`Logout failed: ${err instanceof Error ? err.message : String(err)}`, deps.colors.error) }
@@ -306,13 +306,13 @@ export function Home(props: {
306
306
  const current = resolveModelFromConfig(cfg) || AIProvider.DEFAULT_MODEL
307
307
  const currentBuiltin = AIProvider.BUILTIN_MODELS[current]
308
308
  const currentProvider =
309
- cfg.default_provider ||
309
+ cfg.cli?.defaultProvider ||
310
310
  (current.includes("/") ? current.split("/")[0] : currentBuiltin?.providerID) ||
311
311
  "openai"
312
- const providerCfg = cfg.providers?.[currentProvider]
313
- const providerApi = providerCfg?.api || providerCfg?.compat_profile || (currentProvider === "openai" ? "openai" : "openai-compatible")
314
- const providerKey = providerCfg?.api_key
315
- const providerBase = providerCfg?.base_url || (currentProvider === "openai" ? "https://api.openai.com" : "")
312
+ const providerCfg = cfg.cli?.providers?.[currentProvider]
313
+ const providerApi = providerCfg?.apiType || providerCfg?.compatProfile || (currentProvider === "openai" ? "openai" : "openai-compatible")
314
+ const providerKey = providerCfg?.apiKey
315
+ const providerBase = providerCfg?.baseUrl || (currentProvider === "openai" ? "https://api.openai.com" : "")
316
316
 
317
317
  const remote = await (async () => {
318
318
  if (!providerKey || !providerBase) return [] as string[]
@@ -382,7 +382,7 @@ export function Home(props: {
382
382
  async function pickModel(id: string) {
383
383
  try {
384
384
  const { Config } = await import("../../config")
385
- await Config.save({ model: id })
385
+ await Config.save({ cli: { model: id } })
386
386
  props.onAIConfigured()
387
387
  showMsg(`Set model to ${id}`, theme.colors.success)
388
388
  } catch (err) {
@@ -958,18 +958,20 @@ export function Home(props: {
958
958
 
959
959
  const proxyURL = `${(await Config.url()).replace(/\/+$/, "")}/api/v1/ai-credit/chat`
960
960
  const cfg = await Config.load()
961
- const providers = cfg.providers || {}
961
+ const providers = cfg.cli?.providers || {}
962
962
  providers["codeblog"] = {
963
- api_key: "proxy",
964
- base_url: proxyURL,
965
- api: "openai-compatible",
966
- compat_profile: "openai-compatible",
963
+ apiKey: "proxy",
964
+ baseUrl: proxyURL,
965
+ apiType: "openai-compatible",
966
+ compatProfile: "openai-compatible",
967
967
  }
968
968
 
969
969
  await Config.save({
970
- providers,
971
- default_provider: "codeblog",
972
- model: `codeblog/${balance.model}`,
970
+ cli: {
971
+ providers,
972
+ defaultProvider: "codeblog",
973
+ model: `codeblog/${balance.model}`,
974
+ },
973
975
  })
974
976
 
975
977
  const msg = claim.already_claimed
@@ -1006,17 +1008,17 @@ export function Home(props: {
1006
1008
 
1007
1009
  const { Config } = await import("../../config")
1008
1010
  const cfg = await Config.load()
1009
- const providers = cfg.providers || {}
1011
+ const providers = cfg.cli?.providers || {}
1010
1012
  const resolvedApi = verify.detectedApi || choice.api
1011
1013
  const resolvedCompat = choice.providerID === "openai-compatible" && resolvedApi === "openai"
1012
1014
  ? "openai-compatible" as const
1013
1015
  : resolvedApi
1014
- const providerConfig: { api_key: string; base_url?: string; api: typeof resolvedApi; compat_profile: typeof resolvedCompat } = {
1015
- api_key: key,
1016
- api: resolvedApi,
1017
- compat_profile: resolvedCompat,
1016
+ const providerConfig: { apiKey: string; baseUrl?: string; apiType: typeof resolvedApi; compatProfile: typeof resolvedCompat } = {
1017
+ apiKey: key,
1018
+ apiType: resolvedApi,
1019
+ compatProfile: resolvedCompat,
1018
1020
  }
1019
- if (baseURL) providerConfig.base_url = baseURL
1021
+ if (baseURL) providerConfig.baseUrl = baseURL
1020
1022
  providers[choice.providerID] = providerConfig
1021
1023
 
1022
1024
  const saveModel = choice.providerID === "openai-compatible" && !model.includes("/")
@@ -1024,9 +1026,11 @@ export function Home(props: {
1024
1026
  : model
1025
1027
 
1026
1028
  await Config.save({
1027
- providers,
1028
- default_provider: choice.providerID,
1029
- model: saveModel,
1029
+ cli: {
1030
+ providers,
1031
+ defaultProvider: choice.providerID,
1032
+ model: saveModel,
1033
+ },
1030
1034
  })
1031
1035
 
1032
1036
  showMsg(`✓ AI configured: ${choice.name} (${saveModel})`, theme.colors.success)
@@ -46,7 +46,7 @@ export function ModelPicker(props: { onDone: (model?: string) => void }) {
46
46
  const cfg = await Config.load()
47
47
  const resolved = resolveModelFromConfig(cfg) || AIProvider.DEFAULT_MODEL
48
48
  setCurrent(resolved)
49
- if (cfg.model !== resolved) await Config.save({ model: resolved })
49
+ if (cfg.cli?.model !== resolved) await Config.save({ cli: { model: resolved } })
50
50
 
51
51
  setStatus("Fetching models from API...")
52
52
  const all = await AIProvider.available()
@@ -130,7 +130,7 @@ export function ModelPicker(props: { onDone: (model?: string) => void }) {
130
130
  const item = filtered().find((m) => m.id === id)
131
131
  const saveId = item && item.provider === "openai-compatible" ? `openai-compatible/${id}` : id
132
132
  const { Config } = await import("../../config")
133
- await Config.save({ model: saveId })
133
+ await Config.save({ cli: { model: saveId } })
134
134
  props.onDone(saveId)
135
135
  } catch {
136
136
  props.onDone()