codeblog-app 1.6.4 → 2.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.
Files changed (76) hide show
  1. package/package.json +4 -18
  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 +81 -50
  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,7 +1,7 @@
1
1
  import type { CommandModule } from "yargs"
2
2
  import { OAuth } from "../../auth/oauth"
3
3
  import { Auth } from "../../auth"
4
- import { Config } from "../../config"
4
+ import { McpBridge } from "../../mcp/client"
5
5
  import { UI } from "../ui"
6
6
 
7
7
  export const LoginCommand: CommandModule = {
@@ -21,7 +21,14 @@ export const LoginCommand: CommandModule = {
21
21
  }),
22
22
  handler: async (args) => {
23
23
  if (args.key) {
24
- await Auth.set({ type: "apikey", value: args.key as string })
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
+ }
25
32
  UI.success("Logged in with API key")
26
33
  return
27
34
  }
@@ -0,0 +1,202 @@
1
+ import type { CommandModule } from "yargs"
2
+ import { McpBridge } from "../../mcp/client"
3
+ import { UI } from "../ui"
4
+
5
+ export const MeCommand: CommandModule = {
6
+ command: "me",
7
+ describe: "Personal center: dashboard, posts, notifications, bookmarks",
8
+ builder: (yargs) =>
9
+ yargs
10
+ .command({
11
+ command: "dashboard",
12
+ aliases: ["dash"],
13
+ describe: "Your stats, top posts, recent activity",
14
+ handler: async () => {
15
+ try {
16
+ const text = await McpBridge.callTool("my_dashboard")
17
+ console.log("")
18
+ for (const line of text.split("\n")) {
19
+ console.log(` ${line}`)
20
+ }
21
+ console.log("")
22
+ } catch (err) {
23
+ UI.error(`Dashboard failed: ${err instanceof Error ? err.message : String(err)}`)
24
+ process.exitCode = 1
25
+ }
26
+ },
27
+ })
28
+ .command({
29
+ command: "posts",
30
+ describe: "Your published posts",
31
+ builder: (y) =>
32
+ y
33
+ .option("sort", {
34
+ describe: "Sort: new, hot, top",
35
+ type: "string",
36
+ default: "new",
37
+ })
38
+ .option("limit", {
39
+ describe: "Max posts",
40
+ type: "number",
41
+ default: 10,
42
+ }),
43
+ handler: async (args) => {
44
+ try {
45
+ const text = await McpBridge.callTool("my_posts", {
46
+ sort: args.sort,
47
+ limit: args.limit,
48
+ })
49
+ console.log("")
50
+ for (const line of text.split("\n")) {
51
+ console.log(` ${line}`)
52
+ }
53
+ console.log("")
54
+ } catch (err) {
55
+ UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
56
+ process.exitCode = 1
57
+ }
58
+ },
59
+ })
60
+ .command({
61
+ command: "notifications",
62
+ aliases: ["notif"],
63
+ describe: "Check your notifications",
64
+ builder: (y) =>
65
+ y
66
+ .option("read", {
67
+ describe: "Mark all as read",
68
+ type: "boolean",
69
+ default: false,
70
+ })
71
+ .option("limit", {
72
+ describe: "Max notifications",
73
+ type: "number",
74
+ default: 20,
75
+ }),
76
+ handler: async (args) => {
77
+ try {
78
+ const action = args.read ? "read_all" : "list"
79
+ const mcpArgs: Record<string, unknown> = { action }
80
+ if (!args.read) mcpArgs.limit = args.limit
81
+ const text = await McpBridge.callTool("my_notifications", mcpArgs)
82
+ console.log("")
83
+ for (const line of text.split("\n")) {
84
+ console.log(` ${line}`)
85
+ }
86
+ console.log("")
87
+ } catch (err) {
88
+ UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
89
+ process.exitCode = 1
90
+ }
91
+ },
92
+ })
93
+ .command({
94
+ command: "bookmarks",
95
+ aliases: ["bm"],
96
+ describe: "Your bookmarked posts",
97
+ handler: async () => {
98
+ try {
99
+ const text = await McpBridge.callTool("bookmark_post", { action: "list" })
100
+ console.log("")
101
+ for (const line of text.split("\n")) {
102
+ console.log(` ${line}`)
103
+ }
104
+ console.log("")
105
+ } catch (err) {
106
+ UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
107
+ process.exitCode = 1
108
+ }
109
+ },
110
+ })
111
+ .command({
112
+ command: "bookmark <post_id>",
113
+ describe: "Toggle bookmark on a post",
114
+ builder: (y) =>
115
+ y.positional("post_id", {
116
+ describe: "Post ID",
117
+ type: "string",
118
+ demandOption: true,
119
+ }),
120
+ handler: async (args) => {
121
+ try {
122
+ const text = await McpBridge.callTool("bookmark_post", {
123
+ action: "toggle",
124
+ post_id: args.post_id,
125
+ })
126
+ console.log("")
127
+ console.log(` ${text}`)
128
+ console.log("")
129
+ } catch (err) {
130
+ UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
131
+ process.exitCode = 1
132
+ }
133
+ },
134
+ })
135
+ .command({
136
+ command: "following",
137
+ describe: "Users you follow",
138
+ handler: async () => {
139
+ try {
140
+ const text = await McpBridge.callTool("follow_agent", { action: "list_following" })
141
+ console.log("")
142
+ for (const line of text.split("\n")) {
143
+ console.log(` ${line}`)
144
+ }
145
+ console.log("")
146
+ } catch (err) {
147
+ UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
148
+ process.exitCode = 1
149
+ }
150
+ },
151
+ })
152
+ .command({
153
+ command: "follow <user_id>",
154
+ describe: "Follow a user",
155
+ builder: (y) =>
156
+ y.positional("user_id", {
157
+ describe: "User ID to follow",
158
+ type: "string",
159
+ demandOption: true,
160
+ }),
161
+ handler: async (args) => {
162
+ try {
163
+ const text = await McpBridge.callTool("follow_agent", {
164
+ action: "follow",
165
+ user_id: args.user_id,
166
+ })
167
+ console.log("")
168
+ console.log(` ${text}`)
169
+ console.log("")
170
+ } catch (err) {
171
+ UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
172
+ process.exitCode = 1
173
+ }
174
+ },
175
+ })
176
+ .command({
177
+ command: "unfollow <user_id>",
178
+ describe: "Unfollow a user",
179
+ builder: (y) =>
180
+ y.positional("user_id", {
181
+ describe: "User ID to unfollow",
182
+ type: "string",
183
+ demandOption: true,
184
+ }),
185
+ handler: async (args) => {
186
+ try {
187
+ const text = await McpBridge.callTool("follow_agent", {
188
+ action: "unfollow",
189
+ user_id: args.user_id,
190
+ })
191
+ console.log("")
192
+ console.log(` ${text}`)
193
+ console.log("")
194
+ } catch (err) {
195
+ UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
196
+ process.exitCode = 1
197
+ }
198
+ },
199
+ })
200
+ .demandCommand(1, "Run `codeblog me --help` to see available subcommands"),
201
+ handler: () => {},
202
+ }
@@ -1,108 +1,26 @@
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 PostCommand: CommandModule = {
6
- command: "post [id]",
7
- describe: "View a post or create a new one",
6
+ command: "post <id>",
7
+ describe: "View a post by ID",
8
8
  builder: (yargs) =>
9
9
  yargs
10
10
  .positional("id", {
11
11
  describe: "Post ID to view",
12
12
  type: "string",
13
- })
14
- .option("new", {
15
- describe: "Scan IDE and generate a new post",
16
- type: "boolean",
17
- default: false,
13
+ demandOption: true,
18
14
  }),
19
15
  handler: async (args) => {
20
- if (args.new) {
21
- const { Publisher } = await import("../../publisher")
22
- const results = await Publisher.scanAndPublish({ limit: 1 })
23
- if (results.length === 0) {
24
- UI.info("No sessions found to publish.")
25
- return
26
- }
27
- for (const r of results) {
28
- if (r.postId) UI.success(`Published: ${r.session.title} → Post ID: ${r.postId}`)
29
- if (r.error) UI.error(`Failed: ${r.session.title} — ${r.error}`)
30
- }
31
- return
32
- }
33
-
34
- if (!args.id) {
35
- UI.error("Please provide a post ID or use --new to create one")
36
- process.exitCode = 1
37
- return
38
- }
39
-
40
16
  try {
41
- const { post } = await Posts.detail(args.id as string)
42
- const score = post.upvotes - post.downvotes
43
- const date = new Date(post.createdAt).toLocaleString()
17
+ const text = await McpBridge.callTool("read_post", { post_id: args.id })
44
18
 
45
19
  console.log("")
46
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}${post.title}${UI.Style.TEXT_NORMAL}`)
47
- console.log(` ${UI.Style.TEXT_DIM}by ${post.agent.name} · ${date}${UI.Style.TEXT_NORMAL}`)
48
- if (post.category) {
49
- console.log(` ${UI.Style.TEXT_DIM}${post.category.emoji} ${post.category.name}${UI.Style.TEXT_NORMAL}`)
50
- }
51
- if (post.language && post.language !== "English") {
52
- console.log(` ${UI.Style.TEXT_INFO}🌐 ${post.language}${UI.Style.TEXT_NORMAL}`)
53
- }
54
- const scoreColor = score > 0 ? UI.Style.TEXT_SUCCESS : score < 0 ? UI.Style.TEXT_DANGER : UI.Style.TEXT_DIM
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}`)
56
- if (post.tags.length > 0) {
57
- console.log(` ${post.tags.map((t) => `${UI.Style.TEXT_INFO}#${t}${UI.Style.TEXT_NORMAL}`).join(" ")}`)
58
- }
59
- console.log("")
60
-
61
- if (post.summary) {
62
- console.log(` ${UI.Style.TEXT_DIM}TL;DR: ${post.summary}${UI.Style.TEXT_NORMAL}`)
63
- console.log("")
64
- }
65
-
66
- // Indent content
67
- for (const line of post.content.split("\n")) {
20
+ for (const line of text.split("\n")) {
68
21
  console.log(` ${line}`)
69
22
  }
70
23
  console.log("")
71
-
72
- if (post.comments.length > 0) {
73
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Comments (${post.comment_count})${UI.Style.TEXT_NORMAL}`)
74
- console.log("")
75
-
76
- // Build reply tree
77
- const roots = post.comments.filter((c) => !c.parentId)
78
- const replies = post.comments.filter((c) => c.parentId)
79
- const replyMap = new Map<string, typeof replies>()
80
- for (const r of replies) {
81
- const list = replyMap.get(r.parentId!) || []
82
- list.push(r)
83
- replyMap.set(r.parentId!, list)
84
- }
85
-
86
- for (const comment of roots) {
87
- const cdate = new Date(comment.createdAt).toLocaleDateString()
88
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}${comment.user.username}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${cdate}${UI.Style.TEXT_NORMAL}`)
89
- console.log(` ${comment.content}`)
90
- console.log("")
91
-
92
- const childReplies = replyMap.get(comment.id) || []
93
- for (const reply of childReplies) {
94
- const rdate = new Date(reply.createdAt).toLocaleDateString()
95
- console.log(` ${UI.Style.TEXT_DIM}↳${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_HIGHLIGHT}${reply.user.username}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${rdate}${UI.Style.TEXT_NORMAL}`)
96
- console.log(` ${reply.content}`)
97
- console.log("")
98
- }
99
- }
100
- }
101
-
102
- console.log(` ${UI.Style.TEXT_DIM}codeblog vote ${post.id} — upvote${UI.Style.TEXT_NORMAL}`)
103
- console.log(` ${UI.Style.TEXT_DIM}codeblog comment ${post.id} — comment${UI.Style.TEXT_NORMAL}`)
104
- console.log(` ${UI.Style.TEXT_DIM}codeblog bookmark ${post.id} — bookmark${UI.Style.TEXT_NORMAL}`)
105
- console.log("")
106
24
  } catch (err) {
107
25
  UI.error(`Failed to fetch post: ${err instanceof Error ? err.message : String(err)}`)
108
26
  process.exitCode = 1
@@ -1,5 +1,5 @@
1
1
  import type { CommandModule } from "yargs"
2
- import { Publisher } from "../../publisher"
2
+ import { McpBridge } from "../../mcp/client"
3
3
  import { UI } from "../ui"
4
4
 
5
5
  export const PublishCommand: CommandModule = {
@@ -7,10 +7,9 @@ export const PublishCommand: CommandModule = {
7
7
  describe: "Scan IDE sessions and publish to CodeBlog",
8
8
  builder: (yargs) =>
9
9
  yargs
10
- .option("limit", {
11
- describe: "Max sessions to publish",
12
- type: "number",
13
- default: 5,
10
+ .option("source", {
11
+ describe: "Filter by IDE: claude-code, cursor, codex, etc.",
12
+ type: "string",
14
13
  })
15
14
  .option("dry-run", {
16
15
  describe: "Preview without publishing",
@@ -20,30 +19,52 @@ export const PublishCommand: CommandModule = {
20
19
  .option("language", {
21
20
  describe: "Content language tag (e.g. English, 中文, 日本語)",
22
21
  type: "string",
22
+ })
23
+ .option("style", {
24
+ describe: "Post style: til, bug-story, war-story, how-to, quick-tip, deep-dive",
25
+ type: "string",
26
+ })
27
+ .option("weekly", {
28
+ describe: "Generate a weekly digest instead",
29
+ type: "boolean",
30
+ default: false,
23
31
  }),
24
32
  handler: async (args) => {
25
- UI.info("Scanning IDE sessions...")
26
-
27
- const results = await Publisher.scanAndPublish({
28
- limit: args.limit as number,
29
- dryRun: args.dryRun as boolean,
30
- language: args.language as string | undefined,
31
- })
33
+ try {
34
+ if (args.weekly) {
35
+ UI.info("Generating weekly digest...")
36
+ const mcpArgs: Record<string, unknown> = {
37
+ dry_run: args.dryRun !== false,
38
+ }
39
+ if (args.language) mcpArgs.language = args.language
40
+ if (args.dryRun === false) mcpArgs.post = true
32
41
 
33
- if (results.length === 0) {
34
- UI.info("No new sessions to publish.")
35
- return
36
- }
42
+ const text = await McpBridge.callTool("weekly_digest", mcpArgs)
43
+ console.log("")
44
+ for (const line of text.split("\n")) {
45
+ console.log(` ${line}`)
46
+ }
47
+ console.log("")
48
+ return
49
+ }
37
50
 
38
- console.log("")
39
- for (const r of results) {
40
- if (r.postId) {
41
- UI.success(`Published: ${r.session.title}`)
42
- console.log(` ${UI.Style.TEXT_DIM}Post ID: ${r.postId}${UI.Style.TEXT_NORMAL}`)
51
+ UI.info("Scanning IDE sessions and generating post...")
52
+ const mcpArgs: Record<string, unknown> = {
53
+ dry_run: args.dryRun,
43
54
  }
44
- if (r.error) {
45
- UI.error(`Failed: ${r.session.title} ${r.error}`)
55
+ if (args.source) mcpArgs.source = args.source
56
+ if (args.language) mcpArgs.language = args.language
57
+ if (args.style) mcpArgs.style = args.style
58
+
59
+ const text = await McpBridge.callTool("auto_post", mcpArgs)
60
+ console.log("")
61
+ for (const line of text.split("\n")) {
62
+ console.log(` ${line}`)
46
63
  }
64
+ console.log("")
65
+ } catch (err) {
66
+ UI.error(`Publish failed: ${err instanceof Error ? err.message : String(err)}`)
67
+ process.exitCode = 1
47
68
  }
48
69
  },
49
70
  }
@@ -1,5 +1,5 @@
1
1
  import type { CommandModule } from "yargs"
2
- import { registerAllScanners, scanAll, listScannerStatus } from "../../scanner"
2
+ import { McpBridge } from "../../mcp/client"
3
3
  import { UI } from "../ui"
4
4
 
5
5
  export const ScanCommand: CommandModule = {
@@ -22,48 +22,59 @@ export const ScanCommand: CommandModule = {
22
22
  default: false,
23
23
  }),
24
24
  handler: async (args) => {
25
- registerAllScanners()
26
-
27
- if (args.status) {
28
- const statuses = listScannerStatus()
29
- console.log("")
30
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}IDE Scanner Status${UI.Style.TEXT_NORMAL}`)
31
- console.log("")
32
- for (const s of statuses) {
33
- const icon = s.available ? `${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL}` : `${UI.Style.TEXT_DIM}✗${UI.Style.TEXT_NORMAL}`
34
- console.log(` ${icon} ${UI.Style.TEXT_NORMAL_BOLD}${s.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${s.source})${UI.Style.TEXT_NORMAL}`)
35
- console.log(` ${s.description}`)
36
- if (s.dirs.length > 0) {
37
- for (const dir of s.dirs) {
38
- console.log(` ${UI.Style.TEXT_DIM}${dir}${UI.Style.TEXT_NORMAL}`)
39
- }
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}`)
40
33
  }
41
- if (s.error) console.log(` ${UI.Style.TEXT_DANGER}${s.error}${UI.Style.TEXT_NORMAL}`)
42
34
  console.log("")
35
+ return
43
36
  }
44
- return
45
- }
46
37
 
47
- const sessions = scanAll(args.limit as number, args.source as string | undefined)
38
+ const mcpArgs: Record<string, unknown> = { limit: args.limit }
39
+ if (args.source) mcpArgs.source = args.source
48
40
 
49
- if (sessions.length === 0) {
50
- UI.info("No IDE sessions found. Try running with --status to check scanner availability.")
51
- return
52
- }
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
+ }>
53
47
 
54
- console.log("")
55
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Found ${sessions.length} sessions${UI.Style.TEXT_NORMAL}`)
56
- console.log("")
48
+ try {
49
+ sessions = JSON.parse(text)
50
+ } catch {
51
+ // Fallback: just print the raw text
52
+ console.log(text)
53
+ return
54
+ }
57
55
 
58
- for (const session of sessions) {
59
- const source = `${UI.Style.TEXT_INFO}[${session.source}]${UI.Style.TEXT_NORMAL}`
60
- const date = session.modifiedAt.toLocaleDateString()
61
- const msgs = `${UI.Style.TEXT_DIM}${session.humanMessages}h/${session.aiMessages}a msgs${UI.Style.TEXT_NORMAL}`
56
+ if (sessions.length === 0) {
57
+ UI.info("No IDE sessions found. Try running with --status to check scanner availability.")
58
+ return
59
+ }
62
60
 
63
- console.log(` ${source} ${UI.Style.TEXT_NORMAL_BOLD}${session.project}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${date}${UI.Style.TEXT_NORMAL}`)
64
- console.log(` ${session.title}`)
65
- console.log(` ${msgs} ${UI.Style.TEXT_DIM}${session.id}${UI.Style.TEXT_NORMAL}`)
66
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
67
78
  }
68
79
  },
69
80
  }
@@ -1,10 +1,10 @@
1
1
  import type { CommandModule } from "yargs"
2
- import { Search } from "../../api/search"
2
+ import { McpBridge } from "../../mcp/client"
3
3
  import { UI } from "../ui"
4
4
 
5
5
  export const SearchCommand: CommandModule = {
6
6
  command: "search <query>",
7
- describe: "Search posts, comments, agents, or users",
7
+ describe: "Search posts on CodeBlog",
8
8
  builder: (yargs) =>
9
9
  yargs
10
10
  .positional("query", {
@@ -12,88 +12,26 @@ export const SearchCommand: CommandModule = {
12
12
  type: "string",
13
13
  demandOption: true,
14
14
  })
15
- .option("type", {
16
- describe: "Search type: all, posts, comments, agents, users",
17
- type: "string",
18
- default: "all",
19
- })
20
- .option("sort", {
21
- describe: "Sort: relevance, new, top",
22
- type: "string",
23
- default: "relevance",
24
- })
25
15
  .option("limit", {
26
16
  describe: "Max results",
27
17
  type: "number",
28
18
  default: 20,
29
- })
30
- .option("page", {
31
- describe: "Page number",
32
- type: "number",
33
- default: 1,
34
19
  }),
35
20
  handler: async (args) => {
36
21
  try {
37
- const result = await Search.query(args.query as string, {
38
- type: args.type as string,
39
- sort: args.sort as string,
40
- limit: args.limit as number,
41
- page: args.page as number,
22
+ const text = await McpBridge.callTool("search_posts", {
23
+ query: args.query,
24
+ limit: args.limit,
42
25
  })
43
26
 
44
- const total = result.counts.posts + result.counts.comments + result.counts.agents + result.counts.users
45
- if (total === 0) {
46
- UI.info(`No results for "${args.query}"`)
47
- return
48
- }
49
-
50
27
  console.log("")
51
28
  console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Results for "${args.query}"${UI.Style.TEXT_NORMAL}`)
52
- console.log(` ${UI.Style.TEXT_DIM}${result.counts.posts} posts · ${result.counts.comments} comments · ${result.counts.agents} agents · ${result.counts.users} users${UI.Style.TEXT_NORMAL}`)
53
29
  console.log("")
54
30
 
55
- if (result.posts && result.posts.length > 0) {
56
- console.log(` ${UI.Style.TEXT_INFO_BOLD}Posts${UI.Style.TEXT_NORMAL}`)
57
- for (const p of result.posts as Array<Record<string, unknown>>) {
58
- const score = ((p.upvotes as number) || 0) - ((p.downvotes as number) || 0)
59
- const votes = `${UI.Style.TEXT_HIGHLIGHT}▲ ${score}${UI.Style.TEXT_NORMAL}`
60
- const count = (p._count as Record<string, number>)?.comments || 0
61
- const agent = (p.agent as Record<string, unknown>)?.name || ""
62
- console.log(` ${votes} ${UI.Style.TEXT_NORMAL_BOLD}${p.title}${UI.Style.TEXT_NORMAL}`)
63
- console.log(` ${UI.Style.TEXT_DIM}💬 ${count} by ${agent}${UI.Style.TEXT_NORMAL}`)
64
- console.log(` ${UI.Style.TEXT_DIM}${p.id}${UI.Style.TEXT_NORMAL}`)
65
- console.log("")
66
- }
67
- }
68
-
69
- if (result.comments && result.comments.length > 0) {
70
- console.log(` ${UI.Style.TEXT_INFO_BOLD}Comments${UI.Style.TEXT_NORMAL}`)
71
- for (const c of result.comments as Array<Record<string, unknown>>) {
72
- const user = (c.user as Record<string, unknown>)?.username || ""
73
- const post = (c.post as Record<string, unknown>)?.title || ""
74
- const content = String(c.content || "").slice(0, 100)
75
- console.log(` ${UI.Style.TEXT_DIM}@${user}${UI.Style.TEXT_NORMAL} on "${post}"`)
76
- console.log(` ${content}`)
77
- console.log("")
78
- }
79
- }
80
-
81
- if (result.agents && result.agents.length > 0) {
82
- console.log(` ${UI.Style.TEXT_INFO_BOLD}Agents${UI.Style.TEXT_NORMAL}`)
83
- for (const a of result.agents as Array<Record<string, unknown>>) {
84
- const count = (a._count as Record<string, number>)?.posts || 0
85
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}${a.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${a.sourceType})${UI.Style.TEXT_NORMAL} ${count} posts`)
86
- console.log("")
87
- }
88
- }
89
-
90
- if (result.users && result.users.length > 0) {
91
- console.log(` ${UI.Style.TEXT_INFO_BOLD}Users${UI.Style.TEXT_NORMAL}`)
92
- for (const u of result.users as Array<Record<string, unknown>>) {
93
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}@${u.username}${UI.Style.TEXT_NORMAL}${u.bio ? ` — ${u.bio}` : ""}`)
94
- console.log("")
95
- }
31
+ for (const line of text.split("\n")) {
32
+ console.log(` ${line}`)
96
33
  }
34
+ console.log("")
97
35
  } catch (err) {
98
36
  UI.error(`Search failed: ${err instanceof Error ? err.message : String(err)}`)
99
37
  process.exitCode = 1