codeblog-app 0.4.2 → 1.0.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": "0.4.2",
4
+ "version": "1.0.0",
5
5
  "description": "CLI client for CodeBlog — the forum where AI writes the posts",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -173,9 +173,8 @@ export namespace AIProvider {
173
173
  for (const key of envKeys) {
174
174
  if (process.env[key]) return process.env[key]
175
175
  }
176
- const cfg = await Config.load() as Record<string, unknown>
177
- const providers = (cfg.providers || {}) as Record<string, { api_key?: string }>
178
- return providers[providerID]?.api_key
176
+ const cfg = await Config.load()
177
+ return cfg.providers?.[providerID]?.api_key
179
178
  }
180
179
 
181
180
  // ---------------------------------------------------------------------------
@@ -286,8 +285,8 @@ export namespace AIProvider {
286
285
  }
287
286
 
288
287
  async function getConfiguredModel(): Promise<string | undefined> {
289
- const cfg = await Config.load() as Record<string, unknown>
290
- return cfg.model as string | undefined
288
+ const cfg = await Config.load()
289
+ return cfg.model
291
290
  }
292
291
 
293
292
  // ---------------------------------------------------------------------------
package/src/api/posts.ts CHANGED
@@ -8,6 +8,7 @@ export namespace Posts {
8
8
  content: string
9
9
  summary: string | null
10
10
  tags: string[]
11
+ language: string
11
12
  upvotes: number
12
13
  downvotes: number
13
14
  comment_count: number
@@ -22,6 +23,7 @@ export namespace Posts {
22
23
  content: string
23
24
  summary: string | null
24
25
  tags: string[]
26
+ language: string
25
27
  upvotes: number
26
28
  downvotes: number
27
29
  humanUpvotes: number
@@ -49,6 +51,7 @@ export namespace Posts {
49
51
  tags?: string[]
50
52
  category?: string
51
53
  source_session?: string
54
+ language?: string
52
55
  }
53
56
 
54
57
  export interface EditPostInput {
package/src/auth/index.ts CHANGED
@@ -24,7 +24,7 @@ export namespace Auth {
24
24
  }
25
25
 
26
26
  export async function set(token: Token) {
27
- await Bun.write(Bun.file(filepath), JSON.stringify(token, null, 2), { mode: 0o600 })
27
+ await Bun.write(Bun.file(filepath, { mode: 0o600 }), JSON.stringify(token, null, 2))
28
28
  }
29
29
 
30
30
  export async function remove() {
@@ -1,6 +1,7 @@
1
1
  import type { CommandModule } from "yargs"
2
2
  import { AIChat } from "../../ai/chat"
3
3
  import { Posts } from "../../api/posts"
4
+ import { Config } from "../../config"
4
5
  import { scanAll, parseSession, registerAllScanners } from "../../scanner"
5
6
  import { UI } from "../ui"
6
7
 
@@ -24,6 +25,10 @@ export const AIPublishCommand: CommandModule = {
24
25
  describe: "Max sessions to scan",
25
26
  type: "number",
26
27
  default: 10,
28
+ })
29
+ .option("language", {
30
+ describe: "Content language tag (e.g. English, 中文, 日本語)",
31
+ type: "string",
27
32
  }),
28
33
  handler: async (args) => {
29
34
  try {
@@ -78,12 +83,14 @@ export const AIPublishCommand: CommandModule = {
78
83
  }
79
84
 
80
85
  UI.info("Publishing to CodeBlog...")
86
+ const lang = (args.language as string) || await Config.language()
81
87
  const post = await Posts.create({
82
88
  title: result.title,
83
89
  content: result.content,
84
90
  tags: result.tags,
85
91
  summary: result.summary,
86
92
  source_session: best.filePath,
93
+ ...(lang ? { language: lang } : {}),
87
94
  })
88
95
 
89
96
  UI.success(`Published! Post ID: ${post.post.id}`)
@@ -92,7 +92,7 @@ export const ChatCommand: CommandModule = {
92
92
  if (cmd === "/model") {
93
93
  if (rest) {
94
94
  currentModel = rest
95
- console.log(` ${UI.Style.TEXT_SUCCESS}Model: ${rest}${UI.Style.TEXT_NORMAL}`)
95
+ console.log(` ${UI.Style.TEXT_SUCCESS}Model: ${rest}${UI.Style.TEXT_NORMAL}`)
96
96
  } else {
97
97
  const current = AIProvider.BUILTIN_MODELS[currentModel || AIProvider.DEFAULT_MODEL]
98
98
  console.log(` ${UI.Style.TEXT_DIM}Current: ${current?.name || currentModel || AIProvider.DEFAULT_MODEL}${UI.Style.TEXT_NORMAL}`)
@@ -5,11 +5,11 @@ import { UI } from "../ui"
5
5
 
6
6
  export const ConfigCommand: CommandModule = {
7
7
  command: "config",
8
- describe: "Configure AI provider and model settings",
8
+ describe: "Configure AI provider, model, and server settings",
9
9
  builder: (yargs) =>
10
10
  yargs
11
11
  .option("provider", {
12
- describe: "AI provider: anthropic, openai, google",
12
+ describe: "AI provider: anthropic, openai, google, xai, mistral, groq, etc.",
13
13
  type: "string",
14
14
  })
15
15
  .option("api-key", {
@@ -17,16 +17,36 @@ export const ConfigCommand: CommandModule = {
17
17
  type: "string",
18
18
  })
19
19
  .option("model", {
20
- describe: "Default model ID",
20
+ alias: "m",
21
+ describe: "Set default AI model (e.g. claude-sonnet-4-20250514, gpt-4o)",
22
+ type: "string",
23
+ })
24
+ .option("url", {
25
+ describe: "Set CodeBlog server URL",
21
26
  type: "string",
22
27
  })
23
28
  .option("list", {
29
+ alias: "l",
24
30
  describe: "List available models and their status",
25
31
  type: "boolean",
26
32
  default: false,
33
+ })
34
+ .option("path", {
35
+ describe: "Show config file path",
36
+ type: "boolean",
37
+ default: false,
38
+ })
39
+ .option("language", {
40
+ describe: "Default content language for posts (e.g. English, 中文, 日本語)",
41
+ type: "string",
27
42
  }),
28
43
  handler: async (args) => {
29
44
  try {
45
+ if (args.path) {
46
+ console.log(Config.filepath)
47
+ return
48
+ }
49
+
30
50
  if (args.list) {
31
51
  const models = await AIProvider.available()
32
52
  const providers = await AIProvider.listProviders()
@@ -36,11 +56,10 @@ export const ConfigCommand: CommandModule = {
36
56
  console.log("")
37
57
 
38
58
  const configured = Object.entries(providers).filter(([, p]) => p.hasKey)
39
- const unconfigured = Object.entries(providers).filter(([, p]) => !p.hasKey)
40
59
 
41
60
  if (configured.length > 0) {
42
61
  console.log(` ${UI.Style.TEXT_SUCCESS}Configured:${UI.Style.TEXT_NORMAL}`)
43
- for (const [id, p] of configured) {
62
+ for (const [, p] of configured) {
44
63
  console.log(` ${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_NORMAL_BOLD}${p.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${p.models.length} models)${UI.Style.TEXT_NORMAL}`)
45
64
  }
46
65
  console.log("")
@@ -62,37 +81,56 @@ export const ConfigCommand: CommandModule = {
62
81
  }
63
82
 
64
83
  if (args.provider && args.apiKey) {
65
- const provider = args.provider as string
66
- const cfg = await Config.load() as Record<string, unknown>
67
- const providers = (cfg.providers || {}) as Record<string, Record<string, string>>
68
- providers[provider] = { ...providers[provider], api_key: args.apiKey as string }
69
- await Config.save({ ...cfg, providers } as unknown as Config.CodeblogConfig)
70
- UI.success(`${provider} API key saved`)
84
+ const cfg = await Config.load()
85
+ const providers = cfg.providers || {}
86
+ providers[args.provider as string] = { ...providers[args.provider as string], api_key: args.apiKey as string }
87
+ await Config.save({ providers })
88
+ UI.success(`${args.provider} API key saved`)
71
89
  return
72
90
  }
73
91
 
74
92
  if (args.model) {
75
- const model = args.model as string
76
- const cfg = await Config.load() as Record<string, unknown>
77
- await Config.save({ ...cfg, model } as unknown as Config.CodeblogConfig)
78
- UI.success(`Default model set to ${model}`)
93
+ await Config.save({ model: args.model as string })
94
+ UI.success(`Default model set to ${args.model}`)
95
+ return
96
+ }
97
+
98
+ if (args.url) {
99
+ await Config.save({ api_url: args.url as string })
100
+ UI.success(`Server URL set to ${args.url}`)
101
+ return
102
+ }
103
+
104
+ if (args.language) {
105
+ await Config.save({ default_language: args.language as string })
106
+ UI.success(`Default language set to ${args.language}`)
79
107
  return
80
108
  }
81
109
 
82
110
  // Show current config
83
- const cfg = await Config.load() as Record<string, unknown>
84
- const model = (cfg.model as string) || AIProvider.DEFAULT_MODEL
85
- const providers = (cfg.providers || {}) as Record<string, Record<string, string>>
111
+ const cfg = await Config.load()
112
+ const model = cfg.model || AIProvider.DEFAULT_MODEL
113
+ const providers = cfg.providers || {}
86
114
 
87
115
  console.log("")
88
116
  console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Current Config${UI.Style.TEXT_NORMAL}`)
117
+ console.log(` ${UI.Style.TEXT_DIM}${Config.filepath}${UI.Style.TEXT_NORMAL}`)
89
118
  console.log("")
90
119
  console.log(` Model: ${UI.Style.TEXT_HIGHLIGHT}${model}${UI.Style.TEXT_NORMAL}`)
91
120
  console.log(` API URL: ${cfg.api_url || "https://codeblog.ai"}`)
121
+ console.log(` Language: ${cfg.default_language || `${UI.Style.TEXT_DIM}(server default)${UI.Style.TEXT_NORMAL}`}`)
92
122
  console.log("")
93
- for (const [id, p] of Object.entries(providers)) {
94
- const masked = p.api_key ? p.api_key.slice(0, 8) + "..." : "not set"
95
- console.log(` ${id}: ${UI.Style.TEXT_DIM}${masked}${UI.Style.TEXT_NORMAL}`)
123
+
124
+ if (Object.keys(providers).length > 0) {
125
+ console.log(` ${UI.Style.TEXT_NORMAL_BOLD}AI Providers${UI.Style.TEXT_NORMAL}`)
126
+ for (const [id, p] of Object.entries(providers)) {
127
+ 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}`)
129
+ }
130
+ } else {
131
+ console.log(` ${UI.Style.TEXT_DIM}No AI providers configured.${UI.Style.TEXT_NORMAL}`)
132
+ 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}`)
96
134
  }
97
135
  console.log("")
98
136
  } catch (err) {
@@ -0,0 +1,63 @@
1
+ import type { CommandModule } from "yargs"
2
+ import { Posts } from "../../api/posts"
3
+ import { UI } from "../ui"
4
+
5
+ export const ExploreCommand: CommandModule = {
6
+ command: "explore",
7
+ describe: "Browse and engage with recent posts — like scrolling your tech feed",
8
+ builder: (yargs) =>
9
+ yargs
10
+ .option("limit", {
11
+ alias: "l",
12
+ describe: "Number of posts to show",
13
+ type: "number",
14
+ default: 5,
15
+ })
16
+ .option("engage", {
17
+ alias: "e",
18
+ describe: "Show full content for engagement (comments/votes)",
19
+ type: "boolean",
20
+ default: false,
21
+ }),
22
+ handler: async (args) => {
23
+ try {
24
+ const result = await Posts.list({ limit: args.limit as number })
25
+ const posts = result.posts || []
26
+
27
+ if (posts.length === 0) {
28
+ UI.info("No posts on CodeBlog yet. Be the first with: codeblog ai-publish")
29
+ return
30
+ }
31
+
32
+ console.log("")
33
+ console.log(` ${UI.Style.TEXT_NORMAL_BOLD}CodeBlog Feed${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${posts.length} posts)${UI.Style.TEXT_NORMAL}`)
34
+ console.log("")
35
+
36
+ for (const p of posts) {
37
+ const score = (p.upvotes || 0) - (p.downvotes || 0)
38
+ const tags = Array.isArray(p.tags) ? p.tags : []
39
+ console.log(` ${score >= 0 ? "+" : ""}${score} ${UI.Style.TEXT_HIGHLIGHT}${p.title}${UI.Style.TEXT_NORMAL}`)
40
+ console.log(` ${UI.Style.TEXT_DIM}💬${p.comment_count || 0} by ${(p as any).agent?.name || (p as any).author?.name || "anon"}${UI.Style.TEXT_NORMAL}`)
41
+ if (tags.length > 0) console.log(` ${UI.Style.TEXT_DIM}${tags.map((t: string) => `#${t}`).join(" ")}${UI.Style.TEXT_NORMAL}`)
42
+ console.log(` ${UI.Style.TEXT_DIM}ID: ${p.id}${UI.Style.TEXT_NORMAL}`)
43
+ console.log("")
44
+ }
45
+
46
+ if (args.engage) {
47
+ console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Engage with posts:${UI.Style.TEXT_NORMAL}`)
48
+ console.log(` codeblog post <id> ${UI.Style.TEXT_DIM}— Read full post${UI.Style.TEXT_NORMAL}`)
49
+ console.log(` codeblog vote <id> ${UI.Style.TEXT_DIM}— Upvote${UI.Style.TEXT_NORMAL}`)
50
+ console.log(` codeblog vote <id> --down ${UI.Style.TEXT_DIM}— Downvote${UI.Style.TEXT_NORMAL}`)
51
+ console.log(` codeblog comment <id> "text" ${UI.Style.TEXT_DIM}— Comment${UI.Style.TEXT_NORMAL}`)
52
+ console.log(` codeblog bookmark <id> ${UI.Style.TEXT_DIM}— Bookmark${UI.Style.TEXT_NORMAL}`)
53
+ console.log("")
54
+ } else {
55
+ console.log(` ${UI.Style.TEXT_DIM}Use --engage to see interaction commands${UI.Style.TEXT_NORMAL}`)
56
+ console.log("")
57
+ }
58
+ } catch (err) {
59
+ UI.error(`Explore failed: ${err instanceof Error ? err.message : String(err)}`)
60
+ process.exitCode = 1
61
+ }
62
+ },
63
+ }
@@ -46,9 +46,10 @@ export const FeedCommand: CommandModule = {
46
46
  const comments = `${UI.Style.TEXT_DIM}💬 ${post.comment_count}${UI.Style.TEXT_NORMAL}`
47
47
  const tags = post.tags.slice(0, 4).map((t) => `${UI.Style.TEXT_INFO}#${t}${UI.Style.TEXT_NORMAL}`).join(" ")
48
48
  const author = `${UI.Style.TEXT_DIM}${post.author.name}${UI.Style.TEXT_NORMAL}`
49
+ const lang = post.language && post.language !== "English" ? ` ${UI.Style.TEXT_INFO}[${post.language}]${UI.Style.TEXT_NORMAL}` : ""
49
50
  const date = new Date(post.created_at).toLocaleDateString()
50
51
 
51
- console.log(` ${votes} ${UI.Style.TEXT_NORMAL_BOLD}${post.title}${UI.Style.TEXT_NORMAL}`)
52
+ console.log(` ${votes} ${UI.Style.TEXT_NORMAL_BOLD}${post.title}${UI.Style.TEXT_NORMAL}${lang}`)
52
53
  if (post.summary) {
53
54
  console.log(` ${UI.Style.TEXT_DIM}${post.summary.slice(0, 100)}${UI.Style.TEXT_NORMAL}`)
54
55
  }
@@ -48,6 +48,9 @@ export const PostCommand: CommandModule = {
48
48
  if (post.category) {
49
49
  console.log(` ${UI.Style.TEXT_DIM}${post.category.emoji} ${post.category.name}${UI.Style.TEXT_NORMAL}`)
50
50
  }
51
+ if (post.language && post.language !== "English") {
52
+ console.log(` ${UI.Style.TEXT_INFO}🌐 ${post.language}${UI.Style.TEXT_NORMAL}`)
53
+ }
51
54
  const scoreColor = score > 0 ? UI.Style.TEXT_SUCCESS : score < 0 ? UI.Style.TEXT_DANGER : UI.Style.TEXT_DIM
52
55
  console.log(` ${scoreColor}${score > 0 ? "+" : ""}${score} votes${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}💬 ${post.comment_count} 👁 ${post.views}${UI.Style.TEXT_NORMAL}`)
53
56
  if (post.tags.length > 0) {
@@ -16,6 +16,10 @@ export const PublishCommand: CommandModule = {
16
16
  describe: "Preview without publishing",
17
17
  type: "boolean",
18
18
  default: false,
19
+ })
20
+ .option("language", {
21
+ describe: "Content language tag (e.g. English, 中文, 日本語)",
22
+ type: "string",
19
23
  }),
20
24
  handler: async (args) => {
21
25
  UI.info("Scanning IDE sessions...")
@@ -23,6 +27,7 @@ export const PublishCommand: CommandModule = {
23
27
  const results = await Publisher.scanAndPublish({
24
28
  limit: args.limit as number,
25
29
  dryRun: args.dryRun as boolean,
30
+ language: args.language as string | undefined,
26
31
  })
27
32
 
28
33
  if (results.length === 0) {
@@ -26,7 +26,7 @@ export const TrendingCommand: CommandModule = {
26
26
 
27
27
  // Most commented
28
28
  if (trending.top_commented.length > 0) {
29
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD} Most Discussed (7d)${UI.Style.TEXT_NORMAL}`)
29
+ console.log(` ${UI.Style.TEXT_NORMAL_BOLD}💬 Most Discussed (7d)${UI.Style.TEXT_NORMAL}`)
30
30
  console.log("")
31
31
  for (const [i, post] of trending.top_commented.slice(0, 5).entries()) {
32
32
  const rank = `${UI.Style.TEXT_INFO}${i + 1}.${UI.Style.TEXT_NORMAL}`
@@ -1,6 +1,7 @@
1
1
  import type { CommandModule } from "yargs"
2
2
  import { scanAll, parseSession, registerAllScanners, analyzeSession } from "../../scanner"
3
3
  import { Posts } from "../../api/posts"
4
+ import { Config } from "../../config"
4
5
  import { UI } from "../ui"
5
6
 
6
7
  export const WeeklyDigestCommand: CommandModule = {
@@ -18,6 +19,10 @@ export const WeeklyDigestCommand: CommandModule = {
18
19
  describe: "Preview without posting (default)",
19
20
  type: "boolean",
20
21
  default: true,
22
+ })
23
+ .option("language", {
24
+ describe: "Content language tag (e.g. English, 中文, 日本語)",
25
+ type: "string",
21
26
  }),
22
27
  handler: async (args) => {
23
28
  try {
@@ -91,12 +96,14 @@ export const WeeklyDigestCommand: CommandModule = {
91
96
 
92
97
  if (args.post && !args.dryRun) {
93
98
  UI.info("Publishing digest to CodeBlog...")
99
+ const lang = (args.language as string) || await Config.language()
94
100
  const post = await Posts.create({
95
101
  title: title.slice(0, 80),
96
102
  content: digest,
97
103
  tags: [...tags].slice(0, 8),
98
104
  summary: `${recent.length} sessions, ${projectArr.length} projects, ${langArr.length} languages this week`,
99
105
  source_session: recent[0].filePath,
106
+ ...(lang ? { language: lang } : {}),
100
107
  })
101
108
  UI.success(`Published! Post ID: ${post.post.id}`)
102
109
  } else {
@@ -4,16 +4,26 @@ import { Global } from "../global"
4
4
  const CONFIG_FILE = path.join(Global.Path.config, "config.json")
5
5
 
6
6
  export namespace Config {
7
+ export interface ProviderConfig {
8
+ api_key: string
9
+ base_url?: string
10
+ }
11
+
7
12
  export interface CodeblogConfig {
8
13
  api_url: string
9
14
  api_key?: string
10
15
  token?: string
16
+ model?: string
17
+ default_language?: string
18
+ providers?: Record<string, ProviderConfig>
11
19
  }
12
20
 
13
21
  const defaults: CodeblogConfig = {
14
22
  api_url: "https://codeblog.ai",
15
23
  }
16
24
 
25
+ export const filepath = CONFIG_FILE
26
+
17
27
  export async function load(): Promise<CodeblogConfig> {
18
28
  const file = Bun.file(CONFIG_FILE)
19
29
  const data = await file.json().catch(() => ({}))
@@ -23,7 +33,7 @@ export namespace Config {
23
33
  export async function save(config: Partial<CodeblogConfig>) {
24
34
  const current = await load()
25
35
  const merged = { ...current, ...config }
26
- await Bun.write(CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 0o600 })
36
+ await Bun.write(Bun.file(CONFIG_FILE, { mode: 0o600 }), JSON.stringify(merged, null, 2))
27
37
  }
28
38
 
29
39
  export async function url() {
@@ -37,4 +47,8 @@ export namespace Config {
37
47
  export async function token() {
38
48
  return process.env.CODEBLOG_TOKEN || (await load()).token || ""
39
49
  }
50
+
51
+ export async function language() {
52
+ return process.env.CODEBLOG_LANGUAGE || (await load()).default_language
53
+ }
40
54
  }
@@ -5,10 +5,11 @@ import os from "os"
5
5
 
6
6
  const app = "codeblog"
7
7
 
8
- const data = path.join(xdgData!, app)
9
- const cache = path.join(xdgCache!, app)
10
- const config = path.join(xdgConfig!, app)
11
- const state = path.join(xdgState!, app)
8
+ const home = process.env.CODEBLOG_TEST_HOME || os.homedir()
9
+ const data = path.join(xdgData || path.join(home, ".local", "share"), app)
10
+ const cache = path.join(xdgCache || path.join(home, ".cache"), app)
11
+ const config = path.join(xdgConfig || path.join(home, ".config"), app)
12
+ const state = path.join(xdgState || path.join(home, ".local", "state"), app)
12
13
 
13
14
  export namespace Global {
14
15
  export const Path = {
package/src/index.ts CHANGED
@@ -33,8 +33,9 @@ import { AIPublishCommand } from "./cli/cmd/ai-publish"
33
33
  import { TuiCommand } from "./cli/cmd/tui"
34
34
  import { WeeklyDigestCommand } from "./cli/cmd/weekly-digest"
35
35
  import { TagsCommand } from "./cli/cmd/tags"
36
+ import { ExploreCommand } from "./cli/cmd/explore"
36
37
 
37
- const VERSION = "0.4.2"
38
+ const VERSION = "0.4.3"
38
39
 
39
40
  process.on("unhandledRejection", (e) => {
40
41
  Log.Default.error("rejection", {
@@ -106,6 +107,7 @@ const cli = yargs(hideBin(process.argv))
106
107
  .command(ConfigCommand)
107
108
  // Browse
108
109
  .command(TagsCommand)
110
+ .command(ExploreCommand)
109
111
  // TUI
110
112
  .command(TuiCommand)
111
113
  // Account
@@ -1,5 +1,6 @@
1
1
  import { scanAll, parseSession, analyzeSession, registerAllScanners } from "../scanner"
2
2
  import { Posts } from "../api/posts"
3
+ import { Config } from "../config"
3
4
  import { Database } from "../storage/db"
4
5
  import { published_sessions } from "../storage/schema.sql"
5
6
  import { eq } from "drizzle-orm"
@@ -9,7 +10,7 @@ import type { Session } from "../scanner/types"
9
10
  const log = Log.create({ service: "publisher" })
10
11
 
11
12
  export namespace Publisher {
12
- export async function scanAndPublish(options: { limit?: number; dryRun?: boolean } = {}) {
13
+ export async function scanAndPublish(options: { limit?: number; dryRun?: boolean; language?: string } = {}) {
13
14
  registerAllScanners()
14
15
  const limit = options.limit || 10
15
16
  const sessions = scanAll(limit)
@@ -46,10 +47,12 @@ export namespace Publisher {
46
47
  }
47
48
 
48
49
  const content = formatPost(analysis)
50
+ const lang = options.language || await Config.language()
49
51
  const result = await Posts.create({
50
52
  title: analysis.suggestedTitle,
51
53
  content,
52
54
  tags: analysis.suggestedTags,
55
+ ...(lang ? { language: lang } : {}),
53
56
  })
54
57
 
55
58
  await markPublished(session, result.post.id)
package/src/tui/app.tsx CHANGED
@@ -123,7 +123,7 @@ function App() {
123
123
  {loggedIn() ? "● " : "○ "}
124
124
  </text>
125
125
  <text fg="#6a737c">{loggedIn() ? "logged in" : "not logged in"}</text>
126
- <text fg="#6a737c">{" codeblog v0.4.2"}</text>
126
+ <text fg="#6a737c">{" codeblog v0.4.3"}</text>
127
127
  </box>
128
128
  </box>
129
129
  )
@@ -12,7 +12,14 @@ export function Chat() {
12
12
  const [messages, setMessages] = createSignal<Message[]>([])
13
13
  const [streaming, setStreaming] = createSignal(false)
14
14
  const [streamText, setStreamText] = createSignal("")
15
- const [model, setModel] = createSignal("claude-sonnet-4-20250514")
15
+ const [model, setModel] = createSignal("")
16
+
17
+ // Load configured model on mount
18
+ import("../../config").then(({ Config }) =>
19
+ Config.load().then((cfg) => {
20
+ if (cfg.model) setModel(cfg.model)
21
+ }).catch(() => {}),
22
+ )
16
23
  const [inputBuf, setInputBuf] = createSignal("")
17
24
  const [inputMode, setInputMode] = createSignal(true)
18
25
 
@@ -60,7 +60,7 @@ export function Post() {
60
60
  <box paddingLeft={2} paddingTop={0} flexShrink={0} flexDirection="row" gap={2}>
61
61
  <text fg="#48a868">{`▲${(post()?.upvotes ?? 0) - (post()?.downvotes ?? 0)}`}</text>
62
62
  <text fg="#6a737c">{`💬${post()?.comment_count ?? 0} 👁${post()?.views ?? 0}`}</text>
63
- <text fg="#838c95">{`by ${post()?.agent ?? "anon"}`}</text>
63
+ <text fg="#838c95">{`by ${post()?.agent?.name || "anon"}`}</text>
64
64
  </box>
65
65
 
66
66
  {/* Tags */}
@@ -90,9 +90,9 @@ export function Post() {
90
90
  <box flexDirection="column" paddingTop={1}>
91
91
  <box flexDirection="row" gap={1}>
92
92
  <text fg="#0074cc">
93
- <span style={{ bold: true }}>{comment.agent || comment.user || "anon"}</span>
93
+ <span style={{ bold: true }}>{comment.user?.username || comment.agent || "anon"}</span>
94
94
  </text>
95
- <text fg="#6a737c">{comment.created_at || ""}</text>
95
+ <text fg="#6a737c">{comment.createdAt || comment.created_at || ""}</text>
96
96
  </box>
97
97
  <box paddingLeft={2}>
98
98
  <text fg="#c9d1d9">{comment.content || comment.body || ""}</text>