codeblog-app 2.8.1 → 2.9.0

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "codeblog-app",
4
- "version": "2.8.1",
4
+ "version": "2.9.0",
5
5
  "description": "CLI client for CodeBlog — Agent Only Coding Society",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -58,11 +58,11 @@
58
58
  "typescript": "5.8.2"
59
59
  },
60
60
  "optionalDependencies": {
61
- "codeblog-app-darwin-arm64": "2.8.1",
62
- "codeblog-app-darwin-x64": "2.8.1",
63
- "codeblog-app-linux-arm64": "2.8.1",
64
- "codeblog-app-linux-x64": "2.8.1",
65
- "codeblog-app-windows-x64": "2.8.1"
61
+ "codeblog-app-darwin-arm64": "2.9.0",
62
+ "codeblog-app-darwin-x64": "2.9.0",
63
+ "codeblog-app-linux-arm64": "2.9.0",
64
+ "codeblog-app-linux-x64": "2.9.0",
65
+ "codeblog-app-windows-x64": "2.9.0"
66
66
  },
67
67
  "dependencies": {
68
68
  "@ai-sdk/anthropic": "^3.0.44",
@@ -73,7 +73,7 @@
73
73
  "@opentui/core": "^0.1.79",
74
74
  "@opentui/solid": "^0.1.79",
75
75
  "ai": "^6.0.86",
76
- "codeblog-mcp": "2.8.1",
76
+ "codeblog-mcp": "2.9.0",
77
77
  "drizzle-orm": "1.0.0-beta.12-a5629fb",
78
78
  "fuzzysort": "^3.1.0",
79
79
  "hono": "4.10.7",
@@ -16,7 +16,7 @@ describe("AI Compat", () => {
16
16
  const compat = resolveCompat({
17
17
  providerID: "openai-compatible",
18
18
  modelID: "claude-sonnet-4-20250514",
19
- providerConfig: { api_key: "x", api: "anthropic", compat_profile: "anthropic" },
19
+ providerConfig: { apiKey: "x", apiType: "anthropic", compatProfile: "anthropic" },
20
20
  })
21
21
  expect(compat.api).toBe("anthropic")
22
22
  expect(compat.compatProfile).toBe("anthropic")
@@ -11,25 +11,29 @@ describe("provider-registry", () => {
11
11
 
12
12
  test("routes explicit provider/model first", async () => {
13
13
  const route = await routeModel("openai/gpt-4o", {
14
- api_url: "https://codeblog.ai",
15
- providers: {
16
- openai: { api_key: "sk-openai" },
14
+ serverUrl: "https://codeblog.ai",
15
+ cli: {
16
+ providers: {
17
+ openai: { apiKey: "sk-openai" },
18
+ },
17
19
  },
18
20
  })
19
21
  expect(route.providerID).toBe("openai")
20
22
  expect(route.modelID).toBe("gpt-4o")
21
23
  })
22
24
 
23
- test("routes by default_provider for unknown model", async () => {
25
+ test("routes by defaultProvider for unknown model", async () => {
24
26
  const route = await routeModel("deepseek-chat", {
25
- api_url: "https://codeblog.ai",
26
- default_provider: "openai-compatible",
27
- providers: {
28
- "openai-compatible": {
29
- api_key: "sk-compat",
30
- base_url: "https://api.deepseek.com",
31
- api: "openai-compatible",
32
- compat_profile: "openai-compatible",
27
+ serverUrl: "https://codeblog.ai",
28
+ cli: {
29
+ defaultProvider: "openai-compatible",
30
+ providers: {
31
+ "openai-compatible": {
32
+ apiKey: "sk-compat",
33
+ baseUrl: "https://api.deepseek.com",
34
+ apiType: "openai-compatible",
35
+ compatProfile: "openai-compatible",
36
+ },
33
37
  },
34
38
  },
35
39
  })
@@ -39,20 +43,24 @@ describe("provider-registry", () => {
39
43
 
40
44
  test("unknown model throws deterministic actionable error", async () => {
41
45
  await expect(routeModel("unknown-model-x", {
42
- api_url: "https://codeblog.ai",
43
- providers: {
44
- openai: { api_key: "sk-openai" },
46
+ serverUrl: "https://codeblog.ai",
47
+ cli: {
48
+ providers: {
49
+ openai: { apiKey: "sk-openai" },
50
+ },
45
51
  },
46
52
  })).rejects.toThrow('Unknown model "unknown-model-x"')
47
53
  })
48
54
 
49
55
  test("multi-provider routing is deterministic by prefix", async () => {
50
56
  const route = await routeModel("gpt-4o-mini", {
51
- api_url: "https://codeblog.ai",
52
- default_provider: "openai-compatible",
53
- providers: {
54
- openai: { api_key: "sk-openai" },
55
- "openai-compatible": { api_key: "sk-compat", base_url: "https://api.deepseek.com" },
57
+ serverUrl: "https://codeblog.ai",
58
+ cli: {
59
+ defaultProvider: "openai-compatible",
60
+ providers: {
61
+ openai: { apiKey: "sk-openai" },
62
+ "openai-compatible": { apiKey: "sk-compat", baseUrl: "https://api.deepseek.com" },
63
+ },
56
64
  },
57
65
  })
58
66
  expect(route.providerID).toBe("openai")
@@ -61,11 +69,13 @@ describe("provider-registry", () => {
61
69
 
62
70
  test("legacy model id is normalized to stable default", async () => {
63
71
  const route = await routeModel(undefined, {
64
- api_url: "https://codeblog.ai",
65
- default_provider: "openai",
66
- model: "4.0Ultra",
67
- providers: {
68
- openai: { api_key: "sk-openai" },
72
+ serverUrl: "https://codeblog.ai",
73
+ cli: {
74
+ defaultProvider: "openai",
75
+ model: "4.0Ultra",
76
+ providers: {
77
+ openai: { apiKey: "sk-openai" },
78
+ },
69
79
  },
70
80
  })
71
81
  expect(route.providerID).toBe("openai")
@@ -74,10 +84,12 @@ describe("provider-registry", () => {
74
84
 
75
85
  test("missing model uses provider-specific default", async () => {
76
86
  const route = await routeModel(undefined, {
77
- api_url: "https://codeblog.ai",
78
- default_provider: "openai",
79
- providers: {
80
- openai: { api_key: "sk-openai" },
87
+ serverUrl: "https://codeblog.ai",
88
+ cli: {
89
+ defaultProvider: "openai",
90
+ providers: {
91
+ openai: { apiKey: "sk-openai" },
92
+ },
81
93
  },
82
94
  })
83
95
  expect(route.providerID).toBe("openai")
@@ -86,10 +98,12 @@ describe("provider-registry", () => {
86
98
 
87
99
  test("missing model on openai-compatible keeps provider prefix", async () => {
88
100
  const route = await routeModel(undefined, {
89
- api_url: "https://codeblog.ai",
90
- default_provider: "openai-compatible",
91
- providers: {
92
- "openai-compatible": { api_key: "sk-compat", base_url: "https://example.com/v1", api: "openai-compatible" },
101
+ serverUrl: "https://codeblog.ai",
102
+ cli: {
103
+ defaultProvider: "openai-compatible",
104
+ providers: {
105
+ "openai-compatible": { apiKey: "sk-compat", baseUrl: "https://example.com/v1", apiType: "openai-compatible" },
106
+ },
93
107
  },
94
108
  })
95
109
  expect(route.providerID).toBe("openai-compatible")
@@ -7,7 +7,7 @@ import { Config } from "../../config"
7
7
  describe("AIProvider", () => {
8
8
  const originalEnv = { ...process.env }
9
9
  const testHome = path.join(os.tmpdir(), `codeblog-provider-test-${process.pid}-${Date.now()}`)
10
- const configFile = path.join(testHome, ".config", "codeblog", "config.json")
10
+ const configFile = path.join(testHome, ".codeblog", "config.json")
11
11
  const xdgData = path.join(testHome, ".local", "share")
12
12
  const xdgCache = path.join(testHome, ".cache")
13
13
  const xdgConfig = path.join(testHome, ".config")
@@ -207,7 +207,7 @@ describe("AIProvider", () => {
207
207
 
208
208
  test("getModel throws when no API key for builtin model", async () => {
209
209
  const load = Config.load
210
- Config.load = async () => ({ api_url: "https://codeblog.ai" })
210
+ Config.load = async () => ({ serverUrl: "https://codeblog.ai" })
211
211
  try {
212
212
  await expect(AIProvider.getModel("gpt-4o")).rejects.toThrow("No API key for openai")
213
213
  } finally {
@@ -215,10 +215,10 @@ describe("AIProvider", () => {
215
215
  }
216
216
  })
217
217
 
218
- test("getModel falls back to provider with base_url for unknown model", async () => {
219
- // When a provider with base_url is configured, unknown models get sent there
218
+ test("getModel falls back to provider with baseUrl for unknown model", async () => {
219
+ // When a provider with baseUrl is configured, unknown models get sent there
220
220
  // instead of throwing. This test verifies the fallback behavior.
221
- // If no provider has a base_url, it would throw.
221
+ // If no provider has a baseUrl, it would throw.
222
222
  const result = AIProvider.getModel("nonexistent-model-xyz")
223
223
  // Either resolves (provider with base_url available) or rejects
224
224
  const settled = await Promise.allSettled([result])
@@ -45,7 +45,7 @@ type FetchFn = (
45
45
 
46
46
  export async function getCodeblogFetch(): Promise<FetchFn> {
47
47
  const cfg = await Config.load()
48
- const proxyURL = (cfg.providers?.codeblog?.base_url || `${(await Config.url()).replace(/\/+$/, "")}/api/v1/ai-credit/chat`).replace(/\/+$/, "")
48
+ const proxyURL = (cfg.cli?.providers?.codeblog?.baseUrl || `${(await Config.url()).replace(/\/+$/, "")}/api/v1/ai-credit/chat`).replace(/\/+$/, "")
49
49
 
50
50
  return async (input, init) => {
51
51
  const headers = new Headers(init?.headers)
@@ -90,29 +90,29 @@ export async function saveProvider(url: string, key: string): Promise<{ provider
90
90
  process.env[envBase] = url
91
91
 
92
92
  const cfg = await Config.load()
93
- const providers = cfg.providers || {}
93
+ const providers = cfg.cli?.providers || {}
94
94
  providers[provider] = {
95
- api_key: key,
96
- base_url: url,
97
- api: detected === "anthropic" ? "anthropic" : "openai-compatible",
98
- compat_profile: detected === "anthropic" ? "anthropic" : "openai-compatible",
95
+ apiKey: key,
96
+ baseUrl: url,
97
+ apiType: detected === "anthropic" ? "anthropic" : "openai-compatible",
98
+ compatProfile: detected === "anthropic" ? "anthropic" : "openai-compatible",
99
99
  }
100
100
 
101
101
  // Auto-set model if not already configured
102
- const update: Record<string, unknown> = { providers, default_provider: provider }
103
- if (!cfg.model) {
102
+ const cliUpdate: Record<string, unknown> = { providers, defaultProvider: provider }
103
+ if (!cfg.cli?.model) {
104
104
  const { defaultModelForProvider } = await import("./models")
105
105
  if (detected === "anthropic") {
106
- update.model = defaultModelForProvider("anthropic")
106
+ cliUpdate.model = defaultModelForProvider("anthropic")
107
107
  } else {
108
108
  // For openai-compatible with custom URL, try to fetch available models
109
109
  const model = await fetchFirstModel(url, key)
110
- if (model) update.model = `openai-compatible/${model}`
111
- else update.model = `openai-compatible/${defaultModelForProvider("openai-compatible")}`
110
+ if (model) cliUpdate.model = `openai-compatible/${model}`
111
+ else cliUpdate.model = `openai-compatible/${defaultModelForProvider("openai-compatible")}`
112
112
  }
113
113
  }
114
114
 
115
- await Config.save(update)
115
+ await Config.save({ cli: cliUpdate })
116
116
  return { provider: `${detected} format` }
117
117
  }
118
118
 
@@ -120,21 +120,21 @@ export async function saveProvider(url: string, key: string): Promise<{ provider
120
120
  if (ENV_MAP[provider]) process.env[ENV_MAP[provider]] = key
121
121
 
122
122
  const cfg = await Config.load()
123
- const providers = cfg.providers || {}
123
+ const providers = cfg.cli?.providers || {}
124
124
  providers[provider] = {
125
- api_key: key,
126
- api: provider === "anthropic" ? "anthropic" : provider === "google" ? "google" : provider === "openai" ? "openai" : "openai-compatible",
127
- compat_profile: provider === "anthropic" ? "anthropic" : provider === "google" ? "google" : provider === "openai" ? "openai" : "openai-compatible",
125
+ apiKey: key,
126
+ apiType: provider === "anthropic" ? "anthropic" : provider === "google" ? "google" : provider === "openai" ? "openai" : "openai-compatible",
127
+ compatProfile: provider === "anthropic" ? "anthropic" : provider === "google" ? "google" : provider === "openai" ? "openai" : "openai-compatible",
128
128
  }
129
129
 
130
130
  // Auto-set model for known providers
131
- const update: Record<string, unknown> = { providers, default_provider: provider }
132
- if (!cfg.model) {
131
+ const cliUpdate2: Record<string, unknown> = { providers, defaultProvider: provider }
132
+ if (!cfg.cli?.model) {
133
133
  const { defaultModelForProvider } = await import("./models")
134
- update.model = defaultModelForProvider(provider)
134
+ cliUpdate2.model = defaultModelForProvider(provider)
135
135
  }
136
136
 
137
- await Config.save(update)
137
+ await Config.save({ cli: cliUpdate2 })
138
138
  return { provider }
139
139
  }
140
140
 
package/src/ai/models.ts CHANGED
@@ -49,11 +49,11 @@ export function defaultModelForProvider(providerID?: string): string {
49
49
  return PROVIDER_DEFAULT_MODEL[providerID] || DEFAULT_MODEL
50
50
  }
51
51
 
52
- export function resolveModelFromConfig(cfg: { model?: string; default_provider?: string }): string {
53
- const model = normalizeModelID(cfg.model)
52
+ export function resolveModelFromConfig(cfg: { cli?: { model?: string; defaultProvider?: string } }): string {
53
+ const model = normalizeModelID(cfg.cli?.model)
54
54
  if (model) return model
55
- const fallback = defaultModelForProvider(cfg.default_provider)
56
- if (cfg.default_provider === "openai-compatible" && !fallback.includes("/")) {
55
+ const fallback = defaultModelForProvider(cfg.cli?.defaultProvider)
56
+ if (cfg.cli?.defaultProvider === "openai-compatible" && !fallback.includes("/")) {
57
57
  return `openai-compatible/${fallback}`
58
58
  }
59
59
  return fallback
@@ -49,7 +49,7 @@ function readFirstEnv(keys: string[]): string | undefined {
49
49
 
50
50
  export async function loadProviders(cfgInput?: Config.CodeblogConfig): Promise<ProviderRegistryView> {
51
51
  const cfg = cfgInput || await Config.load()
52
- const user = cfg.providers || {}
52
+ const user = cfg.cli?.providers || {}
53
53
  const ids = new Set<string>([
54
54
  ...Object.keys(PROVIDER_ENV),
55
55
  ...Object.keys(user),
@@ -62,12 +62,12 @@ export async function loadProviders(cfgInput?: Config.CodeblogConfig): Promise<P
62
62
  providers[id] = {
63
63
  id,
64
64
  config,
65
- apiKey: readFirstEnv(PROVIDER_ENV[id] || []) || config?.api_key,
66
- baseURL: readFirstEnv(PROVIDER_BASE_URL_ENV[id] || []) || config?.base_url,
65
+ apiKey: readFirstEnv(PROVIDER_ENV[id] || []) || config?.apiKey,
66
+ baseURL: readFirstEnv(PROVIDER_BASE_URL_ENV[id] || []) || config?.baseUrl,
67
67
  }
68
68
  }
69
69
 
70
- return { providers, defaultProvider: cfg.default_provider }
70
+ return { providers, defaultProvider: cfg.cli?.defaultProvider }
71
71
  }
72
72
 
73
73
  function availableProvidersWithKeys(providers: Record<string, ProviderRuntimeConfig>): string[] {
@@ -40,7 +40,7 @@ export namespace AIProvider {
40
40
  if (process.env[key]) return process.env[key]
41
41
  }
42
42
  const cfg = await Config.load()
43
- return cfg.providers?.[providerID]?.api_key
43
+ return cfg.cli?.providers?.[providerID]?.apiKey
44
44
  }
45
45
 
46
46
  export async function getBaseUrl(providerID: string): Promise<string | undefined> {
@@ -49,7 +49,7 @@ export namespace AIProvider {
49
49
  if (process.env[key]) return process.env[key]
50
50
  }
51
51
  const cfg = await Config.load()
52
- return cfg.providers?.[providerID]?.base_url
52
+ return cfg.cli?.providers?.[providerID]?.baseUrl
53
53
  }
54
54
 
55
55
  export async function listProviders(): Promise<Record<string, { name: string; models: string[]; hasKey: boolean }>> {
@@ -89,7 +89,7 @@ export namespace AIProvider {
89
89
  }
90
90
 
91
91
  export async function getModel(modelID?: string): Promise<LanguageModel> {
92
- const useRegistry = await Config.featureEnabled("ai_provider_registry_v2")
92
+ const useRegistry = await Config.featureEnabled("aiProviderRegistryV2")
93
93
  if (useRegistry) {
94
94
  const route = await routeModel(modelID)
95
95
  const customFetch = route.providerID === "codeblog" ? await loadCodeblogFetch() : undefined
@@ -99,7 +99,7 @@ export namespace AIProvider {
99
99
  }
100
100
 
101
101
  export async function resolveModelCompat(modelID?: string): Promise<ModelCompatConfig> {
102
- const useRegistry = await Config.featureEnabled("ai_provider_registry_v2")
102
+ const useRegistry = await Config.featureEnabled("aiProviderRegistryV2")
103
103
  if (useRegistry) return (await routeModel(modelID)).compat
104
104
  return (await resolveLegacyRoute(modelID)).compat
105
105
  }
@@ -130,7 +130,7 @@ export namespace AIProvider {
130
130
  modelID: requested,
131
131
  apiKey,
132
132
  baseURL,
133
- compat: resolveCompat({ providerID: builtin.providerID, modelID: requested, providerConfig: cfg.providers?.[builtin.providerID] }),
133
+ compat: resolveCompat({ providerID: builtin.providerID, modelID: requested, providerConfig: cfg.cli?.providers?.[builtin.providerID] }),
134
134
  }
135
135
  }
136
136
 
@@ -145,20 +145,20 @@ export namespace AIProvider {
145
145
  modelID,
146
146
  apiKey,
147
147
  baseURL,
148
- compat: resolveCompat({ providerID: providerID!, modelID, providerConfig: cfg.providers?.[providerID!] }),
148
+ compat: resolveCompat({ providerID: providerID!, modelID, providerConfig: cfg.cli?.providers?.[providerID!] }),
149
149
  }
150
150
  }
151
151
 
152
- if (cfg.providers) {
153
- for (const [providerID, p] of Object.entries(cfg.providers)) {
154
- if (!p.api_key) continue
155
- const baseURL = p.base_url || (await getBaseUrl(providerID))
152
+ if (cfg.cli?.providers) {
153
+ for (const [providerID, p] of Object.entries(cfg.cli.providers)) {
154
+ if (!p.apiKey) continue
155
+ const baseURL = p.baseUrl || (await getBaseUrl(providerID))
156
156
  if (!baseURL) continue
157
- log.info("legacy fallback: unknown model routed to first provider with base_url", { provider: providerID, model: requested })
157
+ log.info("legacy fallback: unknown model routed to first provider with baseUrl", { provider: providerID, model: requested })
158
158
  return {
159
159
  providerID,
160
160
  modelID: requested,
161
- apiKey: p.api_key,
161
+ apiKey: p.apiKey,
162
162
  baseURL,
163
163
  compat: resolveCompat({ providerID, modelID: requested, providerConfig: p }),
164
164
  }
package/src/ai/types.ts CHANGED
@@ -50,10 +50,10 @@ function isOfficialOpenAIBase(baseURL?: string): boolean {
50
50
  }
51
51
 
52
52
  function resolveApiFromProvider(providerID: string, cfg?: Config.ProviderConfig): ModelApi {
53
- if (providerID === "openai" && cfg?.base_url && !isOfficialOpenAIBase(cfg.base_url)) {
53
+ if (providerID === "openai" && cfg?.baseUrl && !isOfficialOpenAIBase(cfg.baseUrl)) {
54
54
  return "openai-compatible"
55
55
  }
56
- if (cfg?.api) return cfg.api
56
+ if (cfg?.apiType) return cfg.apiType
57
57
  if (providerID === "anthropic") return "anthropic"
58
58
  if (providerID === "openai") return "openai"
59
59
  if (providerID === "google") return "google"
@@ -74,7 +74,7 @@ export function resolveCompat(args: {
74
74
  providerConfig?: Config.ProviderConfig
75
75
  }): ModelCompatConfig {
76
76
  const api = resolveApiFromProvider(args.providerID, args.providerConfig)
77
- const configured = args.providerConfig?.compat_profile
77
+ const configured = args.providerConfig?.compatProfile
78
78
  const compatProfile = api === "openai-compatible" && configured === "openai"
79
79
  ? "openai-compatible"
80
80
  : configured || defaultCompatForApi(api)
package/src/auth/index.ts CHANGED
@@ -1,7 +1,5 @@
1
- import path from "path"
2
- import { chmod, writeFile } from "fs/promises"
3
- import { Global } from "../global"
4
1
  import z from "zod"
2
+ import { Config } from "../config"
5
3
 
6
4
  export namespace Auth {
7
5
  export const Token = z
@@ -14,36 +12,27 @@ export namespace Auth {
14
12
  .meta({ ref: "AuthToken" })
15
13
  export type Token = z.infer<typeof Token>
16
14
 
17
- const filepath = path.join(Global.Path.data, "auth.json")
18
-
19
15
  export async function get(): Promise<Token | null> {
20
- const file = Bun.file(filepath)
21
- const data = await file.json().catch(() => null)
22
- if (!data) return null
23
- const parsed = Token.safeParse(data)
24
- if (!parsed.success) return null
25
- return parsed.data
16
+ const cfg = await Config.load()
17
+ if (!cfg.auth?.apiKey) return null
18
+ return { type: "apikey", value: cfg.auth.apiKey, username: cfg.auth.username }
26
19
  }
27
20
 
28
21
  export async function set(token: Token) {
29
- await writeFile(filepath, JSON.stringify(token, null, 2))
30
- await chmod(filepath, 0o600).catch(() => {})
22
+ await Config.save({ auth: { apiKey: token.value, username: token.username } })
31
23
  }
32
24
 
33
25
  export async function remove() {
34
- const fs = await import("fs/promises")
35
- await fs.unlink(filepath).catch(() => {})
26
+ await Config.save({ auth: { apiKey: undefined, userId: undefined, activeAgent: undefined, username: undefined } })
36
27
  }
37
28
 
38
29
  export async function header(): Promise<Record<string, string>> {
39
30
  const token = await get()
40
31
  if (!token) return {}
41
- if (token.type === "apikey") return { Authorization: `Bearer ${token.value}` }
42
32
  return { Authorization: `Bearer ${token.value}` }
43
33
  }
44
34
 
45
35
  export async function authenticated(): Promise<boolean> {
46
- const token = await get()
47
- return token !== null
36
+ return (await get()) !== null
48
37
  }
49
38
  }
package/src/auth/oauth.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Auth } from "./index"
2
2
  import { Config } from "../config"
3
- import { McpBridge } from "../mcp/client"
4
3
  import { Server } from "../server"
5
4
  import { Log } from "../util/log"
6
5
 
@@ -58,12 +57,6 @@ export namespace OAuth {
58
57
  throw new Error(ownerMismatch)
59
58
  }
60
59
  } else {
61
- // Sync API key to MCP config (~/.codeblog/config.json)
62
- try {
63
- await McpBridge.callTool("codeblog_setup", { api_key: key })
64
- } catch (err) {
65
- log.warn("failed to sync API key to MCP config", { error: String(err) })
66
- }
67
60
  log.info("authenticated with api key")
68
61
  }
69
62
  } else if (token) {
@@ -86,12 +86,12 @@ export const ConfigCommand: CommandModule = {
86
86
 
87
87
  if (args.provider && (args.apiKey || args.baseUrl)) {
88
88
  const cfg = await Config.load()
89
- const providers = cfg.providers || {}
89
+ const providers = cfg.cli?.providers || {}
90
90
  const existing = providers[args.provider as string] || {} as Config.ProviderConfig
91
- if (args.apiKey) existing.api_key = args.apiKey as string
92
- if (args.baseUrl) existing.base_url = args.baseUrl as string
91
+ if (args.apiKey) existing.apiKey = args.apiKey as string
92
+ if (args.baseUrl) existing.baseUrl = args.baseUrl as string
93
93
  providers[args.provider as string] = existing
94
- await Config.save({ providers })
94
+ await Config.save({ cli: { providers } })
95
95
  const parts: string[] = []
96
96
  if (args.apiKey) parts.push("API key")
97
97
  if (args.baseUrl) parts.push(`base URL (${args.baseUrl})`)
@@ -100,19 +100,19 @@ export const ConfigCommand: CommandModule = {
100
100
  }
101
101
 
102
102
  if (args.model) {
103
- await Config.save({ model: args.model as string })
103
+ await Config.save({ cli: { model: args.model as string } })
104
104
  UI.success(`Default model set to ${args.model}`)
105
105
  return
106
106
  }
107
107
 
108
108
  if (args.url) {
109
- await Config.save({ api_url: args.url as string })
109
+ await Config.save({ serverUrl: args.url as string })
110
110
  UI.success(`Server URL set to ${args.url}`)
111
111
  return
112
112
  }
113
113
 
114
114
  if (args.language) {
115
- await Config.save({ default_language: args.language as string })
115
+ await Config.save({ cli: { defaultLanguage: args.language as string } })
116
116
  UI.success(`Default language set to ${args.language}`)
117
117
  return
118
118
  }
@@ -121,22 +121,22 @@ export const ConfigCommand: CommandModule = {
121
121
  const cfg = await Config.load()
122
122
  const { resolveModelFromConfig } = await import("../../ai/models")
123
123
  const model = resolveModelFromConfig(cfg) || AIProvider.DEFAULT_MODEL
124
- const providers = cfg.providers || {}
124
+ const providers = cfg.cli?.providers || {}
125
125
 
126
126
  console.log("")
127
127
  console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Current Config${UI.Style.TEXT_NORMAL}`)
128
128
  console.log(` ${UI.Style.TEXT_DIM}${Config.filepath}${UI.Style.TEXT_NORMAL}`)
129
129
  console.log("")
130
130
  console.log(` Model: ${UI.Style.TEXT_HIGHLIGHT}${model}${UI.Style.TEXT_NORMAL}`)
131
- console.log(` API URL: ${cfg.api_url || "https://codeblog.ai"}`)
132
- console.log(` Language: ${cfg.default_language || `${UI.Style.TEXT_DIM}(server default)${UI.Style.TEXT_NORMAL}`}`)
131
+ console.log(` API URL: ${cfg.serverUrl || "https://codeblog.ai"}`)
132
+ console.log(` Language: ${cfg.cli?.defaultLanguage || `${UI.Style.TEXT_DIM}(server default)${UI.Style.TEXT_NORMAL}`}`)
133
133
  console.log("")
134
134
 
135
135
  if (Object.keys(providers).length > 0) {
136
136
  console.log(` ${UI.Style.TEXT_NORMAL_BOLD}AI Providers${UI.Style.TEXT_NORMAL}`)
137
137
  for (const [id, p] of Object.entries(providers)) {
138
- const masked = p.api_key ? p.api_key.slice(0, 8) + "..." : "not set"
139
- const url = p.base_url ? ` → ${p.base_url}` : ""
138
+ const masked = p.apiKey ? p.apiKey.slice(0, 8) + "..." : "not set"
139
+ const url = p.baseUrl ? ` → ${p.baseUrl}` : ""
140
140
  console.log(` ${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL} ${id}: ${UI.Style.TEXT_DIM}${masked}${url}${UI.Style.TEXT_NORMAL}`)
141
141
  }
142
142
  } else {
@@ -1,7 +1,6 @@
1
1
  import type { CommandModule } from "yargs"
2
2
  import { OAuth } from "../../auth/oauth"
3
3
  import { Auth } from "../../auth"
4
- import { McpBridge } from "../../mcp/client"
5
4
  import { UI } from "../ui"
6
5
 
7
6
  export const LoginCommand: CommandModule = {
@@ -23,12 +22,6 @@ export const LoginCommand: CommandModule = {
23
22
  if (args.key) {
24
23
  const key = args.key as string
25
24
  await Auth.set({ type: "apikey", value: key })
26
- // Sync API key to MCP config (~/.codeblog/config.json)
27
- try {
28
- await McpBridge.callTool("codeblog_setup", { api_key: key })
29
- } catch {
30
- // Non-fatal: MCP sync failed but CLI auth is saved
31
- }
32
25
  UI.success("Logged in with API key")
33
26
  return
34
27
  }
@@ -1,14 +1,16 @@
1
1
  import type { CommandModule } from "yargs"
2
2
  import { Auth } from "../../auth"
3
- import { Config } from "../../config"
4
3
  import { UI } from "../ui"
5
4
 
6
5
  export const LogoutCommand: CommandModule = {
7
6
  command: "logout",
8
7
  describe: "Logout from CodeBlog",
9
8
  handler: async () => {
9
+ const { McpBridge } = await import("../../mcp/client")
10
+ const { clearChatToolsCache } = await import("../../ai/tools")
10
11
  await Auth.remove()
11
- await Config.clearActiveAgent()
12
+ await McpBridge.disconnect()
13
+ clearChatToolsCache()
12
14
  UI.success("Logged out successfully")
13
15
  },
14
16
  }