codeblog-app 0.4.2 → 0.4.3
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 +4 -5
- package/src/cli/cmd/config.ts +48 -21
- package/src/cli/cmd/explore.ts +63 -0
- package/src/config/index.ts +9 -0
- package/src/index.ts +3 -1
- package/src/tui/app.tsx +1 -1
package/package.json
CHANGED
package/src/ai/provider.ts
CHANGED
|
@@ -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()
|
|
177
|
-
|
|
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()
|
|
290
|
-
return cfg.model
|
|
288
|
+
const cfg = await Config.load()
|
|
289
|
+
return cfg.model
|
|
291
290
|
}
|
|
292
291
|
|
|
293
292
|
// ---------------------------------------------------------------------------
|
package/src/cli/cmd/config.ts
CHANGED
|
@@ -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
|
|
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,32 @@ export const ConfigCommand: CommandModule = {
|
|
|
17
17
|
type: "string",
|
|
18
18
|
})
|
|
19
19
|
.option("model", {
|
|
20
|
-
|
|
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,
|
|
27
38
|
}),
|
|
28
39
|
handler: async (args) => {
|
|
29
40
|
try {
|
|
41
|
+
if (args.path) {
|
|
42
|
+
console.log(Config.filepath)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
if (args.list) {
|
|
31
47
|
const models = await AIProvider.available()
|
|
32
48
|
const providers = await AIProvider.listProviders()
|
|
@@ -36,11 +52,10 @@ export const ConfigCommand: CommandModule = {
|
|
|
36
52
|
console.log("")
|
|
37
53
|
|
|
38
54
|
const configured = Object.entries(providers).filter(([, p]) => p.hasKey)
|
|
39
|
-
const unconfigured = Object.entries(providers).filter(([, p]) => !p.hasKey)
|
|
40
55
|
|
|
41
56
|
if (configured.length > 0) {
|
|
42
57
|
console.log(` ${UI.Style.TEXT_SUCCESS}Configured:${UI.Style.TEXT_NORMAL}`)
|
|
43
|
-
for (const [
|
|
58
|
+
for (const [, p] of configured) {
|
|
44
59
|
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
60
|
}
|
|
46
61
|
console.log("")
|
|
@@ -62,37 +77,49 @@ export const ConfigCommand: CommandModule = {
|
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
if (args.provider && args.apiKey) {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
UI.success(`${provider} API key saved`)
|
|
80
|
+
const cfg = await Config.load()
|
|
81
|
+
const providers = cfg.providers || {}
|
|
82
|
+
providers[args.provider as string] = { ...providers[args.provider as string], api_key: args.apiKey as string }
|
|
83
|
+
await Config.save({ providers })
|
|
84
|
+
UI.success(`${args.provider} API key saved`)
|
|
71
85
|
return
|
|
72
86
|
}
|
|
73
87
|
|
|
74
88
|
if (args.model) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
await Config.save({ model: args.model as string })
|
|
90
|
+
UI.success(`Default model set to ${args.model}`)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (args.url) {
|
|
95
|
+
await Config.save({ api_url: args.url as string })
|
|
96
|
+
UI.success(`Server URL set to ${args.url}`)
|
|
79
97
|
return
|
|
80
98
|
}
|
|
81
99
|
|
|
82
100
|
// Show current config
|
|
83
|
-
const cfg = await Config.load()
|
|
84
|
-
const model =
|
|
85
|
-
const providers =
|
|
101
|
+
const cfg = await Config.load()
|
|
102
|
+
const model = cfg.model || AIProvider.DEFAULT_MODEL
|
|
103
|
+
const providers = cfg.providers || {}
|
|
86
104
|
|
|
87
105
|
console.log("")
|
|
88
106
|
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Current Config${UI.Style.TEXT_NORMAL}`)
|
|
107
|
+
console.log(` ${UI.Style.TEXT_DIM}${Config.filepath}${UI.Style.TEXT_NORMAL}`)
|
|
89
108
|
console.log("")
|
|
90
109
|
console.log(` Model: ${UI.Style.TEXT_HIGHLIGHT}${model}${UI.Style.TEXT_NORMAL}`)
|
|
91
110
|
console.log(` API URL: ${cfg.api_url || "https://codeblog.ai"}`)
|
|
92
111
|
console.log("")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.log(` ${
|
|
112
|
+
|
|
113
|
+
if (Object.keys(providers).length > 0) {
|
|
114
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}AI Providers${UI.Style.TEXT_NORMAL}`)
|
|
115
|
+
for (const [id, p] of Object.entries(providers)) {
|
|
116
|
+
const masked = p.api_key ? p.api_key.slice(0, 8) + "..." : "not set"
|
|
117
|
+
console.log(` ${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL} ${id}: ${UI.Style.TEXT_DIM}${masked}${UI.Style.TEXT_NORMAL}`)
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
console.log(` ${UI.Style.TEXT_DIM}No AI providers configured.${UI.Style.TEXT_NORMAL}`)
|
|
121
|
+
console.log(` ${UI.Style.TEXT_DIM}Set one: codeblog config --provider anthropic --api-key sk-...${UI.Style.TEXT_NORMAL}`)
|
|
122
|
+
console.log(` ${UI.Style.TEXT_DIM}Or use env: ANTHROPIC_API_KEY=sk-...${UI.Style.TEXT_NORMAL}`)
|
|
96
123
|
}
|
|
97
124
|
console.log("")
|
|
98
125
|
} 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
|
+
}
|
package/src/config/index.ts
CHANGED
|
@@ -4,16 +4,25 @@ 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
|
+
providers?: Record<string, ProviderConfig>
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
const defaults: CodeblogConfig = {
|
|
14
21
|
api_url: "https://codeblog.ai",
|
|
15
22
|
}
|
|
16
23
|
|
|
24
|
+
export const filepath = CONFIG_FILE
|
|
25
|
+
|
|
17
26
|
export async function load(): Promise<CodeblogConfig> {
|
|
18
27
|
const file = Bun.file(CONFIG_FILE)
|
|
19
28
|
const data = await file.json().catch(() => ({}))
|
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.
|
|
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
|
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.
|
|
126
|
+
<text fg="#6a737c">{" codeblog v0.4.3"}</text>
|
|
127
127
|
</box>
|
|
128
128
|
</box>
|
|
129
129
|
)
|