codeblog-app 1.0.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 +1 -1
- package/src/ai/provider.ts +54 -4
- package/src/cli/cmd/ai-publish.ts +16 -0
- package/src/cli/cmd/chat.ts +19 -0
- package/src/cli/cmd/config.ts +17 -5
- package/src/cli/cmd/setup.ts +17 -0
- package/src/global/index.ts +8 -4
- package/src/index.ts +1 -1
package/package.json
CHANGED
package/src/ai/provider.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`)
|
|
@@ -289,6 +320,25 @@ export namespace AIProvider {
|
|
|
289
320
|
return cfg.model
|
|
290
321
|
}
|
|
291
322
|
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// Check if any AI provider has a key configured
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
export async function hasAnyKey(): Promise<boolean> {
|
|
327
|
+
// Check env vars
|
|
328
|
+
for (const providerID of Object.keys(PROVIDER_ENV)) {
|
|
329
|
+
const key = await getApiKey(providerID)
|
|
330
|
+
if (key) return true
|
|
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
|
+
}
|
|
339
|
+
return false
|
|
340
|
+
}
|
|
341
|
+
|
|
292
342
|
// ---------------------------------------------------------------------------
|
|
293
343
|
// List available models with key status (for codeblog config --list)
|
|
294
344
|
// ---------------------------------------------------------------------------
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CommandModule } from "yargs"
|
|
2
2
|
import { AIChat } from "../../ai/chat"
|
|
3
|
+
import { AIProvider } from "../../ai/provider"
|
|
3
4
|
import { Posts } from "../../api/posts"
|
|
4
5
|
import { Config } from "../../config"
|
|
5
6
|
import { scanAll, parseSession, registerAllScanners } from "../../scanner"
|
|
@@ -32,6 +33,21 @@ export const AIPublishCommand: CommandModule = {
|
|
|
32
33
|
}),
|
|
33
34
|
handler: async (args) => {
|
|
34
35
|
try {
|
|
36
|
+
// Check AI key before scanning
|
|
37
|
+
const hasKey = await AIProvider.hasAnyKey()
|
|
38
|
+
if (!hasKey) {
|
|
39
|
+
console.log("")
|
|
40
|
+
UI.warn("No AI provider configured. ai-publish requires an AI API key to generate posts.")
|
|
41
|
+
console.log("")
|
|
42
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Configure an AI provider first:${UI.Style.TEXT_NORMAL}`)
|
|
43
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider anthropic --api-key sk-ant-...${UI.Style.TEXT_NORMAL}`)
|
|
44
|
+
console.log("")
|
|
45
|
+
console.log(` ${UI.Style.TEXT_DIM}Run: codeblog config --list to see all supported providers${UI.Style.TEXT_NORMAL}`)
|
|
46
|
+
console.log("")
|
|
47
|
+
process.exitCode = 1
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
UI.info("Scanning IDE sessions...")
|
|
36
52
|
registerAllScanners()
|
|
37
53
|
const sessions = scanAll(args.limit as number)
|
package/src/cli/cmd/chat.ts
CHANGED
|
@@ -23,6 +23,25 @@ export const ChatCommand: CommandModule = {
|
|
|
23
23
|
handler: async (args) => {
|
|
24
24
|
const modelID = args.model as string | undefined
|
|
25
25
|
|
|
26
|
+
// Check AI key before doing anything
|
|
27
|
+
const hasKey = await AIProvider.hasAnyKey()
|
|
28
|
+
if (!hasKey) {
|
|
29
|
+
console.log("")
|
|
30
|
+
UI.warn("No AI provider configured. AI features require an API key.")
|
|
31
|
+
console.log("")
|
|
32
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Configure an AI provider:${UI.Style.TEXT_NORMAL}`)
|
|
33
|
+
console.log("")
|
|
34
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider anthropic --api-key sk-ant-...${UI.Style.TEXT_NORMAL}`)
|
|
35
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider openai --api-key sk-...${UI.Style.TEXT_NORMAL}`)
|
|
36
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider google --api-key AIza...${UI.Style.TEXT_NORMAL}`)
|
|
37
|
+
console.log("")
|
|
38
|
+
console.log(` ${UI.Style.TEXT_DIM}Or set an environment variable: ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.${UI.Style.TEXT_NORMAL}`)
|
|
39
|
+
console.log(` ${UI.Style.TEXT_DIM}Run: codeblog config --list to see all 15+ supported providers${UI.Style.TEXT_NORMAL}`)
|
|
40
|
+
console.log("")
|
|
41
|
+
process.exitCode = 1
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
// Non-interactive: single prompt
|
|
27
46
|
if (args.prompt) {
|
|
28
47
|
try {
|
package/src/cli/cmd/config.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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/cli/cmd/setup.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CommandModule } from "yargs"
|
|
2
2
|
import { Auth } from "../../auth"
|
|
3
3
|
import { OAuth } from "../../auth/oauth"
|
|
4
|
+
import { AIProvider } from "../../ai/provider"
|
|
4
5
|
import { registerAllScanners, scanAll } from "../../scanner"
|
|
5
6
|
import { Publisher } from "../../publisher"
|
|
6
7
|
import { UI } from "../ui"
|
|
@@ -76,10 +77,26 @@ export const SetupCommand: CommandModule = {
|
|
|
76
77
|
console.log("")
|
|
77
78
|
UI.success("Setup complete! 🎉")
|
|
78
79
|
console.log("")
|
|
80
|
+
|
|
81
|
+
// Check if AI is configured
|
|
82
|
+
const hasKey = await AIProvider.hasAnyKey()
|
|
83
|
+
if (!hasKey) {
|
|
84
|
+
console.log(` ${UI.Style.TEXT_WARNING}💡 Optional: Configure an AI provider to unlock AI features${UI.Style.TEXT_NORMAL}`)
|
|
85
|
+
console.log(` ${UI.Style.TEXT_DIM}(ai-publish, chat, and other AI-powered commands)${UI.Style.TEXT_NORMAL}`)
|
|
86
|
+
console.log("")
|
|
87
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider anthropic --api-key sk-ant-...${UI.Style.TEXT_NORMAL}`)
|
|
88
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider openai --api-key sk-...${UI.Style.TEXT_NORMAL}`)
|
|
89
|
+
console.log("")
|
|
90
|
+
console.log(` ${UI.Style.TEXT_DIM}Run: codeblog config --list to see all 15+ supported providers${UI.Style.TEXT_NORMAL}`)
|
|
91
|
+
console.log("")
|
|
92
|
+
}
|
|
93
|
+
|
|
79
94
|
console.log(` ${UI.Style.TEXT_DIM}Useful commands:${UI.Style.TEXT_NORMAL}`)
|
|
80
95
|
console.log(` codeblog feed ${UI.Style.TEXT_DIM}— Browse the forum${UI.Style.TEXT_NORMAL}`)
|
|
81
96
|
console.log(` codeblog scan ${UI.Style.TEXT_DIM}— Scan IDE sessions${UI.Style.TEXT_NORMAL}`)
|
|
82
97
|
console.log(` codeblog publish ${UI.Style.TEXT_DIM}— Publish sessions${UI.Style.TEXT_NORMAL}`)
|
|
98
|
+
console.log(` codeblog ai-publish ${UI.Style.TEXT_DIM}— AI writes a post from your session${UI.Style.TEXT_NORMAL}`)
|
|
99
|
+
console.log(` codeblog chat ${UI.Style.TEXT_DIM}— Interactive AI chat${UI.Style.TEXT_NORMAL}`)
|
|
83
100
|
console.log(` codeblog dashboard ${UI.Style.TEXT_DIM}— Your stats${UI.Style.TEXT_NORMAL}`)
|
|
84
101
|
console.log("")
|
|
85
102
|
},
|
package/src/global/index.ts
CHANGED
|
@@ -6,10 +6,14 @@ import os from "os"
|
|
|
6
6
|
const app = "codeblog"
|
|
7
7
|
|
|
8
8
|
const home = process.env.CODEBLOG_TEST_HOME || os.homedir()
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
9
|
+
const win = os.platform() === "win32"
|
|
10
|
+
const appdata = process.env.APPDATA || path.join(home, "AppData", "Roaming")
|
|
11
|
+
const localappdata = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local")
|
|
12
|
+
|
|
13
|
+
const data = win ? path.join(localappdata, app) : path.join(xdgData || path.join(home, ".local", "share"), app)
|
|
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)
|
|
16
|
+
const state = win ? path.join(localappdata, app, "state") : path.join(xdgState || path.join(home, ".local", "state"), app)
|
|
13
17
|
|
|
14
18
|
export namespace Global {
|
|
15
19
|
export const Path = {
|
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 = "
|
|
38
|
+
const VERSION = "1.2.0"
|
|
39
39
|
|
|
40
40
|
process.on("unhandledRejection", (e) => {
|
|
41
41
|
Log.Default.error("rejection", {
|