codeblog-app 2.1.0 → 2.1.2

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 (67) 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 -110
  6. package/src/ai/__tests__/provider.test.ts +0 -184
  7. package/src/ai/__tests__/tools.test.ts +0 -90
  8. package/src/ai/chat.ts +0 -169
  9. package/src/ai/configure.ts +0 -134
  10. package/src/ai/provider.ts +0 -238
  11. package/src/ai/tools.ts +0 -336
  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 -102
  16. package/src/cli/cmd/chat.ts +0 -190
  17. package/src/cli/cmd/comment.ts +0 -70
  18. package/src/cli/cmd/config.ts +0 -153
  19. package/src/cli/cmd/feed.ts +0 -57
  20. package/src/cli/cmd/forum.ts +0 -123
  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 -202
  24. package/src/cli/cmd/post.ts +0 -29
  25. package/src/cli/cmd/publish.ts +0 -70
  26. package/src/cli/cmd/scan.ts +0 -80
  27. package/src/cli/cmd/search.ts +0 -40
  28. package/src/cli/cmd/setup.ts +0 -273
  29. package/src/cli/cmd/tui.ts +0 -20
  30. package/src/cli/cmd/update.ts +0 -78
  31. package/src/cli/cmd/vote.ts +0 -50
  32. package/src/cli/cmd/whoami.ts +0 -21
  33. package/src/cli/ui.ts +0 -195
  34. package/src/config/index.ts +0 -54
  35. package/src/flag/index.ts +0 -23
  36. package/src/global/index.ts +0 -38
  37. package/src/id/index.ts +0 -20
  38. package/src/index.ts +0 -197
  39. package/src/mcp/__tests__/client.test.ts +0 -149
  40. package/src/mcp/__tests__/e2e.ts +0 -327
  41. package/src/mcp/__tests__/integration.ts +0 -148
  42. package/src/mcp/client.ts +0 -148
  43. package/src/server/index.ts +0 -48
  44. package/src/storage/chat.ts +0 -92
  45. package/src/storage/db.ts +0 -85
  46. package/src/storage/schema.sql.ts +0 -39
  47. package/src/storage/schema.ts +0 -1
  48. package/src/tui/app.tsx +0 -163
  49. package/src/tui/commands.ts +0 -187
  50. package/src/tui/context/exit.tsx +0 -15
  51. package/src/tui/context/helper.tsx +0 -25
  52. package/src/tui/context/route.tsx +0 -24
  53. package/src/tui/context/theme.tsx +0 -470
  54. package/src/tui/routes/home.tsx +0 -508
  55. package/src/tui/routes/model.tsx +0 -209
  56. package/src/tui/routes/notifications.tsx +0 -85
  57. package/src/tui/routes/post.tsx +0 -108
  58. package/src/tui/routes/search.tsx +0 -104
  59. package/src/tui/routes/setup.tsx +0 -255
  60. package/src/tui/routes/trending.tsx +0 -107
  61. package/src/util/__tests__/context.test.ts +0 -31
  62. package/src/util/__tests__/lazy.test.ts +0 -37
  63. package/src/util/context.ts +0 -23
  64. package/src/util/error.ts +0 -46
  65. package/src/util/lazy.ts +0 -18
  66. package/src/util/log.ts +0 -142
  67. package/tsconfig.json +0 -11
@@ -1,80 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { McpBridge } from "../../mcp/client"
3
- import { UI } from "../ui"
4
-
5
- export const ScanCommand: CommandModule = {
6
- command: "scan",
7
- describe: "Scan local IDE sessions",
8
- builder: (yargs) =>
9
- yargs
10
- .option("limit", {
11
- describe: "Max sessions to show",
12
- type: "number",
13
- default: 20,
14
- })
15
- .option("source", {
16
- describe: "Filter by IDE source",
17
- type: "string",
18
- })
19
- .option("status", {
20
- describe: "Show scanner status",
21
- type: "boolean",
22
- default: false,
23
- }),
24
- handler: async (args) => {
25
- try {
26
- if (args.status) {
27
- const text = await McpBridge.callTool("codeblog_status")
28
- console.log("")
29
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}CodeBlog Status${UI.Style.TEXT_NORMAL}`)
30
- console.log("")
31
- for (const line of text.split("\n")) {
32
- console.log(` ${line}`)
33
- }
34
- console.log("")
35
- return
36
- }
37
-
38
- const mcpArgs: Record<string, unknown> = { limit: args.limit }
39
- if (args.source) mcpArgs.source = args.source
40
-
41
- const text = await McpBridge.callTool("scan_sessions", mcpArgs)
42
- let sessions: Array<{
43
- id: string; source: string; project: string; title: string;
44
- messages: number; human: number; ai: number; modified: string;
45
- size: string; path: string; preview?: string
46
- }>
47
-
48
- try {
49
- sessions = JSON.parse(text)
50
- } catch {
51
- // Fallback: just print the raw text
52
- console.log(text)
53
- return
54
- }
55
-
56
- if (sessions.length === 0) {
57
- UI.info("No IDE sessions found. Try running with --status to check scanner availability.")
58
- return
59
- }
60
-
61
- console.log("")
62
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Found ${sessions.length} sessions${UI.Style.TEXT_NORMAL}`)
63
- console.log("")
64
-
65
- for (const session of sessions) {
66
- const source = `${UI.Style.TEXT_INFO}[${session.source}]${UI.Style.TEXT_NORMAL}`
67
- const date = new Date(session.modified).toLocaleDateString()
68
- const msgs = `${UI.Style.TEXT_DIM}${session.human}h/${session.ai}a msgs${UI.Style.TEXT_NORMAL}`
69
-
70
- console.log(` ${source} ${UI.Style.TEXT_NORMAL_BOLD}${session.project}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${date}${UI.Style.TEXT_NORMAL}`)
71
- console.log(` ${session.title}`)
72
- console.log(` ${msgs} ${UI.Style.TEXT_DIM}${session.id}${UI.Style.TEXT_NORMAL}`)
73
- console.log("")
74
- }
75
- } catch (err) {
76
- UI.error(`Scan failed: ${err instanceof Error ? err.message : String(err)}`)
77
- process.exitCode = 1
78
- }
79
- },
80
- }
@@ -1,40 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { McpBridge } from "../../mcp/client"
3
- import { UI } from "../ui"
4
-
5
- export const SearchCommand: CommandModule = {
6
- command: "search <query>",
7
- describe: "Search posts on CodeBlog",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("query", {
11
- describe: "Search query",
12
- type: "string",
13
- demandOption: true,
14
- })
15
- .option("limit", {
16
- describe: "Max results",
17
- type: "number",
18
- default: 20,
19
- }),
20
- handler: async (args) => {
21
- try {
22
- const text = await McpBridge.callTool("search_posts", {
23
- query: args.query,
24
- limit: args.limit,
25
- })
26
-
27
- console.log("")
28
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Results for "${args.query}"${UI.Style.TEXT_NORMAL}`)
29
- console.log("")
30
-
31
- for (const line of text.split("\n")) {
32
- console.log(` ${line}`)
33
- }
34
- console.log("")
35
- } catch (err) {
36
- UI.error(`Search failed: ${err instanceof Error ? err.message : String(err)}`)
37
- process.exitCode = 1
38
- }
39
- },
40
- }
@@ -1,273 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Auth } from "../../auth"
3
- import { OAuth } from "../../auth/oauth"
4
- import { McpBridge } from "../../mcp/client"
5
- import { UI } from "../ui"
6
-
7
- export let setupCompleted = false
8
-
9
- // ─── Auth ────────────────────────────────────────────────────────────────────
10
-
11
- async function authBrowser(): Promise<boolean> {
12
- try {
13
- console.log(` ${UI.Style.TEXT_DIM}Opening browser for login...${UI.Style.TEXT_NORMAL}`)
14
-
15
- await OAuth.login({
16
- onUrl: (url) => {
17
- console.log(` ${UI.Style.TEXT_DIM}If the browser didn't open, visit:${UI.Style.TEXT_NORMAL}`)
18
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}${url}${UI.Style.TEXT_NORMAL}`)
19
- console.log("")
20
- console.log(` ${UI.Style.TEXT_DIM}Waiting for authentication...${UI.Style.TEXT_NORMAL}`)
21
- },
22
- })
23
-
24
- return true
25
- } catch (err) {
26
- UI.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`)
27
- return false
28
- }
29
- }
30
-
31
- // ─── Scan & Publish ──────────────────────────────────────────────────────────
32
-
33
- async function scanAndPublish(): Promise<void> {
34
- // Scan
35
- await UI.typeText("Scanning your local IDE sessions...", { charDelay: 15 })
36
- console.log("")
37
-
38
- let sessions: Array<{ id: string; source: string; project: string; title: string }>
39
- try {
40
- const text = await McpBridge.callTool("scan_sessions", { limit: 10 })
41
- try {
42
- sessions = JSON.parse(text)
43
- } catch {
44
- console.log(` ${text}`)
45
- return
46
- }
47
- } catch (err) {
48
- UI.warn(`Could not scan sessions: ${err instanceof Error ? err.message : String(err)}`)
49
- await UI.typeText("No worries — you can scan later with /scan in the app.")
50
- return
51
- }
52
-
53
- if (sessions.length === 0) {
54
- await UI.typeText("No IDE sessions found yet. That's okay!")
55
- await UI.typeText("You can scan later with /scan once you've used an AI-powered IDE.")
56
- return
57
- }
58
-
59
- // Show what we found
60
- const sources = [...new Set(sessions.map((s) => s.source))]
61
- await UI.typeText(
62
- `Found ${sessions.length} session${sessions.length > 1 ? "s" : ""} across ${sources.length} IDE${sources.length > 1 ? "s" : ""}: ${sources.join(", ")}`,
63
- { charDelay: 10 },
64
- )
65
- console.log("")
66
-
67
- for (const s of sessions.slice(0, 3)) {
68
- console.log(` ${UI.Style.TEXT_INFO}[${s.source}]${UI.Style.TEXT_NORMAL} ${s.project} — ${s.title.slice(0, 60)}`)
69
- }
70
- if (sessions.length > 3) {
71
- console.log(` ${UI.Style.TEXT_DIM}... and ${sessions.length - 3} more${UI.Style.TEXT_NORMAL}`)
72
- }
73
- console.log("")
74
-
75
- await UI.typeText("Let me analyze your most interesting session and create a blog post...")
76
- console.log("")
77
-
78
- // Dry run — preview
79
- let preview: string
80
- try {
81
- preview = await McpBridge.callTool("auto_post", { dry_run: true })
82
- } catch (err) {
83
- UI.warn(`Could not generate post: ${err instanceof Error ? err.message : String(err)}`)
84
- await UI.typeText("You can try again later with /publish in the app.")
85
- return
86
- }
87
-
88
- // Display preview
89
- const cleaned = UI.cleanMarkdown(preview)
90
- UI.divider()
91
-
92
- // Extract and display title/tags nicely
93
- const lines = cleaned.split("\n")
94
- for (const line of lines) {
95
- const trimmed = line.trim()
96
- if (!trimmed) {
97
- console.log("")
98
- continue
99
- }
100
- if (trimmed.startsWith("DRY RUN")) continue
101
- if (trimmed.startsWith("Title:")) {
102
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}${trimmed}${UI.Style.TEXT_NORMAL}`)
103
- } else if (trimmed.startsWith("Tags:") || trimmed.startsWith("Category:") || trimmed.startsWith("Session:")) {
104
- console.log(` ${UI.Style.TEXT_DIM}${trimmed}${UI.Style.TEXT_NORMAL}`)
105
- } else if (trimmed === "---" || trimmed.match(/^─+$/)) {
106
- // skip dividers in content
107
- } else {
108
- console.log(` ${trimmed}`)
109
- }
110
- }
111
-
112
- UI.divider()
113
-
114
- // Confirm publish
115
- console.log(` ${UI.Style.TEXT_DIM}Press Enter to publish this post, or Esc to skip${UI.Style.TEXT_NORMAL}`)
116
- const choice = await UI.waitEnter()
117
-
118
- if (choice === "escape") {
119
- await UI.typeText("Skipped. You can publish later with /publish in the app.")
120
- return
121
- }
122
-
123
- // Publish
124
- await UI.typeText("Publishing...", { charDelay: 20 })
125
- try {
126
- const result = await McpBridge.callTool("auto_post", { dry_run: false })
127
- console.log("")
128
-
129
- // Extract URL from result
130
- const urlMatch = result.match(/(?:URL|View at|view at)[:\s]*(https?:\/\/\S+)/i)
131
- if (urlMatch) {
132
- UI.success(`Published! View at: ${urlMatch[1]}`)
133
- } else {
134
- // Fallback: show cleaned result
135
- const cleanResult = UI.cleanMarkdown(result)
136
- for (const line of cleanResult.split("\n").slice(0, 5)) {
137
- if (line.trim()) console.log(` ${line.trim()}`)
138
- }
139
- }
140
- } catch (err) {
141
- UI.error(`Publish failed: ${err instanceof Error ? err.message : String(err)}`)
142
- await UI.typeText("You can try again later with /publish.")
143
- }
144
- }
145
-
146
- // ─── AI Configuration ────────────────────────────────────────────────────────
147
-
148
- async function aiConfigPrompt(): Promise<void> {
149
- const { AIProvider } = await import("../../ai/provider")
150
- const hasKey = await AIProvider.hasAnyKey()
151
-
152
- if (hasKey) {
153
- UI.success("AI provider already configured!")
154
- return
155
- }
156
-
157
- UI.divider()
158
-
159
- await UI.typeText("One more thing — would you like to configure an AI chat provider?")
160
- console.log("")
161
- await UI.typeText("With AI configured, you can interact with the forum using natural language:", { charDelay: 8 })
162
- console.log(` ${UI.Style.TEXT_DIM}"Show me trending posts about TypeScript"${UI.Style.TEXT_NORMAL}`)
163
- console.log(` ${UI.Style.TEXT_DIM}"Analyze my latest coding session"${UI.Style.TEXT_NORMAL}`)
164
- console.log(` ${UI.Style.TEXT_DIM}"Write a post about my React refactoring"${UI.Style.TEXT_NORMAL}`)
165
- console.log("")
166
-
167
- console.log(` ${UI.Style.TEXT_DIM}Press Enter to configure AI, or Esc to skip${UI.Style.TEXT_NORMAL}`)
168
- const choice = await UI.waitEnter()
169
-
170
- if (choice === "escape") {
171
- console.log("")
172
- await UI.typeText("No problem! You can configure AI later with /ai in the app.")
173
- console.log("")
174
- await UI.typeText("Even without AI, you can use slash commands to interact:", { charDelay: 8 })
175
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}/scan${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}Scan IDE sessions${UI.Style.TEXT_NORMAL}`)
176
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}/publish${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}Publish a post${UI.Style.TEXT_NORMAL}`)
177
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}/feed${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}Browse the forum${UI.Style.TEXT_NORMAL}`)
178
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}/theme${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}Change color theme${UI.Style.TEXT_NORMAL}`)
179
- return
180
- }
181
-
182
- // AI config flow: URL → Key (reuses saveProvider from ai/configure.ts)
183
- console.log("")
184
- const url = await UI.input(` ${UI.Style.TEXT_NORMAL_BOLD}API URL${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(or press Enter to skip):${UI.Style.TEXT_NORMAL} `)
185
- const key = await UI.input(` ${UI.Style.TEXT_NORMAL_BOLD}API Key:${UI.Style.TEXT_NORMAL} `)
186
-
187
- if (!key || key.length < 5) {
188
- UI.warn("API key too short, skipping AI configuration.")
189
- await UI.typeText("You can configure AI later with /ai in the app.")
190
- return
191
- }
192
-
193
- try {
194
- const { saveProvider } = await import("../../ai/configure")
195
- console.log(` ${UI.Style.TEXT_DIM}Detecting API format...${UI.Style.TEXT_NORMAL}`)
196
- const result = await saveProvider(url.trim(), key.trim())
197
- if (result.error) {
198
- UI.warn(result.error)
199
- await UI.typeText("You can try again later with /ai in the app.")
200
- } else {
201
- UI.success(`AI configured! (${result.provider})`)
202
- }
203
- } catch (err) {
204
- UI.warn(`Configuration failed: ${err instanceof Error ? err.message : String(err)}`)
205
- await UI.typeText("You can try again later with /ai in the app.")
206
- }
207
- }
208
-
209
- // ─── Setup Command ───────────────────────────────────────────────────────────
210
-
211
- export const SetupCommand: CommandModule = {
212
- command: "setup",
213
- describe: "First-time setup wizard: authenticate, scan, publish, configure AI",
214
- handler: async () => {
215
- // Phase 1: Welcome
216
- console.log(UI.logo())
217
- await UI.typeText("Welcome to CodeBlog!", { charDelay: 20 })
218
- await UI.typeText("The AI-powered coding forum in your terminal.", { charDelay: 15 })
219
- console.log("")
220
-
221
- // Phase 2: Authentication
222
- const alreadyAuthed = await Auth.authenticated()
223
- let authenticated = alreadyAuthed
224
-
225
- if (alreadyAuthed) {
226
- const token = await Auth.get()
227
- UI.success(`Already authenticated as ${token?.username || "user"}!`)
228
- } else {
229
- await UI.typeText("Let's get you set up. First, we need to authenticate your account.")
230
- await UI.typeText("You may need to sign up or log in on the website first.", { charDelay: 10 })
231
- console.log("")
232
-
233
- console.log(` ${UI.Style.TEXT_DIM}Press Enter to open browser...${UI.Style.TEXT_NORMAL}`)
234
- await UI.waitEnter()
235
-
236
- authenticated = await authBrowser()
237
- }
238
-
239
- if (!authenticated) {
240
- console.log("")
241
- UI.info("You can try again with: codeblog setup")
242
- return
243
- }
244
-
245
- const token = await Auth.get()
246
- UI.success(`Authenticated as ${token?.username || "user"}!`)
247
-
248
- // Phase 3: Interactive scan & publish
249
- UI.divider()
250
-
251
- await UI.typeText("Great! Let's see what you've been working on.")
252
- await UI.typeText("I'll scan your local IDE sessions to find interesting coding experiences.", { charDelay: 10 })
253
- console.log("")
254
-
255
- console.log(` ${UI.Style.TEXT_DIM}Press Enter to continue...${UI.Style.TEXT_NORMAL}`)
256
- const scanChoice = await UI.waitEnter()
257
-
258
- if (scanChoice === "enter") {
259
- await scanAndPublish()
260
- } else {
261
- await UI.typeText("Skipped. You can scan and publish later in the app.")
262
- }
263
-
264
- // Phase 4: AI configuration
265
- await aiConfigPrompt()
266
-
267
- // Phase 5: Transition to TUI
268
- UI.divider()
269
- setupCompleted = true
270
- await UI.typeText("All set! Launching CodeBlog...", { charDelay: 20 })
271
- await Bun.sleep(800)
272
- },
273
- }
@@ -1,20 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
-
3
- export const TuiCommand: CommandModule = {
4
- command: "tui",
5
- aliases: ["ui"],
6
- describe: "Launch interactive TUI — browse feed, chat with AI, manage posts",
7
- builder: (yargs) =>
8
- yargs
9
- .option("model", {
10
- alias: "m",
11
- describe: "Default AI model",
12
- type: "string",
13
- }),
14
- handler: async (args) => {
15
- const { tui } = await import("../../tui/app")
16
- await tui({
17
- onExit: async () => {},
18
- })
19
- },
20
- }
@@ -1,78 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { UI } from "../ui"
3
-
4
- export const UpdateCommand: CommandModule = {
5
- command: "update",
6
- describe: "Update codeblog CLI to the latest version",
7
- builder: (yargs) =>
8
- yargs.option("force", {
9
- describe: "Force update even if already on latest",
10
- type: "boolean",
11
- default: false,
12
- }),
13
- handler: async (args) => {
14
- const pkg = await import("../../../package.json")
15
- const current = pkg.version
16
-
17
- UI.info(`Current version: v${current}`)
18
- UI.info("Checking for updates...")
19
-
20
- const res = await fetch("https://registry.npmjs.org/codeblog-app/latest")
21
- if (!res.ok) {
22
- UI.error("Failed to check for updates")
23
- process.exitCode = 1
24
- return
25
- }
26
-
27
- const data = await res.json() as { version: string }
28
- const latest = data.version
29
-
30
- if (current === latest && !args.force) {
31
- UI.success(`Already on latest version v${current}`)
32
- return
33
- }
34
-
35
- UI.info(`Updating v${current} → v${latest}...`)
36
-
37
- const os = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "darwin" : "linux"
38
- const arch = process.arch === "arm64" ? "arm64" : "x64"
39
- const platform = `${os}-${arch}`
40
- const pkg_name = `codeblog-app-${platform}`
41
- const url = `https://registry.npmjs.org/${pkg_name}/-/${pkg_name}-${latest}.tgz`
42
-
43
- const tmpdir = (await import("os")).tmpdir()
44
- const path = await import("path")
45
- const fs = await import("fs/promises")
46
- const tmp = path.join(tmpdir, `codeblog-update-${Date.now()}`)
47
- await fs.mkdir(tmp, { recursive: true })
48
-
49
- const tgz = path.join(tmp, "pkg.tgz")
50
- const dlRes = await fetch(url)
51
- if (!dlRes.ok) {
52
- UI.error(`Failed to download update for ${platform}`)
53
- process.exitCode = 1
54
- return
55
- }
56
-
57
- await Bun.write(tgz, dlRes)
58
-
59
- const proc = Bun.spawn(["tar", "-xzf", tgz, "-C", tmp], { stdout: "ignore", stderr: "ignore" })
60
- await proc.exited
61
-
62
- const bin = process.execPath
63
- const ext = os === "windows" ? ".exe" : ""
64
- const src = path.join(tmp, "package", "bin", `codeblog${ext}`)
65
-
66
- await fs.copyFile(src, bin)
67
- if (os !== "windows") {
68
- await fs.chmod(bin, 0o755)
69
- }
70
- if (os === "darwin") {
71
- Bun.spawn(["codesign", "--sign", "-", "--force", bin], { stdout: "ignore", stderr: "ignore" })
72
- }
73
-
74
- await fs.rm(tmp, { recursive: true, force: true })
75
-
76
- UI.success(`Updated to v${latest}!`)
77
- },
78
- }
@@ -1,50 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { McpBridge } from "../../mcp/client"
3
- import { UI } from "../ui"
4
-
5
- export const VoteCommand: CommandModule = {
6
- command: "vote <post_id>",
7
- describe: "Vote on a post (up/down/remove)",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("post_id", {
11
- describe: "Post ID to vote on",
12
- type: "string",
13
- demandOption: true,
14
- })
15
- .option("up", {
16
- alias: "u",
17
- describe: "Upvote",
18
- type: "boolean",
19
- })
20
- .option("down", {
21
- alias: "d",
22
- describe: "Downvote",
23
- type: "boolean",
24
- })
25
- .option("remove", {
26
- describe: "Remove existing vote",
27
- type: "boolean",
28
- })
29
- .conflicts("up", "down")
30
- .conflicts("up", "remove")
31
- .conflicts("down", "remove"),
32
- handler: async (args) => {
33
- let value = 1 // default upvote
34
- if (args.down) value = -1
35
- if (args.remove) value = 0
36
-
37
- try {
38
- const text = await McpBridge.callTool("vote_on_post", {
39
- post_id: args.post_id,
40
- value,
41
- })
42
- console.log("")
43
- console.log(` ${text}`)
44
- console.log("")
45
- } catch (err) {
46
- UI.error(`Vote failed: ${err instanceof Error ? err.message : String(err)}`)
47
- process.exitCode = 1
48
- }
49
- },
50
- }
@@ -1,21 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { McpBridge } from "../../mcp/client"
3
- import { UI } from "../ui"
4
-
5
- export const WhoamiCommand: CommandModule = {
6
- command: "whoami",
7
- describe: "Show current auth status",
8
- handler: async () => {
9
- try {
10
- const text = await McpBridge.callTool("codeblog_status")
11
- console.log("")
12
- for (const line of text.split("\n")) {
13
- console.log(` ${line}`)
14
- }
15
- console.log("")
16
- } catch (err) {
17
- UI.error(`Status check failed: ${err instanceof Error ? err.message : String(err)}`)
18
- process.exitCode = 1
19
- }
20
- },
21
- }