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.
- package/package.json +4 -18
- package/src/ai/__tests__/chat.test.ts +110 -0
- package/src/ai/__tests__/provider.test.ts +184 -0
- package/src/ai/__tests__/tools.test.ts +90 -0
- package/src/ai/chat.ts +81 -50
- package/src/ai/provider.ts +24 -250
- package/src/ai/tools.ts +46 -281
- package/src/auth/oauth.ts +7 -0
- package/src/cli/__tests__/commands.test.ts +225 -0
- package/src/cli/__tests__/setup.test.ts +57 -0
- package/src/cli/cmd/agent.ts +102 -0
- package/src/cli/cmd/chat.ts +1 -1
- package/src/cli/cmd/comment.ts +47 -16
- package/src/cli/cmd/feed.ts +18 -30
- package/src/cli/cmd/forum.ts +123 -0
- package/src/cli/cmd/login.ts +9 -2
- package/src/cli/cmd/me.ts +202 -0
- package/src/cli/cmd/post.ts +6 -88
- package/src/cli/cmd/publish.ts +44 -23
- package/src/cli/cmd/scan.ts +45 -34
- package/src/cli/cmd/search.ts +8 -70
- package/src/cli/cmd/setup.ts +160 -62
- package/src/cli/cmd/vote.ts +29 -14
- package/src/cli/cmd/whoami.ts +7 -36
- package/src/cli/ui.ts +50 -0
- package/src/index.ts +80 -59
- package/src/mcp/__tests__/client.test.ts +149 -0
- package/src/mcp/__tests__/e2e.ts +327 -0
- package/src/mcp/__tests__/integration.ts +148 -0
- package/src/mcp/client.ts +148 -0
- package/src/api/agents.ts +0 -103
- package/src/api/bookmarks.ts +0 -25
- package/src/api/client.ts +0 -96
- package/src/api/debates.ts +0 -35
- package/src/api/feed.ts +0 -25
- package/src/api/notifications.ts +0 -31
- package/src/api/posts.ts +0 -116
- package/src/api/search.ts +0 -29
- package/src/api/tags.ts +0 -13
- package/src/api/trending.ts +0 -38
- package/src/api/users.ts +0 -8
- package/src/cli/cmd/agents.ts +0 -77
- package/src/cli/cmd/ai-publish.ts +0 -118
- package/src/cli/cmd/bookmark.ts +0 -27
- package/src/cli/cmd/bookmarks.ts +0 -42
- package/src/cli/cmd/dashboard.ts +0 -59
- package/src/cli/cmd/debate.ts +0 -89
- package/src/cli/cmd/delete.ts +0 -35
- package/src/cli/cmd/edit.ts +0 -42
- package/src/cli/cmd/explore.ts +0 -63
- package/src/cli/cmd/follow.ts +0 -34
- package/src/cli/cmd/myposts.ts +0 -50
- package/src/cli/cmd/notifications.ts +0 -65
- package/src/cli/cmd/tags.ts +0 -58
- package/src/cli/cmd/trending.ts +0 -64
- package/src/cli/cmd/weekly-digest.ts +0 -117
- package/src/publisher/index.ts +0 -139
- package/src/scanner/__tests__/analyzer.test.ts +0 -67
- package/src/scanner/__tests__/fs-utils.test.ts +0 -50
- package/src/scanner/__tests__/platform.test.ts +0 -27
- package/src/scanner/__tests__/registry.test.ts +0 -56
- package/src/scanner/aider.ts +0 -96
- package/src/scanner/analyzer.ts +0 -237
- package/src/scanner/claude-code.ts +0 -188
- package/src/scanner/codex.ts +0 -127
- package/src/scanner/continue-dev.ts +0 -95
- package/src/scanner/cursor.ts +0 -299
- package/src/scanner/fs-utils.ts +0 -123
- package/src/scanner/index.ts +0 -26
- package/src/scanner/platform.ts +0 -44
- package/src/scanner/registry.ts +0 -68
- package/src/scanner/types.ts +0 -62
- package/src/scanner/vscode-copilot.ts +0 -125
- package/src/scanner/warp.ts +0 -19
- package/src/scanner/windsurf.ts +0 -147
- package/src/scanner/zed.ts +0 -88
package/src/ai/tools.ts
CHANGED
|
@@ -1,30 +1,10 @@
|
|
|
1
|
-
import { tool } from "ai"
|
|
1
|
+
import { tool as _rawTool } from "ai"
|
|
2
2
|
import { z } from "zod"
|
|
3
|
-
import {
|
|
3
|
+
import { McpBridge } from "../mcp/client"
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
async function api(method: string, path: string, body?: unknown) {
|
|
9
|
-
const { Config } = await import("../config")
|
|
10
|
-
const { Auth } = await import("../auth")
|
|
11
|
-
const base = await Config.url()
|
|
12
|
-
const headers: Record<string, string> = { "Content-Type": "application/json" }
|
|
13
|
-
const auth = await Auth.header()
|
|
14
|
-
Object.assign(headers, auth)
|
|
15
|
-
const cfg = await Config.load()
|
|
16
|
-
if (cfg.api_key && !headers["Authorization"]) headers["Authorization"] = `Bearer ${cfg.api_key}`
|
|
17
|
-
const res = await fetch(`${base}${path}`, {
|
|
18
|
-
method,
|
|
19
|
-
headers,
|
|
20
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
21
|
-
})
|
|
22
|
-
if (!res.ok) {
|
|
23
|
-
const err = await res.text().catch(() => "")
|
|
24
|
-
throw new Error(`${res.status}: ${err}`)
|
|
25
|
-
}
|
|
26
|
-
return res.json()
|
|
27
|
-
}
|
|
5
|
+
// Workaround: zod v4 + AI SDK v6 tool() overloads don't match our MCP proxy pattern.
|
|
6
|
+
// This wrapper casts to `any` to avoid TS2769 while keeping runtime behavior intact.
|
|
7
|
+
const tool: any = _rawTool
|
|
28
8
|
|
|
29
9
|
// ---------------------------------------------------------------------------
|
|
30
10
|
// Tool display labels for the TUI streaming indicator
|
|
@@ -53,10 +33,25 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
53
33
|
my_posts: "Loading your posts...",
|
|
54
34
|
my_dashboard: "Loading dashboard...",
|
|
55
35
|
follow_user: "Processing follow...",
|
|
56
|
-
show_config: "Loading config...",
|
|
57
36
|
codeblog_status: "Checking status...",
|
|
58
37
|
}
|
|
59
38
|
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Helper: call MCP tool and return result
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
async function mcp(name: string, args: Record<string, unknown> = {}): Promise<any> {
|
|
43
|
+
return McpBridge.callToolJSON(name, args)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Strip undefined values from args before sending to MCP
|
|
47
|
+
function clean(obj: Record<string, unknown>): Record<string, unknown> {
|
|
48
|
+
const result: Record<string, unknown> = {}
|
|
49
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
50
|
+
if (v !== undefined && v !== null) result[k] = v
|
|
51
|
+
}
|
|
52
|
+
return result
|
|
53
|
+
}
|
|
54
|
+
|
|
60
55
|
// ---------------------------------------------------------------------------
|
|
61
56
|
// Session tools
|
|
62
57
|
// ---------------------------------------------------------------------------
|
|
@@ -66,22 +61,7 @@ const scan_sessions = tool({
|
|
|
66
61
|
limit: z.number().optional().describe("Max sessions to return (default 20)"),
|
|
67
62
|
source: z.string().optional().describe("Filter by source: claude-code, cursor, windsurf, codex, warp, vscode-copilot, aider, continue, zed"),
|
|
68
63
|
}),
|
|
69
|
-
execute: async (
|
|
70
|
-
const { registerAllScanners, scanAll } = await import("../scanner")
|
|
71
|
-
registerAllScanners()
|
|
72
|
-
const sessions = scanAll(limit || 20, source || undefined)
|
|
73
|
-
if (sessions.length === 0) return { count: 0, sessions: [], message: "No IDE sessions found." }
|
|
74
|
-
return {
|
|
75
|
-
count: sessions.length,
|
|
76
|
-
sessions: sessions.slice(0, 10).map((s) => ({
|
|
77
|
-
id: s.id, source: s.source, project: s.project, title: s.title,
|
|
78
|
-
messages: s.messageCount, human: s.humanMessages, ai: s.aiMessages,
|
|
79
|
-
preview: s.preview, modified: s.modifiedAt.toISOString(),
|
|
80
|
-
size: `${Math.round(s.sizeBytes / 1024)}KB`, path: s.filePath,
|
|
81
|
-
})),
|
|
82
|
-
message: `Found ${sessions.length} sessions`,
|
|
83
|
-
}
|
|
84
|
-
},
|
|
64
|
+
execute: async (args: any) => mcp("scan_sessions", clean(args)),
|
|
85
65
|
})
|
|
86
66
|
|
|
87
67
|
const read_session = tool({
|
|
@@ -91,19 +71,7 @@ const read_session = tool({
|
|
|
91
71
|
source: z.string().describe("Source type from scan_sessions (e.g. 'claude-code', 'cursor')"),
|
|
92
72
|
max_turns: z.number().optional().describe("Max conversation turns to read (default: all)"),
|
|
93
73
|
}),
|
|
94
|
-
execute: async (
|
|
95
|
-
const { parseSession } = await import("../scanner")
|
|
96
|
-
const parsed = parseSession(path, source, max_turns)
|
|
97
|
-
if (!parsed) return { error: "Could not parse session" }
|
|
98
|
-
return {
|
|
99
|
-
source: parsed.source, project: parsed.project, title: parsed.title,
|
|
100
|
-
messages: parsed.messageCount,
|
|
101
|
-
turns: parsed.turns.slice(0, 50).map((t) => ({
|
|
102
|
-
role: t.role, content: t.content.slice(0, 3000),
|
|
103
|
-
...(t.timestamp ? { time: t.timestamp.toISOString() } : {}),
|
|
104
|
-
})),
|
|
105
|
-
}
|
|
106
|
-
},
|
|
74
|
+
execute: async (args: any) => mcp("read_session", clean(args)),
|
|
107
75
|
})
|
|
108
76
|
|
|
109
77
|
const analyze_session = tool({
|
|
@@ -112,12 +80,7 @@ const analyze_session = tool({
|
|
|
112
80
|
path: z.string().describe("Absolute path to the session file"),
|
|
113
81
|
source: z.string().describe("Source type (e.g. 'claude-code', 'cursor')"),
|
|
114
82
|
}),
|
|
115
|
-
execute: async (
|
|
116
|
-
const { parseSession, analyzeSession } = await import("../scanner")
|
|
117
|
-
const parsed = parseSession(path, source)
|
|
118
|
-
if (!parsed || parsed.turns.length === 0) return { error: "Could not parse session" }
|
|
119
|
-
return analyzeSession(parsed)
|
|
120
|
-
},
|
|
83
|
+
execute: async (args: any) => mcp("analyze_session", clean(args)),
|
|
121
84
|
})
|
|
122
85
|
|
|
123
86
|
// ---------------------------------------------------------------------------
|
|
@@ -133,11 +96,7 @@ const post_to_codeblog = tool({
|
|
|
133
96
|
summary: z.string().optional().describe("One-line hook"),
|
|
134
97
|
category: z.string().optional().describe("Category: general, til, bugs, patterns, performance, tools"),
|
|
135
98
|
}),
|
|
136
|
-
execute: async (
|
|
137
|
-
const data = await api("POST", "/api/v1/posts", { title, content, source_session, tags, summary, category })
|
|
138
|
-
const { Config } = await import("../config")
|
|
139
|
-
return { message: "Posted!", post_id: data.post.id, url: `${await Config.url()}/post/${data.post.id}` }
|
|
140
|
-
},
|
|
99
|
+
execute: async (args: any) => mcp("post_to_codeblog", clean(args)),
|
|
141
100
|
})
|
|
142
101
|
|
|
143
102
|
const auto_post = tool({
|
|
@@ -147,16 +106,7 @@ const auto_post = tool({
|
|
|
147
106
|
style: z.enum(["til", "deep-dive", "bug-story", "code-review", "quick-tip", "war-story", "how-to", "opinion"]).optional().describe("Post style"),
|
|
148
107
|
dry_run: z.boolean().optional().describe("If true, preview without publishing"),
|
|
149
108
|
}),
|
|
150
|
-
execute: async (
|
|
151
|
-
const { Publisher } = await import("../publisher")
|
|
152
|
-
const results = await Publisher.scanAndPublish({ limit: 1, dryRun: dry_run || false })
|
|
153
|
-
const ok = results.filter((r) => r.postId)
|
|
154
|
-
return {
|
|
155
|
-
published: ok.length, total: results.length,
|
|
156
|
-
posts: ok.map((r) => ({ id: r.postId, source: r.session.source, project: r.session.project })),
|
|
157
|
-
message: ok.length > 0 ? `Published ${ok.length} post(s)` : "No sessions to publish",
|
|
158
|
-
}
|
|
159
|
-
},
|
|
109
|
+
execute: async (args: any) => mcp("auto_post", clean(args)),
|
|
160
110
|
})
|
|
161
111
|
|
|
162
112
|
const weekly_digest = tool({
|
|
@@ -165,31 +115,7 @@ const weekly_digest = tool({
|
|
|
165
115
|
dry_run: z.boolean().optional().describe("Preview without posting (default true)"),
|
|
166
116
|
post: z.boolean().optional().describe("Auto-post the digest"),
|
|
167
117
|
}),
|
|
168
|
-
execute: async (
|
|
169
|
-
const { registerAllScanners, scanAll, parseSession, analyzeSession } = await import("../scanner")
|
|
170
|
-
registerAllScanners()
|
|
171
|
-
const sessions = scanAll(50)
|
|
172
|
-
const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
|
|
173
|
-
const recent = sessions.filter((s) => s.modifiedAt >= cutoff)
|
|
174
|
-
if (recent.length === 0) return { message: "No sessions in the last 7 days." }
|
|
175
|
-
const projects = new Set<string>()
|
|
176
|
-
const languages = new Set<string>()
|
|
177
|
-
const topics = new Set<string>()
|
|
178
|
-
let totalMsgs = 0
|
|
179
|
-
for (const s of recent) {
|
|
180
|
-
projects.add(s.project); totalMsgs += s.messageCount
|
|
181
|
-
const parsed = parseSession(s.filePath, s.source, 30)
|
|
182
|
-
if (!parsed) continue
|
|
183
|
-
const a = analyzeSession(parsed)
|
|
184
|
-
a.languages.forEach((l) => languages.add(l))
|
|
185
|
-
a.topics.forEach((t) => topics.add(t))
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
sessions: recent.length, projects: [...projects], languages: [...languages],
|
|
189
|
-
topics: [...topics], total_messages: totalMsgs,
|
|
190
|
-
message: `${recent.length} sessions across ${projects.size} projects this week`,
|
|
191
|
-
}
|
|
192
|
-
},
|
|
118
|
+
execute: async (args: any) => mcp("weekly_digest", clean(args)),
|
|
193
119
|
})
|
|
194
120
|
|
|
195
121
|
// ---------------------------------------------------------------------------
|
|
@@ -202,21 +128,7 @@ const browse_posts = tool({
|
|
|
202
128
|
page: z.number().optional().describe("Page number (default 1)"),
|
|
203
129
|
limit: z.number().optional().describe("Posts per page (default 10)"),
|
|
204
130
|
}),
|
|
205
|
-
execute: async (
|
|
206
|
-
const qs = new URLSearchParams()
|
|
207
|
-
if (sort) qs.set("sort", sort)
|
|
208
|
-
if (page) qs.set("page", String(page))
|
|
209
|
-
qs.set("limit", String(limit || 10))
|
|
210
|
-
const data = await api("GET", `/api/v1/posts?${qs}`)
|
|
211
|
-
return {
|
|
212
|
-
count: data.posts.length,
|
|
213
|
-
posts: data.posts.map((p: any) => ({
|
|
214
|
-
id: p.id, title: p.title, summary: p.summary,
|
|
215
|
-
upvotes: p.upvotes, downvotes: p.downvotes,
|
|
216
|
-
comments: p.comment_count || 0, agent: p.author?.name,
|
|
217
|
-
})),
|
|
218
|
-
}
|
|
219
|
-
},
|
|
131
|
+
execute: async (args: any) => mcp("browse_posts", clean(args)),
|
|
220
132
|
})
|
|
221
133
|
|
|
222
134
|
const search_posts = tool({
|
|
@@ -225,30 +137,13 @@ const search_posts = tool({
|
|
|
225
137
|
query: z.string().describe("Search query"),
|
|
226
138
|
limit: z.number().optional().describe("Max results (default 10)"),
|
|
227
139
|
}),
|
|
228
|
-
execute: async (
|
|
229
|
-
const { Search } = await import("../api/search")
|
|
230
|
-
const result = await Search.query(query, { limit: limit || 10 })
|
|
231
|
-
return {
|
|
232
|
-
query, count: result.counts?.posts || 0,
|
|
233
|
-
posts: (result.posts || []).slice(0, 10).map((p: any) => ({
|
|
234
|
-
id: p.id, title: p.title, summary: p.summary, tags: p.tags, upvotes: p.upvotes,
|
|
235
|
-
})),
|
|
236
|
-
}
|
|
237
|
-
},
|
|
140
|
+
execute: async (args: any) => mcp("search_posts", clean(args)),
|
|
238
141
|
})
|
|
239
142
|
|
|
240
143
|
const read_post = tool({
|
|
241
144
|
description: "Read a post in full — content, comments, and discussion. Get the post ID from browse_posts or search_posts.",
|
|
242
145
|
parameters: z.object({ post_id: z.string().describe("Post ID to read") }),
|
|
243
|
-
execute: async (
|
|
244
|
-
const data = await api("GET", `/api/v1/posts/${post_id}`)
|
|
245
|
-
const p = data.post
|
|
246
|
-
return {
|
|
247
|
-
id: p.id, title: p.title, content: p.content?.slice(0, 5000),
|
|
248
|
-
upvotes: p.upvotes, downvotes: p.downvotes, views: p.views,
|
|
249
|
-
tags: p.tags, comments: p.comments,
|
|
250
|
-
}
|
|
251
|
-
},
|
|
146
|
+
execute: async (args: any) => mcp("read_post", clean(args)),
|
|
252
147
|
})
|
|
253
148
|
|
|
254
149
|
// ---------------------------------------------------------------------------
|
|
@@ -261,10 +156,7 @@ const comment_on_post = tool({
|
|
|
261
156
|
content: z.string().describe("Your comment (max 5000 chars)"),
|
|
262
157
|
parent_id: z.string().optional().describe("Reply to a specific comment by its ID"),
|
|
263
158
|
}),
|
|
264
|
-
execute: async (
|
|
265
|
-
const data = await api("POST", `/api/v1/posts/${post_id}/comment`, { content, parent_id })
|
|
266
|
-
return { message: "Comment posted!", comment_id: data.comment.id }
|
|
267
|
-
},
|
|
159
|
+
execute: async (args: any) => mcp("comment_on_post", clean(args)),
|
|
268
160
|
})
|
|
269
161
|
|
|
270
162
|
const vote_on_post = tool({
|
|
@@ -273,10 +165,7 @@ const vote_on_post = tool({
|
|
|
273
165
|
post_id: z.string().describe("Post ID to vote on"),
|
|
274
166
|
value: z.number().describe("1 for upvote, -1 for downvote, 0 to remove"),
|
|
275
167
|
}),
|
|
276
|
-
execute: async (
|
|
277
|
-
const data = await api("POST", `/api/v1/posts/${post_id}/vote`, { value })
|
|
278
|
-
return { message: data.message }
|
|
279
|
-
},
|
|
168
|
+
execute: async (args: any) => mcp("vote_on_post", clean(args)),
|
|
280
169
|
})
|
|
281
170
|
|
|
282
171
|
const edit_post = tool({
|
|
@@ -289,16 +178,7 @@ const edit_post = tool({
|
|
|
289
178
|
tags: z.array(z.string()).optional().describe("New tags"),
|
|
290
179
|
category: z.string().optional().describe("New category slug"),
|
|
291
180
|
}),
|
|
292
|
-
execute: async (
|
|
293
|
-
const body: Record<string, unknown> = {}
|
|
294
|
-
if (title) body.title = title
|
|
295
|
-
if (content) body.content = content
|
|
296
|
-
if (summary !== undefined) body.summary = summary
|
|
297
|
-
if (tags) body.tags = tags
|
|
298
|
-
if (category) body.category = category
|
|
299
|
-
const data = await api("PATCH", `/api/v1/posts/${post_id}`, body)
|
|
300
|
-
return { message: "Post updated!", title: data.post.title }
|
|
301
|
-
},
|
|
181
|
+
execute: async (args: any) => mcp("edit_post", clean(args)),
|
|
302
182
|
})
|
|
303
183
|
|
|
304
184
|
const delete_post = tool({
|
|
@@ -307,11 +187,7 @@ const delete_post = tool({
|
|
|
307
187
|
post_id: z.string().describe("Post ID to delete"),
|
|
308
188
|
confirm: z.boolean().describe("Must be true to confirm deletion"),
|
|
309
189
|
}),
|
|
310
|
-
execute: async (
|
|
311
|
-
if (!confirm) return { message: "Set confirm=true to actually delete." }
|
|
312
|
-
const data = await api("DELETE", `/api/v1/posts/${post_id}`)
|
|
313
|
-
return { message: data.message }
|
|
314
|
-
},
|
|
190
|
+
execute: async (args: any) => mcp("delete_post", clean(args)),
|
|
315
191
|
})
|
|
316
192
|
|
|
317
193
|
const bookmark_post = tool({
|
|
@@ -320,15 +196,7 @@ const bookmark_post = tool({
|
|
|
320
196
|
action: z.enum(["toggle", "list"]).describe("'toggle' = bookmark/unbookmark, 'list' = see all bookmarks"),
|
|
321
197
|
post_id: z.string().optional().describe("Post ID (required for toggle)"),
|
|
322
198
|
}),
|
|
323
|
-
execute: async (
|
|
324
|
-
if (action === "toggle") {
|
|
325
|
-
if (!post_id) return { error: "post_id required for toggle" }
|
|
326
|
-
const data = await api("POST", `/api/v1/posts/${post_id}/bookmark`)
|
|
327
|
-
return { message: data.message, bookmarked: data.bookmarked }
|
|
328
|
-
}
|
|
329
|
-
const data = await api("GET", "/api/v1/bookmarks")
|
|
330
|
-
return { count: data.bookmarks.length, bookmarks: data.bookmarks }
|
|
331
|
-
},
|
|
199
|
+
execute: async (args: any) => mcp("bookmark_post", clean(args)),
|
|
332
200
|
})
|
|
333
201
|
|
|
334
202
|
// ---------------------------------------------------------------------------
|
|
@@ -341,25 +209,13 @@ const browse_by_tag = tool({
|
|
|
341
209
|
tag: z.string().optional().describe("Tag to filter by (required for 'posts')"),
|
|
342
210
|
limit: z.number().optional().describe("Max results (default 10)"),
|
|
343
211
|
}),
|
|
344
|
-
execute: async (
|
|
345
|
-
if (action === "trending") {
|
|
346
|
-
const data = await api("GET", "/api/v1/tags")
|
|
347
|
-
return { tags: data.tags }
|
|
348
|
-
}
|
|
349
|
-
if (!tag) return { error: "tag required for 'posts' action" }
|
|
350
|
-
const qs = new URLSearchParams({ tag, limit: String(limit || 10) })
|
|
351
|
-
const data = await api("GET", `/api/v1/posts?${qs}`)
|
|
352
|
-
return { tag, count: data.posts.length, posts: data.posts.map((p: any) => ({ id: p.id, title: p.title, summary: p.summary, upvotes: p.upvotes })) }
|
|
353
|
-
},
|
|
212
|
+
execute: async (args: any) => mcp("browse_by_tag", clean(args)),
|
|
354
213
|
})
|
|
355
214
|
|
|
356
215
|
const trending_topics = tool({
|
|
357
216
|
description: "See what's hot on CodeBlog this week — top upvoted, most discussed, active agents, trending tags.",
|
|
358
217
|
parameters: z.object({}),
|
|
359
|
-
execute: async () =>
|
|
360
|
-
const data = await api("GET", "/api/v1/trending")
|
|
361
|
-
return data.trending
|
|
362
|
-
},
|
|
218
|
+
execute: async () => mcp("trending_topics"),
|
|
363
219
|
})
|
|
364
220
|
|
|
365
221
|
const explore_and_engage = tool({
|
|
@@ -368,22 +224,7 @@ const explore_and_engage = tool({
|
|
|
368
224
|
action: z.enum(["browse", "engage"]).describe("'browse' or 'engage'"),
|
|
369
225
|
limit: z.number().optional().describe("Number of posts (default 5)"),
|
|
370
226
|
}),
|
|
371
|
-
execute: async (
|
|
372
|
-
const qs = new URLSearchParams({ sort: "new", limit: String(limit || 5) })
|
|
373
|
-
const data = await api("GET", `/api/v1/posts?${qs}`)
|
|
374
|
-
const posts = data.posts || []
|
|
375
|
-
if (action === "browse") {
|
|
376
|
-
return { count: posts.length, posts: posts.map((p: any) => ({ id: p.id, title: p.title, summary: p.summary, upvotes: p.upvotes, comments: p.comment_count })) }
|
|
377
|
-
}
|
|
378
|
-
const detailed = []
|
|
379
|
-
for (const p of posts.slice(0, 5)) {
|
|
380
|
-
try {
|
|
381
|
-
const d = await api("GET", `/api/v1/posts/${p.id}`)
|
|
382
|
-
detailed.push({ id: p.id, title: d.post.title, content: d.post.content?.slice(0, 1500), comments: d.post.comment_count, views: d.post.views })
|
|
383
|
-
} catch { continue }
|
|
384
|
-
}
|
|
385
|
-
return { count: detailed.length, posts: detailed, message: "Read each post and use comment_on_post / vote_on_post to engage." }
|
|
386
|
-
},
|
|
227
|
+
execute: async (args: any) => mcp("explore_and_engage", clean(args)),
|
|
387
228
|
})
|
|
388
229
|
|
|
389
230
|
// ---------------------------------------------------------------------------
|
|
@@ -401,17 +242,7 @@ const join_debate = tool({
|
|
|
401
242
|
pro_label: z.string().optional().describe("Pro side label (for create)"),
|
|
402
243
|
con_label: z.string().optional().describe("Con side label (for create)"),
|
|
403
244
|
}),
|
|
404
|
-
execute: async (
|
|
405
|
-
if (action === "list") return { debates: (await api("GET", "/api/v1/debates")).debates }
|
|
406
|
-
if (action === "create") {
|
|
407
|
-
if (!title || !pro_label || !con_label) return { error: "title, pro_label, con_label required" }
|
|
408
|
-
const data = await api("POST", "/api/v1/debates", { action: "create", title, description, proLabel: pro_label, conLabel: con_label })
|
|
409
|
-
return { message: "Debate created!", debate: data.debate }
|
|
410
|
-
}
|
|
411
|
-
if (!debate_id || !side || !content) return { error: "debate_id, side, content required" }
|
|
412
|
-
const data = await api("POST", "/api/v1/debates", { debateId: debate_id, side, content })
|
|
413
|
-
return { message: "Argument submitted!", entry_id: data.entry.id }
|
|
414
|
-
},
|
|
245
|
+
execute: async (args: any) => mcp("join_debate", clean(args)),
|
|
415
246
|
})
|
|
416
247
|
|
|
417
248
|
// ---------------------------------------------------------------------------
|
|
@@ -423,13 +254,7 @@ const my_notifications = tool({
|
|
|
423
254
|
action: z.enum(["list", "read_all"]).describe("'list' = see notifications, 'read_all' = mark all as read"),
|
|
424
255
|
limit: z.number().optional().describe("Max notifications (default 20)"),
|
|
425
256
|
}),
|
|
426
|
-
execute: async (
|
|
427
|
-
if (action === "read_all") return { message: (await api("POST", "/api/v1/notifications/read", {})).message }
|
|
428
|
-
const qs = new URLSearchParams()
|
|
429
|
-
if (limit) qs.set("limit", String(limit))
|
|
430
|
-
const data = await api("GET", `/api/v1/notifications?${qs}`)
|
|
431
|
-
return { unread: data.unread_count, notifications: data.notifications }
|
|
432
|
-
},
|
|
257
|
+
execute: async (args: any) => mcp("my_notifications", clean(args)),
|
|
433
258
|
})
|
|
434
259
|
|
|
435
260
|
// ---------------------------------------------------------------------------
|
|
@@ -444,16 +269,7 @@ const manage_agents = tool({
|
|
|
444
269
|
source_type: z.string().optional().describe("IDE source (for create)"),
|
|
445
270
|
agent_id: z.string().optional().describe("Agent ID (for delete)"),
|
|
446
271
|
}),
|
|
447
|
-
execute: async (
|
|
448
|
-
if (action === "list") return { agents: (await api("GET", "/api/v1/agents/list")).agents }
|
|
449
|
-
if (action === "create") {
|
|
450
|
-
if (!name || !source_type) return { error: "name and source_type required" }
|
|
451
|
-
const data = await api("POST", "/api/v1/agents/create", { name, description, source_type })
|
|
452
|
-
return { message: "Agent created!", agent: data.agent }
|
|
453
|
-
}
|
|
454
|
-
if (!agent_id) return { error: "agent_id required" }
|
|
455
|
-
return { message: (await api("DELETE", `/api/v1/agents/${agent_id}`)).message }
|
|
456
|
-
},
|
|
272
|
+
execute: async (args: any) => mcp("manage_agents", clean(args)),
|
|
457
273
|
})
|
|
458
274
|
|
|
459
275
|
const my_posts = tool({
|
|
@@ -462,19 +278,13 @@ const my_posts = tool({
|
|
|
462
278
|
sort: z.enum(["new", "hot", "top"]).optional().describe("Sort order"),
|
|
463
279
|
limit: z.number().optional().describe("Max posts (default 10)"),
|
|
464
280
|
}),
|
|
465
|
-
execute: async (
|
|
466
|
-
const qs = new URLSearchParams()
|
|
467
|
-
if (sort) qs.set("sort", sort)
|
|
468
|
-
qs.set("limit", String(limit || 10))
|
|
469
|
-
const data = await api("GET", `/api/v1/agents/me/posts?${qs}`)
|
|
470
|
-
return { total: data.total, posts: data.posts }
|
|
471
|
-
},
|
|
281
|
+
execute: async (args: any) => mcp("my_posts", clean(args)),
|
|
472
282
|
})
|
|
473
283
|
|
|
474
284
|
const my_dashboard = tool({
|
|
475
285
|
description: "Your personal CodeBlog dashboard — total stats, top posts, recent comments.",
|
|
476
286
|
parameters: z.object({}),
|
|
477
|
-
execute: async () => (
|
|
287
|
+
execute: async () => mcp("my_dashboard"),
|
|
478
288
|
})
|
|
479
289
|
|
|
480
290
|
const follow_user = tool({
|
|
@@ -484,61 +294,16 @@ const follow_user = tool({
|
|
|
484
294
|
user_id: z.string().optional().describe("User ID (for follow/unfollow)"),
|
|
485
295
|
limit: z.number().optional().describe("Max results (default 10)"),
|
|
486
296
|
}),
|
|
487
|
-
execute: async (
|
|
488
|
-
if (action === "follow" || action === "unfollow") {
|
|
489
|
-
if (!user_id) return { error: "user_id required" }
|
|
490
|
-
const data = await api("POST", `/api/v1/users/${user_id}/follow`, { action })
|
|
491
|
-
return { message: data.message, following: data.following }
|
|
492
|
-
}
|
|
493
|
-
if (action === "feed") {
|
|
494
|
-
const data = await api("GET", `/api/v1/feed?limit=${limit || 10}`)
|
|
495
|
-
return { count: data.posts.length, posts: data.posts }
|
|
496
|
-
}
|
|
497
|
-
const me = await api("GET", "/api/v1/agents/me")
|
|
498
|
-
const uid = me.agent?.userId || me.userId
|
|
499
|
-
if (!uid) return { error: "Could not determine user ID" }
|
|
500
|
-
const data = await api("GET", `/api/v1/users/${uid}/follow?type=following`)
|
|
501
|
-
return { count: data.users.length, users: data.users }
|
|
502
|
-
},
|
|
297
|
+
execute: async (args: any) => mcp("follow_agent", clean(args)),
|
|
503
298
|
})
|
|
504
299
|
|
|
505
300
|
// ---------------------------------------------------------------------------
|
|
506
301
|
// Config & Status
|
|
507
302
|
// ---------------------------------------------------------------------------
|
|
508
|
-
const show_config = tool({
|
|
509
|
-
description: "Show current CodeBlog configuration — AI provider, model, login status.",
|
|
510
|
-
parameters: z.object({}),
|
|
511
|
-
execute: async () => {
|
|
512
|
-
const { Config } = await import("../config")
|
|
513
|
-
const { Auth } = await import("../auth")
|
|
514
|
-
const cfg = await Config.load()
|
|
515
|
-
const authenticated = await Auth.authenticated()
|
|
516
|
-
const token = authenticated ? await Auth.get() : null
|
|
517
|
-
return {
|
|
518
|
-
model: cfg.model || AIProvider.DEFAULT_MODEL,
|
|
519
|
-
providers: Object.keys(cfg.providers || {}),
|
|
520
|
-
logged_in: authenticated,
|
|
521
|
-
username: token?.username || null,
|
|
522
|
-
api_url: cfg.api_url,
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
|
-
})
|
|
526
|
-
|
|
527
303
|
const codeblog_status = tool({
|
|
528
304
|
description: "Health check — see if CodeBlog is set up, which IDEs are detected, and agent status.",
|
|
529
305
|
parameters: z.object({}),
|
|
530
|
-
execute: async () =>
|
|
531
|
-
const { registerAllScanners, listScannerStatus } = await import("../scanner")
|
|
532
|
-
registerAllScanners()
|
|
533
|
-
const scanners = listScannerStatus()
|
|
534
|
-
const { Auth } = await import("../auth")
|
|
535
|
-
return {
|
|
536
|
-
platform: process.platform,
|
|
537
|
-
scanners: scanners.map((s) => ({ name: s.name, source: s.source, available: s.available, dirs: s.dirs?.length || 0 })),
|
|
538
|
-
logged_in: await Auth.authenticated(),
|
|
539
|
-
cwd: process.cwd(),
|
|
540
|
-
}
|
|
541
|
-
},
|
|
306
|
+
execute: async () => mcp("codeblog_status"),
|
|
542
307
|
})
|
|
543
308
|
|
|
544
309
|
// ---------------------------------------------------------------------------
|
|
@@ -552,5 +317,5 @@ export const chatTools = {
|
|
|
552
317
|
browse_by_tag, trending_topics, explore_and_engage, join_debate,
|
|
553
318
|
my_notifications,
|
|
554
319
|
manage_agents, my_posts, my_dashboard, follow_user,
|
|
555
|
-
|
|
320
|
+
codeblog_status,
|
|
556
321
|
}
|
package/src/auth/oauth.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Auth } from "./index"
|
|
2
2
|
import { Config } from "../config"
|
|
3
|
+
import { McpBridge } from "../mcp/client"
|
|
3
4
|
import { Server } from "../server"
|
|
4
5
|
import { Log } from "../util/log"
|
|
5
6
|
|
|
@@ -17,6 +18,12 @@ export namespace OAuth {
|
|
|
17
18
|
|
|
18
19
|
if (key) {
|
|
19
20
|
await Auth.set({ type: "apikey", value: key, username })
|
|
21
|
+
// Sync API key to MCP config (~/.codeblog/config.json)
|
|
22
|
+
try {
|
|
23
|
+
await McpBridge.callTool("codeblog_setup", { api_key: key })
|
|
24
|
+
} catch (err) {
|
|
25
|
+
log.warn("failed to sync API key to MCP config", { error: String(err) })
|
|
26
|
+
}
|
|
20
27
|
log.info("authenticated with api key")
|
|
21
28
|
} else if (token) {
|
|
22
29
|
await Auth.set({ type: "jwt", value: token, username })
|