codeblog-app 2.2.4 → 2.3.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.
Files changed (69) hide show
  1. package/package.json +8 -71
  2. package/drizzle/0000_init.sql +0 -34
  3. package/drizzle/meta/_journal.json +0 -13
  4. package/drizzle.config.ts +0 -10
  5. package/src/ai/__tests__/chat.test.ts +0 -179
  6. package/src/ai/__tests__/provider.test.ts +0 -198
  7. package/src/ai/__tests__/tools.test.ts +0 -93
  8. package/src/ai/chat.ts +0 -224
  9. package/src/ai/configure.ts +0 -134
  10. package/src/ai/provider.ts +0 -302
  11. package/src/ai/tools.ts +0 -114
  12. package/src/auth/index.ts +0 -47
  13. package/src/auth/oauth.ts +0 -94
  14. package/src/cli/__tests__/commands.test.ts +0 -225
  15. package/src/cli/cmd/agent.ts +0 -97
  16. package/src/cli/cmd/chat.ts +0 -190
  17. package/src/cli/cmd/comment.ts +0 -67
  18. package/src/cli/cmd/config.ts +0 -153
  19. package/src/cli/cmd/feed.ts +0 -53
  20. package/src/cli/cmd/forum.ts +0 -106
  21. package/src/cli/cmd/login.ts +0 -45
  22. package/src/cli/cmd/logout.ts +0 -12
  23. package/src/cli/cmd/me.ts +0 -188
  24. package/src/cli/cmd/post.ts +0 -25
  25. package/src/cli/cmd/publish.ts +0 -64
  26. package/src/cli/cmd/scan.ts +0 -78
  27. package/src/cli/cmd/search.ts +0 -35
  28. package/src/cli/cmd/setup.ts +0 -352
  29. package/src/cli/cmd/tui.ts +0 -20
  30. package/src/cli/cmd/uninstall.ts +0 -281
  31. package/src/cli/cmd/update.ts +0 -123
  32. package/src/cli/cmd/vote.ts +0 -50
  33. package/src/cli/cmd/whoami.ts +0 -18
  34. package/src/cli/mcp-print.ts +0 -6
  35. package/src/cli/ui.ts +0 -250
  36. package/src/config/index.ts +0 -54
  37. package/src/flag/index.ts +0 -23
  38. package/src/global/index.ts +0 -38
  39. package/src/id/index.ts +0 -20
  40. package/src/index.ts +0 -200
  41. package/src/mcp/__tests__/client.test.ts +0 -149
  42. package/src/mcp/__tests__/e2e.ts +0 -327
  43. package/src/mcp/__tests__/integration.ts +0 -148
  44. package/src/mcp/client.ts +0 -148
  45. package/src/server/index.ts +0 -48
  46. package/src/storage/chat.ts +0 -71
  47. package/src/storage/db.ts +0 -85
  48. package/src/storage/schema.sql.ts +0 -39
  49. package/src/storage/schema.ts +0 -1
  50. package/src/tui/app.tsx +0 -165
  51. package/src/tui/commands.ts +0 -186
  52. package/src/tui/context/exit.tsx +0 -15
  53. package/src/tui/context/helper.tsx +0 -25
  54. package/src/tui/context/route.tsx +0 -24
  55. package/src/tui/context/theme.tsx +0 -470
  56. package/src/tui/routes/home.tsx +0 -660
  57. package/src/tui/routes/model.tsx +0 -210
  58. package/src/tui/routes/notifications.tsx +0 -87
  59. package/src/tui/routes/post.tsx +0 -102
  60. package/src/tui/routes/search.tsx +0 -105
  61. package/src/tui/routes/setup.tsx +0 -255
  62. package/src/tui/routes/trending.tsx +0 -107
  63. package/src/util/__tests__/context.test.ts +0 -31
  64. package/src/util/__tests__/lazy.test.ts +0 -37
  65. package/src/util/context.ts +0 -23
  66. package/src/util/error.ts +0 -46
  67. package/src/util/lazy.ts +0 -18
  68. package/src/util/log.ts +0 -142
  69. package/tsconfig.json +0 -11
@@ -1,190 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { AIChat } from "../../ai/chat"
3
- import { AIProvider } from "../../ai/provider"
4
- import { UI } from "../ui"
5
- import readline from "readline"
6
-
7
- export const ChatCommand: CommandModule = {
8
- command: "chat",
9
- aliases: ["c"],
10
- describe: "Interactive AI chat — write posts, analyze code, browse the forum",
11
- builder: (yargs) =>
12
- yargs
13
- .option("model", {
14
- alias: "m",
15
- describe: "Model to use (e.g. claude-sonnet-4-20250514, gpt-4o)",
16
- type: "string",
17
- })
18
- .option("prompt", {
19
- alias: "p",
20
- describe: "Single prompt (non-interactive mode)",
21
- type: "string",
22
- }),
23
- handler: async (args) => {
24
- const modelID = args.model as string | undefined
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
-
45
- // Non-interactive: single prompt
46
- if (args.prompt) {
47
- try {
48
- await AIChat.stream(
49
- [{ role: "user", content: args.prompt as string }],
50
- {
51
- onToken: (token) => process.stdout.write(token),
52
- onFinish: () => process.stdout.write("\n"),
53
- onError: (err) => UI.error(err.message),
54
- },
55
- modelID,
56
- )
57
- } catch (err) {
58
- UI.error(err instanceof Error ? err.message : String(err))
59
- process.exitCode = 1
60
- }
61
- return
62
- }
63
-
64
- // Interactive REPL
65
- const modelInfo = AIProvider.BUILTIN_MODELS[modelID || AIProvider.DEFAULT_MODEL]
66
- const modelName = modelInfo?.name || modelID || AIProvider.DEFAULT_MODEL
67
-
68
- console.log("")
69
- console.log(` ${UI.Style.TEXT_HIGHLIGHT_BOLD}CodeBlog AI Chat${UI.Style.TEXT_NORMAL}`)
70
- console.log(` ${UI.Style.TEXT_DIM}Model: ${modelName}${UI.Style.TEXT_NORMAL}`)
71
- console.log(` ${UI.Style.TEXT_DIM}Type your message. Commands: /help /model /clear /exit${UI.Style.TEXT_NORMAL}`)
72
- console.log("")
73
-
74
- const messages: AIChat.Message[] = []
75
- const rl = readline.createInterface({
76
- input: process.stdin,
77
- output: process.stdout,
78
- prompt: `${UI.Style.TEXT_HIGHLIGHT}❯ ${UI.Style.TEXT_NORMAL}`,
79
- })
80
-
81
- let currentModel = modelID
82
-
83
- rl.prompt()
84
-
85
- rl.on("line", async (line) => {
86
- const input = line.trim()
87
- if (!input) {
88
- rl.prompt()
89
- return
90
- }
91
-
92
- // Handle commands
93
- if (input.startsWith("/")) {
94
- const cmd = input.split(" ")[0]!
95
- const rest = input.slice(cmd.length).trim()
96
-
97
- if (cmd === "/exit" || cmd === "/quit" || cmd === "/q") {
98
- console.log("")
99
- UI.info("Bye!")
100
- rl.close()
101
- return
102
- }
103
-
104
- if (cmd === "/clear") {
105
- messages.length = 0
106
- console.log(` ${UI.Style.TEXT_DIM}Chat history cleared${UI.Style.TEXT_NORMAL}`)
107
- rl.prompt()
108
- return
109
- }
110
-
111
- if (cmd === "/model") {
112
- if (rest) {
113
- currentModel = rest
114
- console.log(` ${UI.Style.TEXT_SUCCESS}Model: ${rest}${UI.Style.TEXT_NORMAL}`)
115
- } else {
116
- const current = AIProvider.BUILTIN_MODELS[currentModel || AIProvider.DEFAULT_MODEL]
117
- console.log(` ${UI.Style.TEXT_DIM}Current: ${current?.name || currentModel || AIProvider.DEFAULT_MODEL}${UI.Style.TEXT_NORMAL}`)
118
- console.log(` ${UI.Style.TEXT_DIM}Built-in: ${Object.keys(AIProvider.BUILTIN_MODELS).join(", ")}${UI.Style.TEXT_NORMAL}`)
119
- console.log(` ${UI.Style.TEXT_DIM}Any model from models.dev works too (e.g. anthropic/claude-sonnet-4-20250514)${UI.Style.TEXT_NORMAL}`)
120
- }
121
- rl.prompt()
122
- return
123
- }
124
-
125
- if (cmd === "/help") {
126
- console.log("")
127
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Commands${UI.Style.TEXT_NORMAL}`)
128
- console.log(` ${UI.Style.TEXT_DIM}/model [id]${UI.Style.TEXT_NORMAL} Switch or show model`)
129
- console.log(` ${UI.Style.TEXT_DIM}/clear${UI.Style.TEXT_NORMAL} Clear chat history`)
130
- console.log(` ${UI.Style.TEXT_DIM}/exit${UI.Style.TEXT_NORMAL} Exit chat`)
131
- console.log("")
132
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Tips${UI.Style.TEXT_NORMAL}`)
133
- console.log(` ${UI.Style.TEXT_DIM}Ask me to write a blog post, analyze code, draft comments,${UI.Style.TEXT_NORMAL}`)
134
- console.log(` ${UI.Style.TEXT_DIM}summarize discussions, or generate tags and titles.${UI.Style.TEXT_NORMAL}`)
135
- console.log("")
136
- rl.prompt()
137
- return
138
- }
139
-
140
- console.log(` ${UI.Style.TEXT_DIM}Unknown command: ${cmd}. Type /help${UI.Style.TEXT_NORMAL}`)
141
- rl.prompt()
142
- return
143
- }
144
-
145
- // Send message to AI
146
- messages.push({ role: "user", content: input })
147
-
148
- console.log("")
149
- process.stdout.write(` ${UI.Style.TEXT_INFO}`)
150
-
151
- try {
152
- let response = ""
153
- await AIChat.stream(
154
- messages,
155
- {
156
- onToken: (token) => {
157
- process.stdout.write(token)
158
- response += token
159
- },
160
- onFinish: () => {
161
- process.stdout.write(UI.Style.TEXT_NORMAL)
162
- console.log("")
163
- console.log("")
164
- },
165
- onError: (err) => {
166
- process.stdout.write(UI.Style.TEXT_NORMAL)
167
- console.log("")
168
- UI.error(err.message)
169
- },
170
- },
171
- currentModel,
172
- )
173
- messages.push({ role: "assistant", content: response })
174
- } catch (err) {
175
- process.stdout.write(UI.Style.TEXT_NORMAL)
176
- console.log("")
177
- UI.error(err instanceof Error ? err.message : String(err))
178
- }
179
-
180
- rl.prompt()
181
- })
182
-
183
- rl.on("close", () => {
184
- process.exit(0)
185
- })
186
-
187
- // Keep process alive
188
- await new Promise(() => {})
189
- },
190
- }
@@ -1,67 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { mcpPrint } from "../mcp-print"
3
- import { UI } from "../ui"
4
-
5
- export const CommentCommand: CommandModule = {
6
- command: "comment <post_id>",
7
- describe: "Comment on a post",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("post_id", {
11
- describe: "Post ID to comment on",
12
- type: "string",
13
- demandOption: true,
14
- })
15
- .option("reply", {
16
- alias: "r",
17
- describe: "Reply to a specific comment by its ID",
18
- type: "string",
19
- }),
20
- handler: async (args) => {
21
- const postId = args.post_id as string
22
-
23
- console.log("")
24
- console.log(` ${UI.Style.TEXT_DIM}Write your comment (end with an empty line):${UI.Style.TEXT_NORMAL}`)
25
- console.log("")
26
-
27
- // Read multiline input
28
- const lines: string[] = []
29
- const readline = require("readline")
30
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
31
-
32
- const content = await new Promise<string>((resolve) => {
33
- rl.on("line", (line: string) => {
34
- if (line === "" && lines.length > 0) {
35
- rl.close()
36
- resolve(lines.join("\n"))
37
- } else {
38
- lines.push(line)
39
- }
40
- })
41
- rl.on("close", () => {
42
- resolve(lines.join("\n"))
43
- })
44
- rl.prompt()
45
- })
46
-
47
- if (!content.trim()) {
48
- UI.warn("Empty comment, skipped.")
49
- return
50
- }
51
-
52
- try {
53
- const mcpArgs: Record<string, unknown> = {
54
- post_id: postId,
55
- content: content.trim(),
56
- }
57
- if (args.reply) mcpArgs.parent_id = args.reply
58
-
59
- console.log("")
60
- await mcpPrint("comment_on_post", mcpArgs)
61
- console.log("")
62
- } catch (err) {
63
- UI.error(`Comment failed: ${err instanceof Error ? err.message : String(err)}`)
64
- process.exitCode = 1
65
- }
66
- },
67
- }
@@ -1,153 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Config } from "../../config"
3
- import { AIProvider } from "../../ai/provider"
4
- import { UI } from "../ui"
5
-
6
- export const ConfigCommand: CommandModule = {
7
- command: "config",
8
- describe: "Configure AI provider, model, and server settings",
9
- builder: (yargs) =>
10
- yargs
11
- .option("provider", {
12
- describe: "AI provider: anthropic, openai, google, xai, mistral, groq, etc.",
13
- type: "string",
14
- })
15
- .option("api-key", {
16
- describe: "API key for the provider",
17
- type: "string",
18
- })
19
- .option("model", {
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",
26
- type: "string",
27
- })
28
- .option("list", {
29
- alias: "l",
30
- describe: "List available models and their status",
31
- type: "boolean",
32
- default: false,
33
- })
34
- .option("path", {
35
- describe: "Show config file path",
36
- type: "boolean",
37
- default: false,
38
- })
39
- .option("base-url", {
40
- describe: "Custom base URL for the provider (for third-party API proxies)",
41
- type: "string",
42
- })
43
- .option("language", {
44
- describe: "Default content language for posts (e.g. English, 中文, 日本語)",
45
- type: "string",
46
- }),
47
- handler: async (args) => {
48
- try {
49
- if (args.path) {
50
- console.log(Config.filepath)
51
- return
52
- }
53
-
54
- if (args.list) {
55
- const models = await AIProvider.available()
56
- const providers = await AIProvider.listProviders()
57
-
58
- console.log("")
59
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Providers${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${Object.keys(providers).length} from models.dev)${UI.Style.TEXT_NORMAL}`)
60
- console.log("")
61
-
62
- const configured = Object.entries(providers).filter(([, p]) => p.hasKey)
63
-
64
- if (configured.length > 0) {
65
- console.log(` ${UI.Style.TEXT_SUCCESS}Configured:${UI.Style.TEXT_NORMAL}`)
66
- for (const [, p] of configured) {
67
- 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}`)
68
- }
69
- console.log("")
70
- }
71
-
72
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Built-in Models${UI.Style.TEXT_NORMAL}`)
73
- console.log("")
74
- for (const { model, hasKey } of models) {
75
- const status = hasKey ? `${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL}` : `${UI.Style.TEXT_DIM}✗${UI.Style.TEXT_NORMAL}`
76
- console.log(` ${status} ${UI.Style.TEXT_NORMAL_BOLD}${model.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${model.id})${UI.Style.TEXT_NORMAL}`)
77
- console.log(` ${UI.Style.TEXT_DIM}${model.providerID} · ${(model.contextWindow / 1000).toFixed(0)}k context${UI.Style.TEXT_NORMAL}`)
78
- }
79
- console.log("")
80
- console.log(` ${UI.Style.TEXT_DIM}✓ = API key configured, ✗ = needs key${UI.Style.TEXT_NORMAL}`)
81
- console.log(` ${UI.Style.TEXT_DIM}Set key: codeblog config --provider anthropic --api-key sk-...${UI.Style.TEXT_NORMAL}`)
82
- console.log(` ${UI.Style.TEXT_DIM}Any model from models.dev can be used with provider/model format${UI.Style.TEXT_NORMAL}`)
83
- console.log("")
84
- return
85
- }
86
-
87
- if (args.provider && (args.apiKey || args.baseUrl)) {
88
- const cfg = await Config.load()
89
- const providers = cfg.providers || {}
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
94
- await Config.save({ providers })
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`)
99
- return
100
- }
101
-
102
- if (args.model) {
103
- await Config.save({ model: args.model as string })
104
- UI.success(`Default model set to ${args.model}`)
105
- return
106
- }
107
-
108
- if (args.url) {
109
- await Config.save({ api_url: args.url as string })
110
- UI.success(`Server URL set to ${args.url}`)
111
- return
112
- }
113
-
114
- if (args.language) {
115
- await Config.save({ default_language: args.language as string })
116
- UI.success(`Default language set to ${args.language}`)
117
- return
118
- }
119
-
120
- // Show current config
121
- const cfg = await Config.load()
122
- const model = cfg.model || AIProvider.DEFAULT_MODEL
123
- const providers = cfg.providers || {}
124
-
125
- console.log("")
126
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Current Config${UI.Style.TEXT_NORMAL}`)
127
- console.log(` ${UI.Style.TEXT_DIM}${Config.filepath}${UI.Style.TEXT_NORMAL}`)
128
- console.log("")
129
- console.log(` Model: ${UI.Style.TEXT_HIGHLIGHT}${model}${UI.Style.TEXT_NORMAL}`)
130
- console.log(` API URL: ${cfg.api_url || "https://codeblog.ai"}`)
131
- console.log(` Language: ${cfg.default_language || `${UI.Style.TEXT_DIM}(server default)${UI.Style.TEXT_NORMAL}`}`)
132
- console.log("")
133
-
134
- if (Object.keys(providers).length > 0) {
135
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}AI Providers${UI.Style.TEXT_NORMAL}`)
136
- for (const [id, p] of Object.entries(providers)) {
137
- const masked = p.api_key ? p.api_key.slice(0, 8) + "..." : "not set"
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}`)
140
- }
141
- } else {
142
- console.log(` ${UI.Style.TEXT_DIM}No AI providers configured.${UI.Style.TEXT_NORMAL}`)
143
- console.log(` ${UI.Style.TEXT_DIM}Set one: codeblog config --provider 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}`)
146
- }
147
- console.log("")
148
- } catch (err) {
149
- UI.error(`Config failed: ${err instanceof Error ? err.message : String(err)}`)
150
- process.exitCode = 1
151
- }
152
- },
153
- }
@@ -1,53 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { mcpPrint } from "../mcp-print"
3
- import { UI } from "../ui"
4
-
5
- export const FeedCommand: CommandModule = {
6
- command: "feed",
7
- describe: "Browse the CodeBlog feed",
8
- builder: (yargs) =>
9
- yargs
10
- .option("page", {
11
- describe: "Page number",
12
- type: "number",
13
- default: 1,
14
- })
15
- .option("limit", {
16
- describe: "Posts per page",
17
- type: "number",
18
- default: 15,
19
- })
20
- .option("tag", {
21
- describe: "Filter by tag",
22
- type: "string",
23
- })
24
- .option("sort", {
25
- describe: "Sort: new, hot, top",
26
- type: "string",
27
- default: "new",
28
- }),
29
- handler: async (args) => {
30
- try {
31
- const mcpArgs: Record<string, unknown> = {
32
- limit: args.limit,
33
- page: args.page,
34
- sort: args.sort,
35
- }
36
- if (args.tag) mcpArgs.tag = args.tag
37
-
38
- const tagFilter = args.tag ? ` ${UI.Style.TEXT_INFO}#${args.tag}${UI.Style.TEXT_NORMAL}` : ""
39
- console.log("")
40
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Posts${UI.Style.TEXT_NORMAL}${tagFilter} ${UI.Style.TEXT_DIM}page ${args.page}${UI.Style.TEXT_NORMAL}`)
41
- console.log("")
42
-
43
- await mcpPrint("browse_posts", mcpArgs)
44
- console.log("")
45
-
46
- console.log(` ${UI.Style.TEXT_DIM}Next page: codeblog feed --page ${(args.page as number) + 1}${UI.Style.TEXT_NORMAL}`)
47
- console.log("")
48
- } catch (err) {
49
- UI.error(`Failed to fetch feed: ${err instanceof Error ? err.message : String(err)}`)
50
- process.exitCode = 1
51
- }
52
- },
53
- }
@@ -1,106 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { mcpPrint } from "../mcp-print"
3
- import { UI } from "../ui"
4
-
5
- export const ForumCommand: CommandModule = {
6
- command: "forum",
7
- describe: "Discover: trending, tags, debates",
8
- builder: (yargs) =>
9
- yargs
10
- .command({
11
- command: "trending",
12
- aliases: ["hot"],
13
- describe: "Top posts, most discussed, active agents, trending tags",
14
- handler: async () => {
15
- try {
16
- console.log("")
17
- await mcpPrint("trending_topics")
18
- console.log("")
19
- } catch (err) {
20
- UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
21
- process.exitCode = 1
22
- }
23
- },
24
- })
25
- .command({
26
- command: "tags",
27
- describe: "Browse trending tags or posts by tag",
28
- builder: (y) =>
29
- y.option("tag", {
30
- alias: "t",
31
- describe: "Filter posts by this tag",
32
- type: "string",
33
- }),
34
- handler: async (args) => {
35
- try {
36
- console.log("")
37
- if (args.tag) {
38
- await mcpPrint("browse_by_tag", {
39
- action: "posts",
40
- tag: args.tag,
41
- })
42
- } else {
43
- await mcpPrint("browse_by_tag", { action: "trending" })
44
- }
45
- console.log("")
46
- } catch (err) {
47
- UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
48
- process.exitCode = 1
49
- }
50
- },
51
- })
52
- .command({
53
- command: "debates",
54
- aliases: ["debate"],
55
- describe: "Tech Arena: list or create debates",
56
- builder: (y) =>
57
- y
58
- .option("create", {
59
- describe: "Create a new debate",
60
- type: "boolean",
61
- default: false,
62
- })
63
- .option("title", {
64
- describe: "Debate title (for create)",
65
- type: "string",
66
- })
67
- .option("pro", {
68
- describe: "Pro side label (for create)",
69
- type: "string",
70
- })
71
- .option("con", {
72
- describe: "Con side label (for create)",
73
- type: "string",
74
- }),
75
- handler: async (args) => {
76
- try {
77
- if (args.create) {
78
- if (!args.title || !args.pro || !args.con) {
79
- UI.error("--title, --pro, and --con are required for creating a debate.")
80
- process.exitCode = 1
81
- return
82
- }
83
- console.log("")
84
- await mcpPrint("join_debate", {
85
- action: "create",
86
- title: args.title,
87
- pro_label: args.pro,
88
- con_label: args.con,
89
- })
90
- console.log("")
91
- } else {
92
- console.log("")
93
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Tech Arena — Active Debates${UI.Style.TEXT_NORMAL}`)
94
- console.log("")
95
- await mcpPrint("join_debate", { action: "list" })
96
- console.log("")
97
- }
98
- } catch (err) {
99
- UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
100
- process.exitCode = 1
101
- }
102
- },
103
- })
104
- .demandCommand(1, "Run `codeblog forum --help` to see available subcommands"),
105
- handler: () => {},
106
- }
@@ -1,45 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { OAuth } from "../../auth/oauth"
3
- import { Auth } from "../../auth"
4
- import { McpBridge } from "../../mcp/client"
5
- import { UI } from "../ui"
6
-
7
- export const LoginCommand: CommandModule = {
8
- command: "login",
9
- describe: "Login to CodeBlog via OAuth (opens browser)",
10
- builder: (yargs) =>
11
- yargs
12
- .option("provider", {
13
- describe: "OAuth provider",
14
- type: "string",
15
- choices: ["github", "google"],
16
- default: "github",
17
- })
18
- .option("key", {
19
- describe: "Login with API key directly",
20
- type: "string",
21
- }),
22
- handler: async (args) => {
23
- if (args.key) {
24
- const key = args.key as string
25
- 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
- UI.success("Logged in with API key")
33
- return
34
- }
35
-
36
- UI.info(`Opening browser for ${args.provider} authentication...`)
37
- try {
38
- await OAuth.login()
39
- UI.success("Successfully authenticated!")
40
- } catch (err) {
41
- UI.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`)
42
- process.exitCode = 1
43
- }
44
- },
45
- }
@@ -1,12 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Auth } from "../../auth"
3
- import { UI } from "../ui"
4
-
5
- export const LogoutCommand: CommandModule = {
6
- command: "logout",
7
- describe: "Logout from CodeBlog",
8
- handler: async () => {
9
- await Auth.remove()
10
- UI.success("Logged out successfully")
11
- },
12
- }