codeblog-app 1.6.5 → 2.0.1

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 (76) hide show
  1. package/package.json +9 -23
  2. package/src/ai/__tests__/chat.test.ts +110 -0
  3. package/src/ai/__tests__/provider.test.ts +184 -0
  4. package/src/ai/__tests__/tools.test.ts +90 -0
  5. package/src/ai/chat.ts +14 -14
  6. package/src/ai/provider.ts +24 -250
  7. package/src/ai/tools.ts +46 -281
  8. package/src/auth/oauth.ts +7 -0
  9. package/src/cli/__tests__/commands.test.ts +225 -0
  10. package/src/cli/__tests__/setup.test.ts +57 -0
  11. package/src/cli/cmd/agent.ts +102 -0
  12. package/src/cli/cmd/chat.ts +1 -1
  13. package/src/cli/cmd/comment.ts +47 -16
  14. package/src/cli/cmd/feed.ts +18 -30
  15. package/src/cli/cmd/forum.ts +123 -0
  16. package/src/cli/cmd/login.ts +9 -2
  17. package/src/cli/cmd/me.ts +202 -0
  18. package/src/cli/cmd/post.ts +6 -88
  19. package/src/cli/cmd/publish.ts +44 -23
  20. package/src/cli/cmd/scan.ts +45 -34
  21. package/src/cli/cmd/search.ts +8 -70
  22. package/src/cli/cmd/setup.ts +160 -62
  23. package/src/cli/cmd/vote.ts +29 -14
  24. package/src/cli/cmd/whoami.ts +7 -36
  25. package/src/cli/ui.ts +50 -0
  26. package/src/index.ts +80 -59
  27. package/src/mcp/__tests__/client.test.ts +149 -0
  28. package/src/mcp/__tests__/e2e.ts +327 -0
  29. package/src/mcp/__tests__/integration.ts +148 -0
  30. package/src/mcp/client.ts +148 -0
  31. package/src/api/agents.ts +0 -103
  32. package/src/api/bookmarks.ts +0 -25
  33. package/src/api/client.ts +0 -96
  34. package/src/api/debates.ts +0 -35
  35. package/src/api/feed.ts +0 -25
  36. package/src/api/notifications.ts +0 -31
  37. package/src/api/posts.ts +0 -116
  38. package/src/api/search.ts +0 -29
  39. package/src/api/tags.ts +0 -13
  40. package/src/api/trending.ts +0 -38
  41. package/src/api/users.ts +0 -8
  42. package/src/cli/cmd/agents.ts +0 -77
  43. package/src/cli/cmd/ai-publish.ts +0 -118
  44. package/src/cli/cmd/bookmark.ts +0 -27
  45. package/src/cli/cmd/bookmarks.ts +0 -42
  46. package/src/cli/cmd/dashboard.ts +0 -59
  47. package/src/cli/cmd/debate.ts +0 -89
  48. package/src/cli/cmd/delete.ts +0 -35
  49. package/src/cli/cmd/edit.ts +0 -42
  50. package/src/cli/cmd/explore.ts +0 -63
  51. package/src/cli/cmd/follow.ts +0 -34
  52. package/src/cli/cmd/myposts.ts +0 -50
  53. package/src/cli/cmd/notifications.ts +0 -65
  54. package/src/cli/cmd/tags.ts +0 -58
  55. package/src/cli/cmd/trending.ts +0 -64
  56. package/src/cli/cmd/weekly-digest.ts +0 -117
  57. package/src/publisher/index.ts +0 -139
  58. package/src/scanner/__tests__/analyzer.test.ts +0 -67
  59. package/src/scanner/__tests__/fs-utils.test.ts +0 -50
  60. package/src/scanner/__tests__/platform.test.ts +0 -27
  61. package/src/scanner/__tests__/registry.test.ts +0 -56
  62. package/src/scanner/aider.ts +0 -96
  63. package/src/scanner/analyzer.ts +0 -237
  64. package/src/scanner/claude-code.ts +0 -188
  65. package/src/scanner/codex.ts +0 -127
  66. package/src/scanner/continue-dev.ts +0 -95
  67. package/src/scanner/cursor.ts +0 -299
  68. package/src/scanner/fs-utils.ts +0 -123
  69. package/src/scanner/index.ts +0 -26
  70. package/src/scanner/platform.ts +0 -44
  71. package/src/scanner/registry.ts +0 -68
  72. package/src/scanner/types.ts +0 -62
  73. package/src/scanner/vscode-copilot.ts +0 -125
  74. package/src/scanner/warp.ts +0 -19
  75. package/src/scanner/windsurf.ts +0 -147
  76. package/src/scanner/zed.ts +0 -88
@@ -1,56 +1,118 @@
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"
5
- import { registerAllScanners, scanAll } from "../../scanner"
6
- import { Publisher } from "../../publisher"
4
+ import { McpBridge } from "../../mcp/client"
7
5
  import { UI } from "../ui"
8
6
 
9
- export const SetupCommand: CommandModule = {
10
- command: "setup",
11
- describe: "First-time setup wizard: login → scan → publish",
12
- handler: async () => {
13
- console.log(UI.logo())
14
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Welcome to CodeBlog! 🚀${UI.Style.TEXT_NORMAL}`)
15
- console.log(` ${UI.Style.TEXT_DIM}The AI-powered coding forum${UI.Style.TEXT_NORMAL}`)
16
- console.log("")
7
+ function extractApiKey(text: string): string | null {
8
+ const match = text.match(/^API-KEY:\s*(\S+)/m)
9
+ return match ? match[1]! : null
10
+ }
17
11
 
18
- // Step 1: Auth
19
- const authenticated = await Auth.authenticated()
20
- if (!authenticated) {
21
- UI.info("Step 1: Let's log you in...")
22
- console.log("")
23
- const provider = await UI.input(" Choose provider (github/google) [github]: ")
24
- const chosen = (provider === "google" ? "google" : "github") as "github" | "google"
12
+ function extractUsername(text: string): string | null {
13
+ // Try "Account: username (email)" first (registration path)
14
+ const acct = text.match(/^Account:\s*(\S+)/m)
15
+ if (acct) return acct[1]!
16
+ // Try "Owner: username" (api_key verification path)
17
+ const owner = text.match(/^Owner:\s*(\S+)/m)
18
+ if (owner) return owner[1]!
19
+ return null
20
+ }
25
21
 
26
- try {
27
- await OAuth.login(chosen)
28
- UI.success("Authenticated!")
29
- } catch (err) {
30
- UI.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`)
31
- UI.info("You can try again with: codeblog login")
32
- return
33
- }
22
+ export { extractApiKey, extractUsername }
23
+
24
+ async function authQuickSignup(): Promise<boolean> {
25
+ const email = await UI.input(" Email: ")
26
+ if (!email) { UI.error("Email is required."); return false }
27
+
28
+ const username = await UI.input(" Username: ")
29
+ if (!username) { UI.error("Username is required."); return false }
30
+
31
+ const password = await UI.password(" Password: ")
32
+ if (!password || password.length < 6) { UI.error("Password must be at least 6 characters."); return false }
33
+
34
+ const lang = await UI.input(" Language (e.g. English/中文) [English]: ")
35
+ const default_language = lang || "English"
36
+
37
+ console.log("")
38
+ UI.info("Creating your account...")
39
+
40
+ try {
41
+ const result = await McpBridge.callTool("codeblog_setup", {
42
+ email, username, password, default_language,
43
+ })
44
+
45
+ const apiKey = extractApiKey(result)
46
+ const user = extractUsername(result)
47
+
48
+ if (apiKey) {
49
+ await Auth.set({ type: "apikey", value: apiKey, username: user || username })
34
50
  } else {
35
- UI.success("Already authenticated!")
51
+ UI.warn("Account created but could not extract API key from response.")
52
+ UI.info("Try: codeblog setup → option 3 to paste your API key manually.")
53
+ return false
36
54
  }
37
55
 
38
- console.log("")
56
+ return true
57
+ } catch (err) {
58
+ UI.error(`Signup failed: ${err instanceof Error ? err.message : String(err)}`)
59
+ return false
60
+ }
61
+ }
62
+ async function authOAuth(): Promise<boolean> {
63
+ const provider = await UI.input(" Choose provider (github/google) [github]: ")
64
+ const chosen = (provider === "google" ? "google" : "github") as "github" | "google"
39
65
 
40
- // Step 2: Scan
41
- UI.info("Step 2: Scanning your IDE sessions...")
42
- registerAllScanners()
43
- const sessions = scanAll(10)
66
+ try {
67
+ await OAuth.login(chosen)
68
+ return true
69
+ } catch (err) {
70
+ UI.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`)
71
+ return false
72
+ }
73
+ }
74
+
75
+ async function authApiKey(): Promise<boolean> {
76
+ const key = await UI.input(" Paste your API key (starts with cbk_): ")
77
+ if (!key || !key.startsWith("cbk_")) {
78
+ UI.error("Invalid API key. It should start with 'cbk_'.")
79
+ return false
80
+ }
81
+
82
+ UI.info("Verifying API key...")
83
+ try {
84
+ const result = await McpBridge.callTool("codeblog_setup", { api_key: key })
85
+ const username = extractUsername(result)
86
+ await Auth.set({ type: "apikey", value: key, username: username || undefined })
87
+ return true
88
+ } catch (err) {
89
+ UI.error(`Verification failed: ${err instanceof Error ? err.message : String(err)}`)
90
+ return false
91
+ }
92
+ }
93
+
94
+ async function postAuthFlow(): Promise<void> {
95
+ console.log("")
96
+
97
+ // Scan
98
+ UI.info("Scanning your IDE sessions...")
99
+ try {
100
+ const text = await McpBridge.callTool("scan_sessions", { limit: 10 })
101
+ let sessions: Array<{ id: string; source: string; project: string; title: string }>
102
+ try {
103
+ sessions = JSON.parse(text)
104
+ } catch {
105
+ console.log(text)
106
+ return
107
+ }
44
108
 
45
109
  if (sessions.length === 0) {
46
- UI.warn("No IDE sessions found. You can manually create posts later.")
47
- UI.info("Run: codeblog scan --status to check which IDEs are detected")
110
+ UI.warn("No IDE sessions found. You can scan later with: codeblog scan")
48
111
  return
49
112
  }
50
113
 
51
- console.log(` Found ${UI.Style.TEXT_HIGHLIGHT}${sessions.length}${UI.Style.TEXT_NORMAL} sessions across your IDEs`)
114
+ console.log(` Found ${UI.Style.TEXT_HIGHLIGHT}${sessions.length}${UI.Style.TEXT_NORMAL} sessions`)
52
115
  console.log("")
53
-
54
116
  for (const s of sessions.slice(0, 5)) {
55
117
  console.log(` ${UI.Style.TEXT_INFO}[${s.source}]${UI.Style.TEXT_NORMAL} ${s.project} — ${s.title.slice(0, 60)}`)
56
118
  }
@@ -59,45 +121,81 @@ export const SetupCommand: CommandModule = {
59
121
  }
60
122
  console.log("")
61
123
 
62
- // Step 3: Publish
124
+ // Publish
63
125
  const answer = await UI.input(" Publish your latest session to CodeBlog? (y/n) [y]: ")
64
126
  if (answer.toLowerCase() === "n") {
65
- UI.info("Skipped publishing. You can publish later with: codeblog publish")
66
- return
127
+ UI.info("Skipped. You can publish later with: codeblog publish")
128
+ } else {
129
+ UI.info("Publishing...")
130
+ try {
131
+ const result = await McpBridge.callTool("auto_post", { dry_run: false })
132
+ console.log("")
133
+ for (const line of result.split("\n")) {
134
+ console.log(` ${line}`)
135
+ }
136
+ } catch (pubErr) {
137
+ UI.error(`Publish failed: ${pubErr instanceof Error ? pubErr.message : String(pubErr)}`)
138
+ }
67
139
  }
140
+ } catch (err) {
141
+ UI.error(`Scan failed: ${err instanceof Error ? err.message : String(err)}`)
142
+ }
143
+ }
144
+ export const SetupCommand: CommandModule = {
145
+ command: "setup",
146
+ describe: "First-time setup wizard: authenticate → scan → publish",
147
+ handler: async () => {
148
+ console.log(UI.logo())
149
+ console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Welcome to CodeBlog!${UI.Style.TEXT_NORMAL}`)
150
+ console.log(` ${UI.Style.TEXT_DIM}The AI-powered coding forum${UI.Style.TEXT_NORMAL}`)
151
+ console.log("")
68
152
 
69
- UI.info("Step 3: Publishing...")
70
- const results = await Publisher.scanAndPublish({ limit: 1 })
71
-
72
- for (const r of results) {
73
- if (r.postId) UI.success(`Published: ${r.session.title}`)
74
- if (r.error) UI.error(`Failed: ${r.error}`)
75
- }
153
+ const alreadyAuthed = await Auth.authenticated()
154
+ let authenticated = alreadyAuthed
76
155
 
77
- console.log("")
78
- UI.success("Setup complete! 🎉")
79
- console.log("")
156
+ if (alreadyAuthed) {
157
+ UI.success("Already authenticated!")
158
+ } else {
159
+ const choice = await UI.select(" How would you like to get started?", [
160
+ "Quick signup (email + password)",
161
+ "Login with GitHub / Google",
162
+ "Paste existing API key",
163
+ ])
80
164
 
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
165
  console.log("")
90
- console.log(` ${UI.Style.TEXT_DIM}Run: codeblog config --list to see all 15+ supported providers${UI.Style.TEXT_NORMAL}`)
166
+
167
+ switch (choice) {
168
+ case 0:
169
+ authenticated = await authQuickSignup()
170
+ break
171
+ case 1:
172
+ authenticated = await authOAuth()
173
+ break
174
+ case 2:
175
+ authenticated = await authApiKey()
176
+ break
177
+ }
178
+ }
179
+
180
+ if (!authenticated) {
91
181
  console.log("")
182
+ UI.info("You can try again with: codeblog setup")
183
+ return
92
184
  }
93
185
 
186
+ const token = await Auth.get()
187
+ UI.success(`Authenticated as ${token?.username || "user"}!`)
188
+
189
+ await postAuthFlow()
190
+
191
+ console.log("")
192
+ UI.success("Setup complete!")
193
+ console.log("")
94
194
  console.log(` ${UI.Style.TEXT_DIM}Useful commands:${UI.Style.TEXT_NORMAL}`)
95
195
  console.log(` codeblog feed ${UI.Style.TEXT_DIM}— Browse the forum${UI.Style.TEXT_NORMAL}`)
96
196
  console.log(` codeblog scan ${UI.Style.TEXT_DIM}— Scan IDE sessions${UI.Style.TEXT_NORMAL}`)
97
197
  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}`)
100
- console.log(` codeblog dashboard ${UI.Style.TEXT_DIM}— Your stats${UI.Style.TEXT_NORMAL}`)
198
+ console.log(` codeblog chat ${UI.Style.TEXT_DIM}— AI chat${UI.Style.TEXT_NORMAL}`)
101
199
  console.log("")
102
200
  },
103
- }
201
+ }
@@ -1,34 +1,49 @@
1
1
  import type { CommandModule } from "yargs"
2
- import { Posts } from "../../api/posts"
2
+ import { McpBridge } from "../../mcp/client"
3
3
  import { UI } from "../ui"
4
4
 
5
5
  export const VoteCommand: CommandModule = {
6
- command: "vote <post-id>",
7
- describe: "Vote on a post (up/down/clear)",
6
+ command: "vote <post_id>",
7
+ describe: "Vote on a post (up/down/remove)",
8
8
  builder: (yargs) =>
9
9
  yargs
10
- .positional("post-id", {
10
+ .positional("post_id", {
11
11
  describe: "Post ID to vote on",
12
12
  type: "string",
13
13
  demandOption: true,
14
14
  })
15
+ .option("up", {
16
+ alias: "u",
17
+ describe: "Upvote",
18
+ type: "boolean",
19
+ })
15
20
  .option("down", {
16
- describe: "Downvote instead of upvote",
21
+ alias: "d",
22
+ describe: "Downvote",
17
23
  type: "boolean",
18
- default: false,
19
24
  })
20
- .option("clear", {
21
- describe: "Remove your vote",
25
+ .option("remove", {
26
+ describe: "Remove existing vote",
22
27
  type: "boolean",
23
- default: false,
24
- }),
28
+ })
29
+ .conflicts("up", "down")
30
+ .conflicts("up", "remove")
31
+ .conflicts("down", "remove"),
25
32
  handler: async (args) => {
33
+ let value = 1 // default upvote
34
+ if (args.down) value = -1
35
+ if (args.remove) value = 0
36
+
26
37
  try {
27
- const value: 1 | -1 | 0 = args.clear ? 0 : args.down ? -1 : 1
28
- const result = await Posts.vote(args.postId as string, value)
29
- UI.success(result.message)
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("")
30
45
  } catch (err) {
31
- UI.error(`Failed to vote: ${err instanceof Error ? err.message : String(err)}`)
46
+ UI.error(`Vote failed: ${err instanceof Error ? err.message : String(err)}`)
32
47
  process.exitCode = 1
33
48
  }
34
49
  },
@@ -1,50 +1,21 @@
1
1
  import type { CommandModule } from "yargs"
2
- import { Auth } from "../../auth"
3
- import { Agents } from "../../api/agents"
4
- import { Config } from "../../config"
2
+ import { McpBridge } from "../../mcp/client"
5
3
  import { UI } from "../ui"
6
- import { ApiError } from "../../api/client"
7
4
 
8
5
  export const WhoamiCommand: CommandModule = {
9
6
  command: "whoami",
10
7
  describe: "Show current auth status",
11
8
  handler: async () => {
12
- const token = await Auth.get()
13
- const url = await Config.url()
14
-
15
- console.log("")
16
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Auth Status${UI.Style.TEXT_NORMAL}`)
17
- console.log("")
18
- console.log(` Server: ${UI.Style.TEXT_DIM}${url}${UI.Style.TEXT_NORMAL}`)
19
-
20
- if (!token) {
21
- console.log(` Status: ${UI.Style.TEXT_WARNING}Not authenticated${UI.Style.TEXT_NORMAL}`)
22
- console.log("")
23
- UI.info("Run: codeblog login")
24
- return
25
- }
26
-
27
- console.log(` Type: ${UI.Style.TEXT_INFO}${token.type}${UI.Style.TEXT_NORMAL}`)
28
- const masked = token.value.slice(0, 8) + "..." + token.value.slice(-4)
29
- console.log(` Key: ${UI.Style.TEXT_DIM}${masked}${UI.Style.TEXT_NORMAL}`)
30
-
31
9
  try {
32
- const result = await Agents.me()
10
+ const text = await McpBridge.callTool("codeblog_status")
33
11
  console.log("")
34
- console.log(` ${UI.Style.TEXT_SUCCESS}✓ Connected${UI.Style.TEXT_NORMAL}`)
35
- console.log(` Agent: ${UI.Style.TEXT_HIGHLIGHT}${result.agent.name}${UI.Style.TEXT_NORMAL}`)
36
- console.log(` Posts: ${result.agent.posts_count}`)
37
- if (result.agent.owner) {
38
- console.log(` Owner: ${result.agent.owner}`)
12
+ for (const line of text.split("\n")) {
13
+ console.log(` ${line}`)
39
14
  }
15
+ console.log("")
40
16
  } catch (err) {
41
- if (err instanceof ApiError && err.unauthorized) {
42
- console.log(` Status: ${UI.Style.TEXT_DANGER}Invalid credentials${UI.Style.TEXT_NORMAL}`)
43
- UI.info("Run: codeblog login")
44
- } else {
45
- console.log(` Status: ${UI.Style.TEXT_WARNING}Cannot reach server${UI.Style.TEXT_NORMAL}`)
46
- }
17
+ UI.error(`Status check failed: ${err instanceof Error ? err.message : String(err)}`)
18
+ process.exitCode = 1
47
19
  }
48
- console.log("")
49
20
  },
50
21
  }
package/src/cli/ui.ts CHANGED
@@ -71,4 +71,54 @@ export namespace UI {
71
71
  })
72
72
  })
73
73
  }
74
+
75
+ export async function password(prompt: string): Promise<string> {
76
+ const readline = require("readline")
77
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr, terminal: true })
78
+ return new Promise((resolve) => {
79
+ // Disable echoing by writing the prompt manually and muting stdout
80
+ process.stderr.write(prompt)
81
+ const stdin = process.stdin
82
+ const wasRaw = stdin.isRaw
83
+ if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(true)
84
+
85
+ let buf = ""
86
+ const onData = (ch: Buffer) => {
87
+ const c = ch.toString("utf8")
88
+ if (c === "\n" || c === "\r" || c === "\u0004") {
89
+ if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
90
+ stdin.removeListener("data", onData)
91
+ process.stderr.write("\n")
92
+ rl.close()
93
+ resolve(buf.trim())
94
+ } else if (c === "\u007f" || c === "\b") {
95
+ // backspace
96
+ if (buf.length > 0) buf = buf.slice(0, -1)
97
+ } else if (c === "\u0003") {
98
+ // Ctrl+C
99
+ if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
100
+ stdin.removeListener("data", onData)
101
+ rl.close()
102
+ process.exit(130)
103
+ } else {
104
+ buf += c
105
+ process.stderr.write("*")
106
+ }
107
+ }
108
+ stdin.on("data", onData)
109
+ })
110
+ }
111
+
112
+ export async function select(prompt: string, options: string[]): Promise<number> {
113
+ console.log(prompt)
114
+ for (let i = 0; i < options.length; i++) {
115
+ console.log(` ${Style.TEXT_HIGHLIGHT}[${i + 1}]${Style.TEXT_NORMAL} ${options[i]}`)
116
+ }
117
+ console.log("")
118
+ const answer = await input(` Choice [1]: `)
119
+ const num = parseInt(answer, 10)
120
+ if (!answer) return 0
121
+ if (isNaN(num) || num < 1 || num > options.length) return 0
122
+ return num - 1
123
+ }
74
124
  }
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ import { hideBin } from "yargs/helpers"
3
3
  import { Log } from "./util/log"
4
4
  import { UI } from "./cli/ui"
5
5
  import { EOL } from "os"
6
+ import { McpBridge } from "./mcp/client"
7
+ import { Auth } from "./auth"
6
8
 
7
9
  // Commands
8
10
  import { SetupCommand } from "./cli/cmd/setup"
@@ -14,39 +16,27 @@ import { PostCommand } from "./cli/cmd/post"
14
16
  import { ScanCommand } from "./cli/cmd/scan"
15
17
  import { PublishCommand } from "./cli/cmd/publish"
16
18
  import { SearchCommand } from "./cli/cmd/search"
17
- import { TrendingCommand } from "./cli/cmd/trending"
18
- import { VoteCommand } from "./cli/cmd/vote"
19
19
  import { CommentCommand } from "./cli/cmd/comment"
20
- import { BookmarkCommand } from "./cli/cmd/bookmark"
21
- import { NotificationsCommand } from "./cli/cmd/notifications"
22
- import { DashboardCommand } from "./cli/cmd/dashboard"
23
- import { DebateCommand } from "./cli/cmd/debate"
24
- import { BookmarksCommand } from "./cli/cmd/bookmarks"
25
- import { AgentsCommand } from "./cli/cmd/agents"
26
- import { FollowCommand } from "./cli/cmd/follow"
27
- import { MyPostsCommand } from "./cli/cmd/myposts"
28
- import { EditCommand } from "./cli/cmd/edit"
29
- import { DeleteCommand } from "./cli/cmd/delete"
20
+ import { VoteCommand } from "./cli/cmd/vote"
30
21
  import { ChatCommand } from "./cli/cmd/chat"
31
22
  import { ConfigCommand } from "./cli/cmd/config"
32
- import { AIPublishCommand } from "./cli/cmd/ai-publish"
33
23
  import { TuiCommand } from "./cli/cmd/tui"
34
- import { WeeklyDigestCommand } from "./cli/cmd/weekly-digest"
35
- import { TagsCommand } from "./cli/cmd/tags"
36
- import { ExploreCommand } from "./cli/cmd/explore"
37
24
  import { UpdateCommand } from "./cli/cmd/update"
25
+ import { MeCommand } from "./cli/cmd/me"
26
+ import { AgentCommand } from "./cli/cmd/agent"
27
+ import { ForumCommand } from "./cli/cmd/forum"
38
28
 
39
29
  const VERSION = (await import("../package.json")).version
40
30
 
41
31
  process.on("unhandledRejection", (e) => {
42
32
  Log.Default.error("rejection", {
43
- e: e instanceof Error ? e.message : e,
33
+ e: e instanceof Error ? e.stack || e.message : e,
44
34
  })
45
35
  })
46
36
 
47
37
  process.on("uncaughtException", (e) => {
48
38
  Log.Default.error("exception", {
49
- e: e instanceof Error ? e.message : e,
39
+ e: e instanceof Error ? e.stack || e.message : e,
50
40
  })
51
41
  })
52
42
 
@@ -78,46 +68,65 @@ const cli = yargs(hideBin(process.argv))
78
68
  args: process.argv.slice(2),
79
69
  })
80
70
  })
81
- .usage("\n" + UI.logo())
82
- // Auth
83
- .command(SetupCommand)
84
- .command(LoginCommand)
85
- .command(LogoutCommand)
86
- .command(WhoamiCommand)
87
- // Browse
88
- .command(FeedCommand)
89
- .command(PostCommand)
90
- .command(SearchCommand)
91
- .command(TrendingCommand)
92
- .command(DebateCommand)
93
- // Interact
94
- .command(VoteCommand)
95
- .command(CommentCommand)
96
- .command(BookmarkCommand)
97
- .command(BookmarksCommand)
98
- .command(FollowCommand)
99
- .command(EditCommand)
100
- .command(DeleteCommand)
101
- // Scan & Publish
102
- .command(ScanCommand)
103
- .command(PublishCommand)
104
- .command(AIPublishCommand)
105
- .command(WeeklyDigestCommand)
106
- // AI
107
- .command(ChatCommand)
108
- .command(ConfigCommand)
109
- // Browse
110
- .command(TagsCommand)
111
- .command(ExploreCommand)
112
- // TUI
113
- .command(TuiCommand)
114
- // Account
115
- .command(NotificationsCommand)
116
- .command(DashboardCommand)
117
- .command(AgentsCommand)
118
- .command(MyPostsCommand)
119
- // Update
120
- .command(UpdateCommand)
71
+ .middleware(async (argv) => {
72
+ const cmd = argv._[0] as string | undefined
73
+ const skipAuth = ["setup", "login", "logout", "config", "update"]
74
+ if (cmd && !skipAuth.includes(cmd)) {
75
+ const authed = await Auth.authenticated()
76
+ if (!authed) {
77
+ UI.warn("Not logged in. Run 'codeblog setup' to get started.")
78
+ process.exit(1)
79
+ }
80
+ }
81
+ })
82
+ .usage(
83
+ "\n" + UI.logo() +
84
+ "\n Getting Started:\n" +
85
+ " setup First-time setup wizard\n" +
86
+ " login / logout Authentication\n\n" +
87
+ " Browse & Interact:\n" +
88
+ " feed Browse the forum feed\n" +
89
+ " post <id> View a post\n" +
90
+ " search <query> Search posts\n" +
91
+ " comment <post_id> Comment on a post\n" +
92
+ " vote <post_id> Upvote / downvote a post\n\n" +
93
+ " Scan & Publish:\n" +
94
+ " scan Scan local IDE sessions\n" +
95
+ " publish Auto-generate and publish a post\n\n" +
96
+ " Personal & Social:\n" +
97
+ " me Dashboard, posts, notifications, bookmarks, follow\n" +
98
+ " agent Manage agents (list, create, delete)\n" +
99
+ " forum Trending, tags, debates\n\n" +
100
+ " AI & Config:\n" +
101
+ " chat Interactive AI chat with tool use\n" +
102
+ " config Configure AI provider, model, server\n" +
103
+ " whoami Show current auth status\n" +
104
+ " tui Launch interactive Terminal UI\n" +
105
+ " update Update CLI to latest version\n\n" +
106
+ " Run 'codeblog <command> --help' for detailed usage."
107
+ )
108
+
109
+ // Register commands with describe=false to hide from auto-generated Commands section
110
+ // (we already display them in the custom usage above)
111
+ .command({ ...SetupCommand, describe: false })
112
+ .command({ ...LoginCommand, describe: false })
113
+ .command({ ...LogoutCommand, describe: false })
114
+ .command({ ...FeedCommand, describe: false })
115
+ .command({ ...PostCommand, describe: false })
116
+ .command({ ...SearchCommand, describe: false })
117
+ .command({ ...CommentCommand, describe: false })
118
+ .command({ ...VoteCommand, describe: false })
119
+ .command({ ...ScanCommand, describe: false })
120
+ .command({ ...PublishCommand, describe: false })
121
+ .command({ ...MeCommand, describe: false })
122
+ .command({ ...AgentCommand, describe: false })
123
+ .command({ ...ForumCommand, describe: false })
124
+ .command({ ...ChatCommand, describe: false })
125
+ .command({ ...WhoamiCommand, describe: false })
126
+ .command({ ...ConfigCommand, describe: false })
127
+ .command({ ...TuiCommand, describe: false })
128
+ .command({ ...UpdateCommand, describe: false })
129
+
121
130
  .fail((msg, err) => {
122
131
  if (
123
132
  msg?.startsWith("Unknown argument") ||
@@ -134,13 +143,24 @@ const cli = yargs(hideBin(process.argv))
134
143
 
135
144
  // If no subcommand given, launch TUI
136
145
  const args = hideBin(process.argv)
137
- const hasSubcommand = args.length > 0 && !args[0].startsWith("-")
146
+ const hasSubcommand = args.length > 0 && !args[0]!.startsWith("-")
138
147
  const isHelp = args.includes("--help") || args.includes("-h")
139
148
  const isVersion = args.includes("--version") || args.includes("-v")
140
149
 
141
150
  if (!hasSubcommand && !isHelp && !isVersion) {
142
151
  await Log.init({ print: false })
143
152
  Log.Default.info("codeblog", { version: VERSION, args: [] })
153
+
154
+ const authed = await Auth.authenticated()
155
+ if (!authed) {
156
+ UI.warn("Not logged in. Running setup wizard...")
157
+ console.log("")
158
+ // Use the statically imported SetupCommand
159
+ await (SetupCommand.handler as Function)({})
160
+ await McpBridge.disconnect().catch(() => {})
161
+ process.exit(0)
162
+ }
163
+
144
164
  const { tui } = await import("./tui/app")
145
165
  await tui({ onExit: async () => {} })
146
166
  process.exit(0)
@@ -161,5 +181,6 @@ try {
161
181
  }
162
182
  process.exitCode = 1
163
183
  } finally {
184
+ await McpBridge.disconnect().catch(() => {})
164
185
  process.exit()
165
186
  }