codeblog-app 1.1.0 → 1.2.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": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "description": "CLI client for CodeBlog — the forum where AI writes the posts",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -53,7 +53,7 @@ export namespace AIProvider {
53
53
  // Provider env key mapping
54
54
  // ---------------------------------------------------------------------------
55
55
  const PROVIDER_ENV: Record<string, string[]> = {
56
- anthropic: ["ANTHROPIC_API_KEY"],
56
+ anthropic: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"],
57
57
  openai: ["OPENAI_API_KEY"],
58
58
  google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
59
59
  "amazon-bedrock": ["AWS_ACCESS_KEY_ID"],
@@ -70,6 +70,22 @@ export namespace AIProvider {
70
70
  "openai-compatible": ["OPENAI_COMPATIBLE_API_KEY"],
71
71
  }
72
72
 
73
+ // ---------------------------------------------------------------------------
74
+ // Provider base URL env mapping (for third-party API proxies)
75
+ // ---------------------------------------------------------------------------
76
+ const PROVIDER_BASE_URL_ENV: Record<string, string[]> = {
77
+ anthropic: ["ANTHROPIC_BASE_URL"],
78
+ openai: ["OPENAI_BASE_URL", "OPENAI_API_BASE"],
79
+ google: ["GOOGLE_API_BASE_URL"],
80
+ azure: ["AZURE_OPENAI_BASE_URL"],
81
+ xai: ["XAI_BASE_URL"],
82
+ mistral: ["MISTRAL_BASE_URL"],
83
+ groq: ["GROQ_BASE_URL"],
84
+ deepinfra: ["DEEPINFRA_BASE_URL"],
85
+ openrouter: ["OPENROUTER_BASE_URL"],
86
+ "openai-compatible": ["OPENAI_COMPATIBLE_BASE_URL"],
87
+ }
88
+
73
89
  // ---------------------------------------------------------------------------
74
90
  // Provider → npm package mapping
75
91
  // ---------------------------------------------------------------------------
@@ -177,6 +193,18 @@ export namespace AIProvider {
177
193
  return cfg.providers?.[providerID]?.api_key
178
194
  }
179
195
 
196
+ // ---------------------------------------------------------------------------
197
+ // Get base URL for a provider (env var or config)
198
+ // ---------------------------------------------------------------------------
199
+ export async function getBaseUrl(providerID: string): Promise<string | undefined> {
200
+ const envKeys = PROVIDER_BASE_URL_ENV[providerID] || []
201
+ for (const key of envKeys) {
202
+ if (process.env[key]) return process.env[key]
203
+ }
204
+ const cfg = await Config.load()
205
+ return cfg.providers?.[providerID]?.base_url
206
+ }
207
+
180
208
  // ---------------------------------------------------------------------------
181
209
  // List all available providers with their models
182
210
  // ---------------------------------------------------------------------------
@@ -223,7 +251,8 @@ export namespace AIProvider {
223
251
  if (builtin) {
224
252
  const apiKey = await getApiKey(builtin.providerID)
225
253
  if (!apiKey) throw noKeyError(builtin.providerID)
226
- return getLanguageModel(builtin.providerID, id, apiKey)
254
+ const base = await getBaseUrl(builtin.providerID)
255
+ return getLanguageModel(builtin.providerID, id, apiKey, undefined, base)
227
256
  }
228
257
 
229
258
  // Try models.dev
@@ -234,7 +263,8 @@ export namespace AIProvider {
234
263
  const apiKey = await getApiKey(providerID)
235
264
  if (!apiKey) throw noKeyError(providerID)
236
265
  const npm = p.models[id].provider?.npm || p.npm || "@ai-sdk/openai-compatible"
237
- return getLanguageModel(providerID, id, apiKey, npm, p.api)
266
+ const base = await getBaseUrl(providerID)
267
+ return getLanguageModel(providerID, id, apiKey, npm, base || p.api)
238
268
  }
239
269
  }
240
270
 
@@ -244,7 +274,8 @@ export namespace AIProvider {
244
274
  const mid = rest.join("/")
245
275
  const apiKey = await getApiKey(providerID)
246
276
  if (!apiKey) throw noKeyError(providerID)
247
- return getLanguageModel(providerID, mid, apiKey)
277
+ const base = await getBaseUrl(providerID)
278
+ return getLanguageModel(providerID, mid, apiKey, undefined, base)
248
279
  }
249
280
 
250
281
  throw new Error(`Unknown model: ${id}. Run: codeblog config --list`)
@@ -293,10 +324,18 @@ export namespace AIProvider {
293
324
  // Check if any AI provider has a key configured
294
325
  // ---------------------------------------------------------------------------
295
326
  export async function hasAnyKey(): Promise<boolean> {
327
+ // Check env vars
296
328
  for (const providerID of Object.keys(PROVIDER_ENV)) {
297
329
  const key = await getApiKey(providerID)
298
330
  if (key) return true
299
331
  }
332
+ // Check config file (covers third-party providers not in PROVIDER_ENV)
333
+ const cfg = await Config.load()
334
+ if (cfg.providers) {
335
+ for (const p of Object.values(cfg.providers)) {
336
+ if (p.api_key) return true
337
+ }
338
+ }
300
339
  return false
301
340
  }
302
341
 
@@ -36,6 +36,10 @@ export const ConfigCommand: CommandModule = {
36
36
  type: "boolean",
37
37
  default: false,
38
38
  })
39
+ .option("base-url", {
40
+ describe: "Custom base URL for the provider (for third-party API proxies)",
41
+ type: "string",
42
+ })
39
43
  .option("language", {
40
44
  describe: "Default content language for posts (e.g. English, 中文, 日本語)",
41
45
  type: "string",
@@ -80,12 +84,18 @@ export const ConfigCommand: CommandModule = {
80
84
  return
81
85
  }
82
86
 
83
- if (args.provider && args.apiKey) {
87
+ if (args.provider && (args.apiKey || args.baseUrl)) {
84
88
  const cfg = await Config.load()
85
89
  const providers = cfg.providers || {}
86
- providers[args.provider as string] = { ...providers[args.provider as string], api_key: args.apiKey as string }
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
93
+ providers[args.provider as string] = existing
87
94
  await Config.save({ providers })
88
- UI.success(`${args.provider} API key saved`)
95
+ const parts: string[] = []
96
+ if (args.apiKey) parts.push("API key")
97
+ if (args.baseUrl) parts.push(`base URL (${args.baseUrl})`)
98
+ UI.success(`${args.provider} ${parts.join(" + ")} saved`)
89
99
  return
90
100
  }
91
101
 
@@ -125,12 +135,14 @@ export const ConfigCommand: CommandModule = {
125
135
  console.log(` ${UI.Style.TEXT_NORMAL_BOLD}AI Providers${UI.Style.TEXT_NORMAL}`)
126
136
  for (const [id, p] of Object.entries(providers)) {
127
137
  const masked = p.api_key ? p.api_key.slice(0, 8) + "..." : "not set"
128
- console.log(` ${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL} ${id}: ${UI.Style.TEXT_DIM}${masked}${UI.Style.TEXT_NORMAL}`)
138
+ const url = p.base_url ? `${p.base_url}` : ""
139
+ console.log(` ${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL} ${id}: ${UI.Style.TEXT_DIM}${masked}${url}${UI.Style.TEXT_NORMAL}`)
129
140
  }
130
141
  } else {
131
142
  console.log(` ${UI.Style.TEXT_DIM}No AI providers configured.${UI.Style.TEXT_NORMAL}`)
132
143
  console.log(` ${UI.Style.TEXT_DIM}Set one: codeblog config --provider anthropic --api-key sk-...${UI.Style.TEXT_NORMAL}`)
133
- console.log(` ${UI.Style.TEXT_DIM}Or use env: ANTHROPIC_API_KEY=sk-...${UI.Style.TEXT_NORMAL}`)
144
+ console.log(` ${UI.Style.TEXT_DIM}Third-party proxy: codeblog config --provider anthropic --api-key sk-... --base-url https://proxy.example.com${UI.Style.TEXT_NORMAL}`)
145
+ console.log(` ${UI.Style.TEXT_DIM}Or use env: ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL${UI.Style.TEXT_NORMAL}`)
134
146
  }
135
147
  console.log("")
136
148
  } catch (err) {
package/src/index.ts CHANGED
@@ -35,7 +35,7 @@ import { WeeklyDigestCommand } from "./cli/cmd/weekly-digest"
35
35
  import { TagsCommand } from "./cli/cmd/tags"
36
36
  import { ExploreCommand } from "./cli/cmd/explore"
37
37
 
38
- const VERSION = "1.1.0"
38
+ const VERSION = "1.2.0"
39
39
 
40
40
  process.on("unhandledRejection", (e) => {
41
41
  Log.Default.error("rejection", {