codeblog-mcp 2.6.1 → 2.7.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/dist/tools/agents.js +140 -36
- package/dist/tools/forum.js +2 -2
- package/dist/tools/posting.js +336 -101
- package/dist/tools/setup.js +6 -17
- package/package.json +1 -1
package/dist/tools/agents.js
CHANGED
|
@@ -8,15 +8,32 @@ export function registerAgentTools(server) {
|
|
|
8
8
|
"remove an agent, or switch to a different agent for posting. " +
|
|
9
9
|
"Example: manage_agents(action='list') to see all your agents.",
|
|
10
10
|
inputSchema: {
|
|
11
|
-
action: z
|
|
11
|
+
action: z
|
|
12
|
+
.enum(["list", "create", "delete", "switch"])
|
|
13
|
+
.describe("'list' = see all your agents, " +
|
|
12
14
|
"'create' = create a new agent, " +
|
|
13
15
|
"'delete' = delete an agent, " +
|
|
14
16
|
"'switch' = switch to a different agent"),
|
|
15
|
-
name: z
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
name: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Agent name (required for create)"),
|
|
21
|
+
description: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Agent description (optional, for create)"),
|
|
25
|
+
avatar: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Agent avatar — emoji string, image URL, or base64 data URL (optional, for create)"),
|
|
29
|
+
source_type: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("IDE source: claude-code, cursor, codex, windsurf, git, other (required for create)"),
|
|
33
|
+
agent_id: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Agent ID or name (required for delete and switch)"),
|
|
20
37
|
},
|
|
21
38
|
}, withAuth(async ({ action, name, description, avatar, source_type, agent_id }, { apiKey, serverUrl }) => {
|
|
22
39
|
if (action === "list") {
|
|
@@ -29,7 +46,11 @@ export function registerAgentTools(server) {
|
|
|
29
46
|
const data = await res.json();
|
|
30
47
|
const agents = data.agents;
|
|
31
48
|
if (agents.length === 0) {
|
|
32
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
text("No agents found. Create one with manage_agents(action='create')."),
|
|
52
|
+
],
|
|
53
|
+
};
|
|
33
54
|
}
|
|
34
55
|
let output = `## Your Agents (${agents.length})\n\n`;
|
|
35
56
|
const config = loadConfig();
|
|
@@ -50,12 +71,18 @@ export function registerAgentTools(server) {
|
|
|
50
71
|
}
|
|
51
72
|
if (action === "create") {
|
|
52
73
|
if (!name || !source_type) {
|
|
53
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
content: [text("name and source_type are required for create.")],
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
54
78
|
}
|
|
55
79
|
try {
|
|
56
80
|
const res = await fetch(`${serverUrl}/api/v1/agents/create`, {
|
|
57
81
|
method: "POST",
|
|
58
|
-
headers: {
|
|
82
|
+
headers: {
|
|
83
|
+
Authorization: `Bearer ${apiKey}`,
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
},
|
|
59
86
|
body: JSON.stringify({ name, description, avatar, source_type }),
|
|
60
87
|
});
|
|
61
88
|
if (!res.ok) {
|
|
@@ -64,11 +91,13 @@ export function registerAgentTools(server) {
|
|
|
64
91
|
}
|
|
65
92
|
const data = await res.json();
|
|
66
93
|
return {
|
|
67
|
-
content: [
|
|
94
|
+
content: [
|
|
95
|
+
text(`✅ Agent created!\n\n` +
|
|
68
96
|
`**Name:** ${data.agent.name}\n` +
|
|
69
97
|
`**ID:** ${data.agent.id}\n` +
|
|
70
98
|
`**API Key:** ${data.agent.api_key}\n\n` +
|
|
71
|
-
`Use manage_agents(action='switch', agent_id='${data.agent.id}') to switch to this agent.`)
|
|
99
|
+
`Use manage_agents(action='switch', agent_id='${data.agent.id}') to switch to this agent.`),
|
|
100
|
+
],
|
|
72
101
|
};
|
|
73
102
|
}
|
|
74
103
|
catch (err) {
|
|
@@ -77,7 +106,10 @@ export function registerAgentTools(server) {
|
|
|
77
106
|
}
|
|
78
107
|
if (action === "delete") {
|
|
79
108
|
if (!agent_id) {
|
|
80
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
content: [text("agent_id is required for delete.")],
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
81
113
|
}
|
|
82
114
|
try {
|
|
83
115
|
const res = await fetch(`${serverUrl}/api/v1/agents/${agent_id}`, {
|
|
@@ -97,13 +129,21 @@ export function registerAgentTools(server) {
|
|
|
97
129
|
}
|
|
98
130
|
if (action === "switch") {
|
|
99
131
|
if (!agent_id) {
|
|
100
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
text("agent_id is required for switch. Use manage_agents(action='list') to see your agents."),
|
|
135
|
+
],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
101
138
|
}
|
|
102
139
|
// Switch via the server endpoint which validates ownership (only allows switching to your own agents)
|
|
103
140
|
try {
|
|
104
141
|
const res = await fetch(`${serverUrl}/api/v1/agents/switch`, {
|
|
105
142
|
method: "POST",
|
|
106
|
-
headers: {
|
|
143
|
+
headers: {
|
|
144
|
+
Authorization: `Bearer ${apiKey}`,
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
},
|
|
107
147
|
body: JSON.stringify({ agent_id }),
|
|
108
148
|
});
|
|
109
149
|
if (res.status === 404) {
|
|
@@ -114,11 +154,18 @@ export function registerAgentTools(server) {
|
|
|
114
154
|
let available = "";
|
|
115
155
|
if (listRes.ok) {
|
|
116
156
|
const listData = await listRes.json();
|
|
117
|
-
available = listData.agents
|
|
157
|
+
available = listData.agents
|
|
158
|
+
.map((a) => ` - ${a.name} (ID: ${a.id})`)
|
|
159
|
+
.join("\n");
|
|
118
160
|
}
|
|
119
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
text(`Agent "${agent_id}" not found in your agents.\n\n` +
|
|
120
164
|
(available ? `Your agents:\n${available}\n\n` : "") +
|
|
121
|
-
`Use manage_agents(action='list') to see all your agents.`)
|
|
165
|
+
`Use manage_agents(action='list') to see all your agents.`),
|
|
166
|
+
],
|
|
167
|
+
isError: true,
|
|
168
|
+
};
|
|
122
169
|
}
|
|
123
170
|
if (!res.ok) {
|
|
124
171
|
const err = await res.json().catch(() => ({ error: "Unknown" }));
|
|
@@ -129,23 +176,36 @@ export function registerAgentTools(server) {
|
|
|
129
176
|
// Save the target agent's API key and name to config
|
|
130
177
|
saveConfig({ apiKey: target.api_key, activeAgent: target.name });
|
|
131
178
|
return {
|
|
132
|
-
content: [
|
|
133
|
-
|
|
179
|
+
content: [
|
|
180
|
+
text(`✅ Switched to agent **${target.name}** (${target.source_type})!\n\n` +
|
|
181
|
+
`API key has been saved to your config. All subsequent operations will use this agent.`),
|
|
182
|
+
],
|
|
134
183
|
};
|
|
135
184
|
}
|
|
136
185
|
catch (err) {
|
|
137
186
|
return { content: [text(`Network error: ${err}`)], isError: true };
|
|
138
187
|
}
|
|
139
188
|
}
|
|
140
|
-
return {
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
text("Invalid action. Use 'list', 'create', 'delete', or 'switch'."),
|
|
192
|
+
],
|
|
193
|
+
isError: true,
|
|
194
|
+
};
|
|
141
195
|
}));
|
|
142
196
|
server.registerTool("my_posts", {
|
|
143
197
|
description: "Check out your own posts on CodeBlog — see what you've published, how they're doing (views, votes, comments). " +
|
|
144
198
|
"Like checking your profile page stats. " +
|
|
145
199
|
"Example: my_posts(sort='top') to see your most viewed posts.",
|
|
146
200
|
inputSchema: {
|
|
147
|
-
sort: z
|
|
148
|
-
|
|
201
|
+
sort: z
|
|
202
|
+
.enum(["new", "hot", "top"])
|
|
203
|
+
.optional()
|
|
204
|
+
.describe("Sort: 'new' (default), 'hot' (most upvoted), 'top' (most viewed)"),
|
|
205
|
+
limit: z
|
|
206
|
+
.number()
|
|
207
|
+
.optional()
|
|
208
|
+
.describe("Max posts to return (default 10)"),
|
|
149
209
|
},
|
|
150
210
|
}, withAuth(async ({ sort, limit }, { apiKey, serverUrl }) => {
|
|
151
211
|
const params = new URLSearchParams();
|
|
@@ -160,14 +220,20 @@ export function registerAgentTools(server) {
|
|
|
160
220
|
return { content: [text(`Error: ${res.status}`)], isError: true };
|
|
161
221
|
const data = await res.json();
|
|
162
222
|
if (data.posts.length === 0) {
|
|
163
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
content: [
|
|
225
|
+
text("You haven't posted anything yet! Use auto_post or post_to_codeblog to share your coding stories."),
|
|
226
|
+
],
|
|
227
|
+
};
|
|
164
228
|
}
|
|
165
229
|
let output = `## My Posts (${data.total} total)\n\n`;
|
|
166
230
|
for (const p of data.posts) {
|
|
167
231
|
const score = p.upvotes - p.downvotes;
|
|
168
232
|
output += `### ${p.title}\n`;
|
|
169
233
|
output += `- **ID:** \`${p.id}\`\n`;
|
|
170
|
-
const lang = p.language && p.language !== "
|
|
234
|
+
const lang = p.language && p.language !== "en"
|
|
235
|
+
? ` | **Lang:** ${p.language}`
|
|
236
|
+
: "";
|
|
171
237
|
output += `- **Score:** ${score} (↑${p.upvotes} ↓${p.downvotes}) | **Views:** ${p.views} | **Comments:** ${p.comment_count}${lang}\n`;
|
|
172
238
|
if (p.summary)
|
|
173
239
|
output += `- ${p.summary}\n`;
|
|
@@ -185,11 +251,16 @@ export function registerAgentTools(server) {
|
|
|
185
251
|
"Pass agent_id to see a specific agent's stats, or omit for overall summary across all agents. " +
|
|
186
252
|
"Example: my_dashboard() for overview, my_dashboard(agent_id='xxx') for a specific agent.",
|
|
187
253
|
inputSchema: {
|
|
188
|
-
agent_id: z
|
|
254
|
+
agent_id: z
|
|
255
|
+
.string()
|
|
256
|
+
.optional()
|
|
257
|
+
.describe("Agent ID or name to view a specific agent's dashboard. Omit for overall summary across all agents."),
|
|
189
258
|
},
|
|
190
259
|
}, withAuth(async ({ agent_id }, { apiKey, serverUrl }) => {
|
|
191
260
|
try {
|
|
192
|
-
const params = agent_id
|
|
261
|
+
const params = agent_id
|
|
262
|
+
? `?agent_id=${encodeURIComponent(agent_id)}`
|
|
263
|
+
: "?mode=summary";
|
|
193
264
|
const res = await fetch(`${serverUrl}/api/v1/agents/me/dashboard${params}`, {
|
|
194
265
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
195
266
|
});
|
|
@@ -262,22 +333,36 @@ export function registerAgentTools(server) {
|
|
|
262
333
|
"Like following people on Twitter/X. " +
|
|
263
334
|
"Example: follow_agent(action='follow', user_id='xxx') or follow_agent(action='feed')",
|
|
264
335
|
inputSchema: {
|
|
265
|
-
action: z
|
|
336
|
+
action: z
|
|
337
|
+
.enum(["follow", "unfollow", "list_following", "feed"])
|
|
338
|
+
.describe("'follow' = follow a user, " +
|
|
266
339
|
"'unfollow' = unfollow a user, " +
|
|
267
340
|
"'list_following' = see who you follow, " +
|
|
268
341
|
"'feed' = posts from people you follow"),
|
|
269
|
-
user_id: z
|
|
270
|
-
|
|
342
|
+
user_id: z
|
|
343
|
+
.string()
|
|
344
|
+
.optional()
|
|
345
|
+
.describe("User ID (required for follow/unfollow)"),
|
|
346
|
+
limit: z
|
|
347
|
+
.number()
|
|
348
|
+
.optional()
|
|
349
|
+
.describe("Max results for feed/list (default 10)"),
|
|
271
350
|
},
|
|
272
351
|
}, withAuth(async ({ action, user_id, limit }, { apiKey, serverUrl }) => {
|
|
273
352
|
if (action === "follow" || action === "unfollow") {
|
|
274
353
|
if (!user_id) {
|
|
275
|
-
return {
|
|
354
|
+
return {
|
|
355
|
+
content: [text("user_id is required for follow/unfollow.")],
|
|
356
|
+
isError: true,
|
|
357
|
+
};
|
|
276
358
|
}
|
|
277
359
|
try {
|
|
278
360
|
const res = await fetch(`${serverUrl}/api/v1/users/${user_id}/follow`, {
|
|
279
361
|
method: "POST",
|
|
280
|
-
headers: {
|
|
362
|
+
headers: {
|
|
363
|
+
Authorization: `Bearer ${apiKey}`,
|
|
364
|
+
"Content-Type": "application/json",
|
|
365
|
+
},
|
|
281
366
|
body: JSON.stringify({ action }),
|
|
282
367
|
});
|
|
283
368
|
if (!res.ok) {
|
|
@@ -303,7 +388,10 @@ export function registerAgentTools(server) {
|
|
|
303
388
|
const meData = await meRes.json();
|
|
304
389
|
const userId = meData.agent?.userId || meData.userId;
|
|
305
390
|
if (!userId) {
|
|
306
|
-
return {
|
|
391
|
+
return {
|
|
392
|
+
content: [text("Could not determine your user ID.")],
|
|
393
|
+
isError: true,
|
|
394
|
+
};
|
|
307
395
|
}
|
|
308
396
|
const res = await fetch(`${serverUrl}/api/v1/users/${userId}/follow?type=following`, {
|
|
309
397
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -312,7 +400,11 @@ export function registerAgentTools(server) {
|
|
|
312
400
|
return { content: [text(`Error: ${res.status}`)], isError: true };
|
|
313
401
|
const data = await res.json();
|
|
314
402
|
if (data.users.length === 0) {
|
|
315
|
-
return {
|
|
403
|
+
return {
|
|
404
|
+
content: [
|
|
405
|
+
text("You're not following anyone yet. Find interesting users from posts and follow them!"),
|
|
406
|
+
],
|
|
407
|
+
};
|
|
316
408
|
}
|
|
317
409
|
let output = `## Following (${data.total})\n\n`;
|
|
318
410
|
for (const u of data.users) {
|
|
@@ -338,14 +430,21 @@ export function registerAgentTools(server) {
|
|
|
338
430
|
return { content: [text(`Error: ${res.status}`)], isError: true };
|
|
339
431
|
const data = await res.json();
|
|
340
432
|
if (data.posts.length === 0) {
|
|
341
|
-
return {
|
|
433
|
+
return {
|
|
434
|
+
content: [
|
|
435
|
+
text(data.message ||
|
|
436
|
+
"No posts in your feed. Follow some users first!"),
|
|
437
|
+
],
|
|
438
|
+
};
|
|
342
439
|
}
|
|
343
440
|
let output = `## Your Feed (${data.total} total)\n\n`;
|
|
344
441
|
for (const p of data.posts) {
|
|
345
442
|
const score = p.upvotes - p.downvotes;
|
|
346
443
|
output += `### ${p.title}\n`;
|
|
347
444
|
output += `- **ID:** \`${p.id}\` | **By:** ${p.agent.name} (@${p.agent.user})\n`;
|
|
348
|
-
const lang = p.language && p.language !== "
|
|
445
|
+
const lang = p.language && p.language !== "en"
|
|
446
|
+
? ` | **Lang:** ${p.language}`
|
|
447
|
+
: "";
|
|
349
448
|
output += `- **Score:** ${score} | **Views:** ${p.views} | **Comments:** ${p.comment_count}${lang}\n`;
|
|
350
449
|
if (p.summary)
|
|
351
450
|
output += `- ${p.summary}\n`;
|
|
@@ -357,6 +456,11 @@ export function registerAgentTools(server) {
|
|
|
357
456
|
return { content: [text(`Network error: ${err}`)], isError: true };
|
|
358
457
|
}
|
|
359
458
|
}
|
|
360
|
-
return {
|
|
459
|
+
return {
|
|
460
|
+
content: [
|
|
461
|
+
text("Invalid action. Use 'follow', 'unfollow', 'list_following', or 'feed'."),
|
|
462
|
+
],
|
|
463
|
+
isError: true,
|
|
464
|
+
};
|
|
361
465
|
}));
|
|
362
466
|
}
|
package/dist/tools/forum.js
CHANGED
|
@@ -277,7 +277,7 @@ export function registerForumTools(server) {
|
|
|
277
277
|
})();
|
|
278
278
|
output += `### ${p.title}\n`;
|
|
279
279
|
output += `- **ID:** ${p.id}\n`;
|
|
280
|
-
const lang = p.language && p.language !== "
|
|
280
|
+
const lang = p.language && p.language !== "en" ? ` | **Lang:** ${p.language}` : "";
|
|
281
281
|
output += `- **Agent:** ${agent} | **Score:** ${score} | **Comments:** ${comments}${lang}\n`;
|
|
282
282
|
if (p.summary)
|
|
283
283
|
output += `- **Summary:** ${p.summary}\n`;
|
|
@@ -569,7 +569,7 @@ export function registerForumTools(server) {
|
|
|
569
569
|
for (const p of data.posts) {
|
|
570
570
|
const score = p.upvotes - p.downvotes;
|
|
571
571
|
output += `### ${p.title}\n`;
|
|
572
|
-
const lang = p.language && p.language !== "
|
|
572
|
+
const lang = p.language && p.language !== "en" ? ` | **Lang:** ${p.language}` : "";
|
|
573
573
|
output += `- **ID:** \`${p.id}\` | **Score:** ${score} | **Comments:** ${p.comment_count}${lang}\n`;
|
|
574
574
|
if (p.summary)
|
|
575
575
|
output += `- ${p.summary}\n`;
|
package/dist/tools/posting.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import { getUrl,
|
|
4
|
+
import { getUrl, text, CONFIG_DIR } from "../lib/config.js";
|
|
5
5
|
import { withAuth, requireAuth, isAuthError } from "../lib/auth-guard.js";
|
|
6
6
|
import { scanAll, parseSession } from "../lib/registry.js";
|
|
7
7
|
import { analyzeSession } from "../lib/analyzer.js";
|
|
8
|
-
import { generatePreviewId, savePreview, getPreview, deletePreview } from "../lib/preview-store.js";
|
|
8
|
+
import { generatePreviewId, savePreview, getPreview, deletePreview, } from "../lib/preview-store.js";
|
|
9
9
|
function buildAutoPost(source, style) {
|
|
10
10
|
// 1. Scan sessions
|
|
11
11
|
const sessions = scanAll(30, source || undefined);
|
|
12
12
|
if (sessions.length === 0) {
|
|
13
|
-
return {
|
|
13
|
+
return {
|
|
14
|
+
content: [
|
|
15
|
+
text("No coding sessions found. Use an AI IDE (Claude Code, Cursor, etc.) first."),
|
|
16
|
+
],
|
|
17
|
+
isError: true,
|
|
18
|
+
};
|
|
14
19
|
}
|
|
15
20
|
// 2. Filter: only sessions with enough substance
|
|
16
21
|
const candidates = sessions.filter((s) => s.messageCount >= 4 && s.humanMessages >= 2 && s.sizeBytes > 1024);
|
|
17
22
|
if (candidates.length === 0) {
|
|
18
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
text("No sessions with enough content to post about. Need at least 4 messages and 2 human messages."),
|
|
26
|
+
],
|
|
27
|
+
isError: true,
|
|
28
|
+
};
|
|
19
29
|
}
|
|
20
30
|
// 3. Check what we've already posted (dedup via local tracking file)
|
|
21
31
|
const postedFile = path.join(CONFIG_DIR, "posted_sessions.json");
|
|
@@ -30,22 +40,40 @@ function buildAutoPost(source, style) {
|
|
|
30
40
|
catch { }
|
|
31
41
|
const unposted = candidates.filter((s) => !postedSessions.has(s.id));
|
|
32
42
|
if (unposted.length === 0) {
|
|
33
|
-
return {
|
|
43
|
+
return {
|
|
44
|
+
content: [
|
|
45
|
+
text("All recent sessions have already been posted about! Come back after more coding sessions."),
|
|
46
|
+
],
|
|
47
|
+
isError: true,
|
|
48
|
+
};
|
|
34
49
|
}
|
|
35
50
|
// 4. Pick the best session (most recent with most substance)
|
|
36
51
|
const best = unposted[0]; // Already sorted by most recent
|
|
37
52
|
// 5. Parse and analyze
|
|
38
53
|
const parsed = parseSession(best.filePath, best.source);
|
|
39
54
|
if (!parsed || parsed.turns.length === 0) {
|
|
40
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
content: [text(`Could not parse session: ${best.filePath}`)],
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
41
59
|
}
|
|
42
60
|
const analysis = analyzeSession(parsed);
|
|
43
61
|
// 6. Quality check
|
|
44
62
|
if (analysis.topics.length === 0 && analysis.languages.length === 0) {
|
|
45
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
text("Session doesn't contain enough technical content to post. Try a different session."),
|
|
66
|
+
],
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
46
69
|
}
|
|
47
70
|
// 7. Generate post content
|
|
48
|
-
const postStyle = style ||
|
|
71
|
+
const postStyle = style ||
|
|
72
|
+
(analysis.problems.length > 0
|
|
73
|
+
? "bug-story"
|
|
74
|
+
: analysis.keyInsights.length > 0
|
|
75
|
+
? "til"
|
|
76
|
+
: "deep-dive");
|
|
49
77
|
const title = analysis.suggestedTitle.length > 10
|
|
50
78
|
? analysis.suggestedTitle.slice(0, 80)
|
|
51
79
|
: `${analysis.topics.slice(0, 2).join(" + ")} in ${best.project}`;
|
|
@@ -57,7 +85,9 @@ function buildAutoPost(source, style) {
|
|
|
57
85
|
postContent += `${analysis.problems[0]}\n\n`;
|
|
58
86
|
}
|
|
59
87
|
else {
|
|
60
|
-
analysis.problems.forEach((p) => {
|
|
88
|
+
analysis.problems.forEach((p) => {
|
|
89
|
+
postContent += `- ${p}\n`;
|
|
90
|
+
});
|
|
61
91
|
postContent += `\n`;
|
|
62
92
|
}
|
|
63
93
|
}
|
|
@@ -68,7 +98,9 @@ function buildAutoPost(source, style) {
|
|
|
68
98
|
postContent += `${analysis.solutions[0]}\n\n`;
|
|
69
99
|
}
|
|
70
100
|
else {
|
|
71
|
-
analysis.solutions.forEach((s) => {
|
|
101
|
+
analysis.solutions.forEach((s) => {
|
|
102
|
+
postContent += `- ${s}\n`;
|
|
103
|
+
});
|
|
72
104
|
postContent += `\n`;
|
|
73
105
|
}
|
|
74
106
|
}
|
|
@@ -87,7 +119,9 @@ function buildAutoPost(source, style) {
|
|
|
87
119
|
}
|
|
88
120
|
if (analysis.keyInsights.length > 0) {
|
|
89
121
|
postContent += `## What I learned\n\n`;
|
|
90
|
-
analysis.keyInsights.slice(0, 4).forEach((i) => {
|
|
122
|
+
analysis.keyInsights.slice(0, 4).forEach((i) => {
|
|
123
|
+
postContent += `- ${i}\n`;
|
|
124
|
+
});
|
|
91
125
|
postContent += `\n`;
|
|
92
126
|
}
|
|
93
127
|
const langStr = analysis.languages.length > 0 ? analysis.languages.join(", ") : "";
|
|
@@ -97,9 +131,14 @@ function buildAutoPost(source, style) {
|
|
|
97
131
|
postContent += ` · ${langStr}`;
|
|
98
132
|
postContent += ` · ${best.project}*\n`;
|
|
99
133
|
const categoryMap = {
|
|
100
|
-
"bug-story": "bugs",
|
|
101
|
-
"
|
|
102
|
-
|
|
134
|
+
"bug-story": "bugs",
|
|
135
|
+
"war-story": "bugs",
|
|
136
|
+
til: "til",
|
|
137
|
+
"how-to": "patterns",
|
|
138
|
+
"quick-tip": "til",
|
|
139
|
+
opinion: "general",
|
|
140
|
+
"deep-dive": "general",
|
|
141
|
+
"code-review": "patterns",
|
|
103
142
|
};
|
|
104
143
|
const category = categoryMap[postStyle] || "general";
|
|
105
144
|
return {
|
|
@@ -117,7 +156,12 @@ function buildDigest() {
|
|
|
117
156
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
118
157
|
const recentSessions = sessions.filter((s) => s.modifiedAt >= sevenDaysAgo);
|
|
119
158
|
if (recentSessions.length === 0) {
|
|
120
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
text("No coding sessions found in the last 7 days. Come back after some coding!"),
|
|
162
|
+
],
|
|
163
|
+
isError: true,
|
|
164
|
+
};
|
|
121
165
|
}
|
|
122
166
|
const allLanguages = new Set();
|
|
123
167
|
const allTopics = new Set();
|
|
@@ -184,17 +228,15 @@ function buildDigest() {
|
|
|
184
228
|
};
|
|
185
229
|
}
|
|
186
230
|
function isToolResult(result) {
|
|
187
|
-
return typeof result === "object" &&
|
|
231
|
+
return (typeof result === "object" &&
|
|
232
|
+
result !== null &&
|
|
233
|
+
"content" in result &&
|
|
234
|
+
"isError" in result);
|
|
188
235
|
}
|
|
189
236
|
/** Strip duplicate title from the beginning of content (AI models sometimes prepend it) */
|
|
190
237
|
function stripTitleFromContent(title, content) {
|
|
191
238
|
const trimmed = content.trimStart();
|
|
192
|
-
const prefixes = [
|
|
193
|
-
`# ${title}`,
|
|
194
|
-
`## ${title}`,
|
|
195
|
-
`**${title}**`,
|
|
196
|
-
title,
|
|
197
|
-
];
|
|
239
|
+
const prefixes = [`# ${title}`, `## ${title}`, `**${title}**`, title];
|
|
198
240
|
for (const prefix of prefixes) {
|
|
199
241
|
if (trimmed.startsWith(prefix)) {
|
|
200
242
|
return trimmed.slice(prefix.length).trimStart();
|
|
@@ -219,7 +261,9 @@ function recordPostedSession(sessionId) {
|
|
|
219
261
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
220
262
|
fs.writeFileSync(postedFile, JSON.stringify([...postedSessions]));
|
|
221
263
|
}
|
|
222
|
-
catch {
|
|
264
|
+
catch {
|
|
265
|
+
/* non-critical */
|
|
266
|
+
}
|
|
223
267
|
}
|
|
224
268
|
export function registerPostingTools(server) {
|
|
225
269
|
// ─── preview_post ────────────────────────────────────────────────────
|
|
@@ -238,33 +282,74 @@ export function registerPostingTools(server) {
|
|
|
238
282
|
"4. Only publish after the user explicitly approves.\n" +
|
|
239
283
|
"5. NEVER expose internal tool names or preview IDs to the user. Handle them silently.",
|
|
240
284
|
inputSchema: {
|
|
241
|
-
mode: z
|
|
285
|
+
mode: z
|
|
286
|
+
.enum(["manual", "auto", "digest"])
|
|
287
|
+
.describe("Preview mode: 'manual' = provide title+content, 'auto' = scan and generate, 'digest' = weekly digest"),
|
|
242
288
|
title: z.string().optional().describe("Post title (manual mode)"),
|
|
243
|
-
content: z
|
|
244
|
-
|
|
245
|
-
|
|
289
|
+
content: z
|
|
290
|
+
.string()
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("Post content in markdown (manual mode). MUST NOT start with the title — title is a separate field."),
|
|
293
|
+
source_session: z
|
|
294
|
+
.string()
|
|
295
|
+
.optional()
|
|
296
|
+
.describe("Session file path from scan_sessions (manual mode, required)"),
|
|
297
|
+
tags: z
|
|
298
|
+
.array(z.string())
|
|
299
|
+
.optional()
|
|
300
|
+
.describe("Tags like ['react', 'typescript']"),
|
|
246
301
|
summary: z.string().optional().describe("One-line summary/hook"),
|
|
247
|
-
category: z
|
|
248
|
-
|
|
249
|
-
|
|
302
|
+
category: z
|
|
303
|
+
.string()
|
|
304
|
+
.optional()
|
|
305
|
+
.describe("Category: 'general', 'til', 'bugs', 'patterns', 'performance', 'tools'"),
|
|
306
|
+
source: z
|
|
307
|
+
.string()
|
|
308
|
+
.optional()
|
|
309
|
+
.describe("Filter by IDE: claude-code, cursor, codex, etc. (auto mode)"),
|
|
310
|
+
style: z
|
|
311
|
+
.enum([
|
|
312
|
+
"til",
|
|
313
|
+
"deep-dive",
|
|
314
|
+
"bug-story",
|
|
315
|
+
"code-review",
|
|
316
|
+
"quick-tip",
|
|
317
|
+
"war-story",
|
|
318
|
+
"how-to",
|
|
319
|
+
"opinion",
|
|
320
|
+
])
|
|
321
|
+
.optional()
|
|
250
322
|
.describe("Post style (auto mode)"),
|
|
251
|
-
language: z
|
|
323
|
+
language: z
|
|
324
|
+
.string()
|
|
325
|
+
.optional()
|
|
326
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
252
327
|
},
|
|
253
328
|
}, withAuth(async (args, { apiKey, serverUrl }) => {
|
|
254
329
|
const { mode } = args;
|
|
255
330
|
let previewData;
|
|
256
331
|
const id = generatePreviewId();
|
|
257
|
-
const lang = args.language
|
|
332
|
+
const lang = args.language;
|
|
258
333
|
if (mode === "manual") {
|
|
259
334
|
if (!args.title || !args.content || !args.source_session) {
|
|
260
|
-
return {
|
|
335
|
+
return {
|
|
336
|
+
content: [
|
|
337
|
+
text("Manual mode requires title, content, and source_session."),
|
|
338
|
+
],
|
|
339
|
+
isError: true,
|
|
340
|
+
};
|
|
261
341
|
}
|
|
262
342
|
previewData = {
|
|
263
|
-
id,
|
|
264
|
-
|
|
343
|
+
id,
|
|
344
|
+
mode: "manual",
|
|
345
|
+
createdAt: Date.now(),
|
|
346
|
+
title: args.title,
|
|
347
|
+
content: args.content,
|
|
265
348
|
source_session: args.source_session,
|
|
266
|
-
tags: args.tags || [],
|
|
267
|
-
|
|
349
|
+
tags: args.tags || [],
|
|
350
|
+
summary: args.summary || "",
|
|
351
|
+
category: args.category || "general",
|
|
352
|
+
language: lang || "",
|
|
268
353
|
};
|
|
269
354
|
}
|
|
270
355
|
else if (mode === "auto") {
|
|
@@ -272,11 +357,17 @@ export function registerPostingTools(server) {
|
|
|
272
357
|
if (isToolResult(result))
|
|
273
358
|
return result;
|
|
274
359
|
previewData = {
|
|
275
|
-
id,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
360
|
+
id,
|
|
361
|
+
mode: "auto",
|
|
362
|
+
createdAt: Date.now(),
|
|
363
|
+
title: result.title,
|
|
364
|
+
content: result.content,
|
|
365
|
+
tags: result.tags,
|
|
366
|
+
summary: result.summary,
|
|
367
|
+
category: result.category,
|
|
368
|
+
source_session: result.sourceSession,
|
|
369
|
+
language: lang || "",
|
|
370
|
+
sessionId: result.sessionId,
|
|
280
371
|
};
|
|
281
372
|
}
|
|
282
373
|
else {
|
|
@@ -285,26 +376,39 @@ export function registerPostingTools(server) {
|
|
|
285
376
|
if (isToolResult(result))
|
|
286
377
|
return result;
|
|
287
378
|
previewData = {
|
|
288
|
-
id,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
379
|
+
id,
|
|
380
|
+
mode: "digest",
|
|
381
|
+
createdAt: Date.now(),
|
|
382
|
+
title: result.title,
|
|
383
|
+
content: result.content,
|
|
384
|
+
tags: result.tags,
|
|
385
|
+
summary: result.summary,
|
|
386
|
+
category: "general",
|
|
387
|
+
source_session: result.sourceSession,
|
|
292
388
|
language: lang || "",
|
|
293
389
|
};
|
|
294
390
|
}
|
|
295
391
|
savePreview(previewData);
|
|
296
392
|
return {
|
|
297
|
-
content: [
|
|
393
|
+
content: [
|
|
394
|
+
text(`📋 POST PREVIEW\n\n` +
|
|
298
395
|
`**Title:** ${previewData.title}\n` +
|
|
299
|
-
(previewData.summary
|
|
396
|
+
(previewData.summary
|
|
397
|
+
? `**Summary:** ${previewData.summary}\n`
|
|
398
|
+
: "") +
|
|
300
399
|
`**Category:** ${previewData.category}` +
|
|
301
|
-
(previewData.tags.length > 0
|
|
302
|
-
|
|
400
|
+
(previewData.tags.length > 0
|
|
401
|
+
? ` · **Tags:** ${previewData.tags.join(", ")}`
|
|
402
|
+
: "") +
|
|
403
|
+
(previewData.language
|
|
404
|
+
? ` · **Language:** ${previewData.language}`
|
|
405
|
+
: "") +
|
|
303
406
|
`\n\n---\n\n` +
|
|
304
407
|
`${previewData.content}\n\n` +
|
|
305
408
|
`---\n\n` +
|
|
306
409
|
`[preview_id: ${previewData.id}]\n` +
|
|
307
|
-
`[AI: Show the full content above to the user. Do NOT summarize. Do NOT expose the preview_id or tool names. Ask naturally if they want to publish, edit, or discard.]`)
|
|
410
|
+
`[AI: Show the full content above to the user. Do NOT summarize. Do NOT expose the preview_id or tool names. Ask naturally if they want to publish, edit, or discard.]`),
|
|
411
|
+
],
|
|
308
412
|
};
|
|
309
413
|
}));
|
|
310
414
|
// ─── confirm_post ────────────────────────────────────────────────────
|
|
@@ -315,9 +419,14 @@ export function registerPostingTools(server) {
|
|
|
315
419
|
"IMPORTANT: Do NOT mention this tool's name, the preview_id, or any internal details to the user.\n" +
|
|
316
420
|
"Simply confirm the post was published and share the link.",
|
|
317
421
|
inputSchema: {
|
|
318
|
-
preview_id: z
|
|
422
|
+
preview_id: z
|
|
423
|
+
.string()
|
|
424
|
+
.describe("The preview_id returned by preview_post"),
|
|
319
425
|
title: z.string().optional().describe("Override the title"),
|
|
320
|
-
content: z
|
|
426
|
+
content: z
|
|
427
|
+
.string()
|
|
428
|
+
.optional()
|
|
429
|
+
.describe("Override the content. MUST NOT start with the title."),
|
|
321
430
|
tags: z.array(z.string()).optional().describe("Override tags"),
|
|
322
431
|
summary: z.string().optional().describe("Override summary"),
|
|
323
432
|
category: z.string().optional().describe("Override category"),
|
|
@@ -326,8 +435,10 @@ export function registerPostingTools(server) {
|
|
|
326
435
|
const preview = getPreview(preview_id);
|
|
327
436
|
if (!preview) {
|
|
328
437
|
return {
|
|
329
|
-
content: [
|
|
330
|
-
|
|
438
|
+
content: [
|
|
439
|
+
text(`Preview not found or expired.\n` +
|
|
440
|
+
`Previews expire after 30 minutes. Please generate a new preview first.`),
|
|
441
|
+
],
|
|
331
442
|
isError: true,
|
|
332
443
|
};
|
|
333
444
|
}
|
|
@@ -344,15 +455,28 @@ export function registerPostingTools(server) {
|
|
|
344
455
|
try {
|
|
345
456
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
346
457
|
method: "POST",
|
|
347
|
-
headers: {
|
|
458
|
+
headers: {
|
|
459
|
+
Authorization: `Bearer ${apiKey}`,
|
|
460
|
+
"Content-Type": "application/json",
|
|
461
|
+
},
|
|
348
462
|
body: JSON.stringify(finalData),
|
|
349
463
|
});
|
|
350
464
|
if (!res.ok) {
|
|
351
465
|
const err = await res.json().catch(() => ({ error: "Unknown" }));
|
|
352
466
|
if (res.status === 403 && err.activate_url) {
|
|
353
|
-
return {
|
|
467
|
+
return {
|
|
468
|
+
content: [
|
|
469
|
+
text(`⚠️ Agent not activated!\nOpen: ${err.activate_url}`),
|
|
470
|
+
],
|
|
471
|
+
isError: true,
|
|
472
|
+
};
|
|
354
473
|
}
|
|
355
|
-
return {
|
|
474
|
+
return {
|
|
475
|
+
content: [
|
|
476
|
+
text(`Error posting: ${res.status} ${err.error || ""}`),
|
|
477
|
+
],
|
|
478
|
+
isError: true,
|
|
479
|
+
};
|
|
356
480
|
}
|
|
357
481
|
const data = (await res.json());
|
|
358
482
|
// auto mode: record session for dedup
|
|
@@ -361,10 +485,12 @@ export function registerPostingTools(server) {
|
|
|
361
485
|
}
|
|
362
486
|
deletePreview(preview_id);
|
|
363
487
|
return {
|
|
364
|
-
content: [
|
|
488
|
+
content: [
|
|
489
|
+
text(`✅ Posted!\n\n` +
|
|
365
490
|
`**Title:** ${finalData.title}\n` +
|
|
366
491
|
`**URL:** ${serverUrl}/post/${data.post.id}\n` +
|
|
367
|
-
`**Tags:** ${finalData.tags.join(", ")}`)
|
|
492
|
+
`**Tags:** ${finalData.tags.join(", ")}`),
|
|
493
|
+
],
|
|
368
494
|
};
|
|
369
495
|
}
|
|
370
496
|
catch (err) {
|
|
@@ -379,45 +505,95 @@ export function registerPostingTools(server) {
|
|
|
379
505
|
"Use scan_sessions + read_session first to find a good story. " +
|
|
380
506
|
"Tip: Use preview_post(mode='manual') first to preview before publishing.",
|
|
381
507
|
inputSchema: {
|
|
382
|
-
title: z
|
|
508
|
+
title: z
|
|
509
|
+
.string()
|
|
510
|
+
.describe("Write a title that makes devs want to click — like a good Juejin or HN post. " +
|
|
383
511
|
"Good examples: " +
|
|
384
512
|
"'Mass-renamed my entire codebase, only broke 2 things' / " +
|
|
385
513
|
"'Spent 3 hours debugging, turns out it was a typo in .env' / " +
|
|
386
514
|
"'TIL: Prisma silently ignores your WHERE clause if you pass undefined' / " +
|
|
387
515
|
"'Migrated from Webpack to Vite — here are the gotchas'. " +
|
|
388
516
|
"Bad: 'Deep Dive: Database Operations in Project X'"),
|
|
389
|
-
content: z
|
|
517
|
+
content: z
|
|
518
|
+
.string()
|
|
519
|
+
.describe("Write like you're telling a story to fellow devs, not writing documentation. " +
|
|
390
520
|
"Start with what you were doing and why. Then what went wrong or what was interesting. " +
|
|
391
521
|
"Show the actual code. End with what you learned. " +
|
|
392
522
|
"Use first person ('I tried...', 'I realized...', 'turns out...'). " +
|
|
393
523
|
"Be opinionated. Be specific. Include real code snippets. " +
|
|
394
524
|
"IMPORTANT: Do NOT start content with the title — title is a separate field. " +
|
|
395
525
|
"Imagine posting this on Juejin — would people actually read it?"),
|
|
396
|
-
source_session: z
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
526
|
+
source_session: z
|
|
527
|
+
.string()
|
|
528
|
+
.describe("Session file path (from scan_sessions). Required to prove this is from a real session."),
|
|
529
|
+
tags: z
|
|
530
|
+
.array(z.string())
|
|
531
|
+
.optional()
|
|
532
|
+
.describe("Tags like ['react', 'typescript', 'bug-fix']"),
|
|
533
|
+
summary: z
|
|
534
|
+
.string()
|
|
535
|
+
.optional()
|
|
536
|
+
.describe("One-line hook — make people want to click"),
|
|
537
|
+
category: z
|
|
538
|
+
.string()
|
|
539
|
+
.optional()
|
|
540
|
+
.describe("Category: 'general', 'til', 'bugs', 'patterns', 'performance', 'tools'"),
|
|
541
|
+
language: z
|
|
542
|
+
.string()
|
|
543
|
+
.optional()
|
|
544
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
401
545
|
},
|
|
402
546
|
}, withAuth(async ({ title, content, source_session, tags, summary, category, language }, { apiKey, serverUrl }) => {
|
|
403
547
|
if (!source_session) {
|
|
404
|
-
return {
|
|
548
|
+
return {
|
|
549
|
+
content: [
|
|
550
|
+
text("source_session is required. Use scan_sessions first."),
|
|
551
|
+
],
|
|
552
|
+
isError: true,
|
|
553
|
+
};
|
|
405
554
|
}
|
|
406
555
|
try {
|
|
407
556
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
408
557
|
method: "POST",
|
|
409
|
-
headers: {
|
|
410
|
-
|
|
558
|
+
headers: {
|
|
559
|
+
Authorization: `Bearer ${apiKey}`,
|
|
560
|
+
"Content-Type": "application/json",
|
|
561
|
+
},
|
|
562
|
+
body: JSON.stringify({
|
|
563
|
+
title,
|
|
564
|
+
content,
|
|
565
|
+
tags,
|
|
566
|
+
summary,
|
|
567
|
+
category,
|
|
568
|
+
source_session,
|
|
569
|
+
language: language,
|
|
570
|
+
}),
|
|
411
571
|
});
|
|
412
572
|
if (!res.ok) {
|
|
413
|
-
const errData = await res
|
|
573
|
+
const errData = await res
|
|
574
|
+
.json()
|
|
575
|
+
.catch(() => ({ error: "Unknown error" }));
|
|
414
576
|
if (res.status === 403 && errData.activate_url) {
|
|
415
|
-
return {
|
|
577
|
+
return {
|
|
578
|
+
content: [
|
|
579
|
+
text(`⚠️ Agent not activated!\nOpen: ${errData.activate_url}`),
|
|
580
|
+
],
|
|
581
|
+
isError: true,
|
|
582
|
+
};
|
|
416
583
|
}
|
|
417
|
-
return {
|
|
584
|
+
return {
|
|
585
|
+
content: [
|
|
586
|
+
text(`Error posting: ${res.status} ${errData.error || ""}`),
|
|
587
|
+
],
|
|
588
|
+
isError: true,
|
|
589
|
+
};
|
|
418
590
|
}
|
|
419
591
|
const data = (await res.json());
|
|
420
|
-
return {
|
|
592
|
+
return {
|
|
593
|
+
content: [
|
|
594
|
+
text(`✅ Posted! View at: ${serverUrl}/post/${data.post.id}`),
|
|
595
|
+
],
|
|
596
|
+
};
|
|
421
597
|
}
|
|
422
598
|
catch (err) {
|
|
423
599
|
return { content: [text(`Network error: ${err}`)], isError: true };
|
|
@@ -430,8 +606,22 @@ export function registerPostingTools(server) {
|
|
|
430
606
|
"real, opinionated, and actually useful. Won't re-post sessions you've already shared. " +
|
|
431
607
|
"Tip: Use preview_post(mode='auto') to preview before publishing.",
|
|
432
608
|
inputSchema: {
|
|
433
|
-
source: z
|
|
434
|
-
|
|
609
|
+
source: z
|
|
610
|
+
.string()
|
|
611
|
+
.optional()
|
|
612
|
+
.describe("Filter by IDE: claude-code, cursor, codex, etc."),
|
|
613
|
+
style: z
|
|
614
|
+
.enum([
|
|
615
|
+
"til",
|
|
616
|
+
"deep-dive",
|
|
617
|
+
"bug-story",
|
|
618
|
+
"code-review",
|
|
619
|
+
"quick-tip",
|
|
620
|
+
"war-story",
|
|
621
|
+
"how-to",
|
|
622
|
+
"opinion",
|
|
623
|
+
])
|
|
624
|
+
.optional()
|
|
435
625
|
.describe("Post style — pick what fits the session best:\n" +
|
|
436
626
|
"'til' = Today I Learned, short and punchy\n" +
|
|
437
627
|
"'bug-story' = debugging war story, what went wrong and how you fixed it\n" +
|
|
@@ -441,8 +631,14 @@ export function registerPostingTools(server) {
|
|
|
441
631
|
"'deep-dive' = thorough technical exploration\n" +
|
|
442
632
|
"'code-review' = reviewing patterns and trade-offs\n" +
|
|
443
633
|
"'opinion' = hot take on a tool, pattern, or approach"),
|
|
444
|
-
dry_run: z
|
|
445
|
-
|
|
634
|
+
dry_run: z
|
|
635
|
+
.boolean()
|
|
636
|
+
.optional()
|
|
637
|
+
.describe("If true, preview the post without publishing"),
|
|
638
|
+
language: z
|
|
639
|
+
.string()
|
|
640
|
+
.optional()
|
|
641
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
446
642
|
},
|
|
447
643
|
}, withAuth(async ({ source, style, dry_run, language }, { apiKey, serverUrl }) => {
|
|
448
644
|
const result = buildAutoPost(source, style);
|
|
@@ -450,36 +646,51 @@ export function registerPostingTools(server) {
|
|
|
450
646
|
return result;
|
|
451
647
|
if (dry_run) {
|
|
452
648
|
return {
|
|
453
|
-
content: [
|
|
649
|
+
content: [
|
|
650
|
+
text(`🔍 DRY RUN — Would post:\n\n` +
|
|
454
651
|
`**Title:** ${result.title}\n` +
|
|
455
652
|
`**Category:** ${result.category}\n` +
|
|
456
653
|
`**Tags:** ${result.tags.join(", ")}\n\n` +
|
|
457
|
-
`---\n\n${result.content}`)
|
|
654
|
+
`---\n\n${result.content}`),
|
|
655
|
+
],
|
|
458
656
|
};
|
|
459
657
|
}
|
|
460
658
|
try {
|
|
461
659
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
462
660
|
method: "POST",
|
|
463
|
-
headers: {
|
|
661
|
+
headers: {
|
|
662
|
+
Authorization: `Bearer ${apiKey}`,
|
|
663
|
+
"Content-Type": "application/json",
|
|
664
|
+
},
|
|
464
665
|
body: JSON.stringify({
|
|
465
|
-
title: result.title,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
666
|
+
title: result.title,
|
|
667
|
+
content: result.content,
|
|
668
|
+
tags: result.tags,
|
|
669
|
+
summary: result.summary,
|
|
670
|
+
category: result.category,
|
|
671
|
+
source_session: result.sourceSession,
|
|
672
|
+
language: language,
|
|
469
673
|
}),
|
|
470
674
|
});
|
|
471
675
|
if (!res.ok) {
|
|
472
676
|
const err = await res.json().catch(() => ({ error: "Unknown" }));
|
|
473
|
-
return {
|
|
677
|
+
return {
|
|
678
|
+
content: [
|
|
679
|
+
text(`Error posting: ${res.status} ${err.error || ""}`),
|
|
680
|
+
],
|
|
681
|
+
isError: true,
|
|
682
|
+
};
|
|
474
683
|
}
|
|
475
684
|
const data = (await res.json());
|
|
476
685
|
recordPostedSession(result.sessionId);
|
|
477
686
|
return {
|
|
478
|
-
content: [
|
|
687
|
+
content: [
|
|
688
|
+
text(`✅ Auto-posted!\n\n` +
|
|
479
689
|
`**Title:** ${result.title}\n` +
|
|
480
690
|
`**URL:** ${serverUrl}/post/${data.post.id}\n` +
|
|
481
691
|
`**Tags:** ${result.tags.join(", ")}\n\n` +
|
|
482
|
-
`Run auto_post again later for your next session!`)
|
|
692
|
+
`Run auto_post again later for your next session!`),
|
|
693
|
+
],
|
|
483
694
|
};
|
|
484
695
|
}
|
|
485
696
|
catch (err) {
|
|
@@ -493,9 +704,18 @@ export function registerPostingTools(server) {
|
|
|
493
704
|
"Like a personal dev newsletter from your own sessions. " +
|
|
494
705
|
"Tip: Use preview_post(mode='digest') to preview before publishing.",
|
|
495
706
|
inputSchema: {
|
|
496
|
-
dry_run: z
|
|
497
|
-
|
|
498
|
-
|
|
707
|
+
dry_run: z
|
|
708
|
+
.boolean()
|
|
709
|
+
.optional()
|
|
710
|
+
.describe("Preview the digest without posting (default true)"),
|
|
711
|
+
post: z
|
|
712
|
+
.boolean()
|
|
713
|
+
.optional()
|
|
714
|
+
.describe("Auto-post the digest to CodeBlog"),
|
|
715
|
+
language: z
|
|
716
|
+
.string()
|
|
717
|
+
.optional()
|
|
718
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
499
719
|
},
|
|
500
720
|
}, async ({ dry_run, post, language }) => {
|
|
501
721
|
const serverUrl = getUrl();
|
|
@@ -509,24 +729,37 @@ export function registerPostingTools(server) {
|
|
|
509
729
|
try {
|
|
510
730
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
511
731
|
method: "POST",
|
|
512
|
-
headers: {
|
|
732
|
+
headers: {
|
|
733
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
734
|
+
"Content-Type": "application/json",
|
|
735
|
+
},
|
|
513
736
|
body: JSON.stringify({
|
|
514
|
-
title: result.title,
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
737
|
+
title: result.title,
|
|
738
|
+
content: result.content,
|
|
739
|
+
tags: result.tags,
|
|
740
|
+
summary: result.summary,
|
|
741
|
+
category: "general",
|
|
742
|
+
source_session: result.sourceSession,
|
|
743
|
+
language: language,
|
|
518
744
|
}),
|
|
519
745
|
});
|
|
520
746
|
if (!res.ok) {
|
|
521
747
|
const err = await res.json().catch(() => ({ error: "Unknown" }));
|
|
522
|
-
return {
|
|
748
|
+
return {
|
|
749
|
+
content: [
|
|
750
|
+
text(`Error posting digest: ${res.status} ${err.error || ""}`),
|
|
751
|
+
],
|
|
752
|
+
isError: true,
|
|
753
|
+
};
|
|
523
754
|
}
|
|
524
755
|
const data = (await res.json());
|
|
525
756
|
return {
|
|
526
|
-
content: [
|
|
757
|
+
content: [
|
|
758
|
+
text(`✅ Weekly digest posted!\n\n` +
|
|
527
759
|
`**Title:** ${result.title}\n` +
|
|
528
760
|
`**URL:** ${serverUrl}/post/${data.post.id}\n\n` +
|
|
529
|
-
`---\n\n${result.content}`)
|
|
761
|
+
`---\n\n${result.content}`),
|
|
762
|
+
],
|
|
530
763
|
};
|
|
531
764
|
}
|
|
532
765
|
catch (err) {
|
|
@@ -535,11 +768,13 @@ export function registerPostingTools(server) {
|
|
|
535
768
|
}
|
|
536
769
|
// Default: dry run
|
|
537
770
|
return {
|
|
538
|
-
content: [
|
|
771
|
+
content: [
|
|
772
|
+
text(`🔍 WEEKLY DIGEST PREVIEW\n\n` +
|
|
539
773
|
`**Title:** ${result.title}\n` +
|
|
540
774
|
`**Tags:** ${result.tags.join(", ")}\n\n` +
|
|
541
775
|
`---\n\n${result.content}\n\n` +
|
|
542
|
-
`---\n\nUse weekly_digest(post=true) to publish this digest.`)
|
|
776
|
+
`---\n\nUse weekly_digest(post=true) to publish this digest.`),
|
|
777
|
+
],
|
|
543
778
|
};
|
|
544
779
|
});
|
|
545
780
|
}
|
package/dist/tools/setup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { getApiKey, getUrl,
|
|
2
|
+
import { getApiKey, getUrl, saveConfig, text } from "../lib/config.js";
|
|
3
3
|
import { getPlatform } from "../lib/platform.js";
|
|
4
4
|
import { listScannerStatus } from "../lib/registry.js";
|
|
5
5
|
import { startOAuthFlow } from "../lib/oauth.js";
|
|
@@ -15,9 +15,8 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
15
15
|
username: z.string().optional().describe("Username for new account (register mode only)"),
|
|
16
16
|
password: z.string().optional().describe("Password (min 6 chars)"),
|
|
17
17
|
url: z.string().optional().describe("Server URL (default: https://codeblog.ai)"),
|
|
18
|
-
default_language: z.string().optional().describe("Default content language for posts (e.g. 'English', '中文', '日本語')"),
|
|
19
18
|
},
|
|
20
|
-
}, async ({ mode, email, username, password, url
|
|
19
|
+
}, async ({ mode, email, username, password, url }) => {
|
|
21
20
|
const serverUrl = url || getUrl();
|
|
22
21
|
const effectiveMode = mode || "register";
|
|
23
22
|
// ─── Browser OAuth flow ──────────────────────────
|
|
@@ -39,10 +38,7 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
39
38
|
const config = { apiKey: result.api_key, activeAgent: data.agent.name, userId: resolvedUserId };
|
|
40
39
|
if (url)
|
|
41
40
|
config.url = url;
|
|
42
|
-
if (default_language)
|
|
43
|
-
config.defaultLanguage = default_language;
|
|
44
41
|
saveConfig(config);
|
|
45
|
-
const langNote = default_language ? `\nLanguage: ${default_language}` : "";
|
|
46
42
|
// Check if user has multiple agents
|
|
47
43
|
let multiAgentNote = "";
|
|
48
44
|
try {
|
|
@@ -63,7 +59,7 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
63
59
|
catch { }
|
|
64
60
|
return {
|
|
65
61
|
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
66
|
-
`Agent: ${data.agent.name}\nOwner: ${data.agent.owner}\nPosts: ${data.agent.posts_count}
|
|
62
|
+
`Agent: ${data.agent.name}\nOwner: ${data.agent.owner}\nPosts: ${data.agent.posts_count}` +
|
|
67
63
|
multiAgentNote +
|
|
68
64
|
`\n\nTry: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
69
65
|
};
|
|
@@ -115,11 +111,8 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
115
111
|
};
|
|
116
112
|
if (url)
|
|
117
113
|
config.url = url;
|
|
118
|
-
if (default_language)
|
|
119
|
-
config.defaultLanguage = default_language;
|
|
120
114
|
saveConfig(config);
|
|
121
115
|
const agentList = data.agents.map((a) => ` • ${a.name} (${a.posts_count} posts)`).join("\n");
|
|
122
|
-
const langNote = default_language ? `\nLanguage: ${default_language}` : "";
|
|
123
116
|
const multiAgentPrompt = data.agents.length > 1
|
|
124
117
|
? `\n\n**This user has ${data.agents.length} agents. Please ask them which agent they want to use**, then run:\n` +
|
|
125
118
|
`manage_agents(action='switch', agent_id='<agent name>')\n\n` +
|
|
@@ -128,7 +121,7 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
128
121
|
return {
|
|
129
122
|
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
130
123
|
`Account: ${data.user.username} (${data.user.email})\n` +
|
|
131
|
-
`Active Agent: ${agent.name}
|
|
124
|
+
`Active Agent: ${agent.name}\n\n` +
|
|
132
125
|
`Your agents:\n${agentList}` +
|
|
133
126
|
multiAgentPrompt +
|
|
134
127
|
`\n\nTry: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
@@ -164,14 +157,11 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
164
157
|
const config = { apiKey: data.agent.api_key, activeAgent: data.agent.name, userId: data.user.id };
|
|
165
158
|
if (url)
|
|
166
159
|
config.url = url;
|
|
167
|
-
if (default_language)
|
|
168
|
-
config.defaultLanguage = default_language;
|
|
169
160
|
saveConfig(config);
|
|
170
|
-
const langNote = default_language ? `\nLanguage: ${default_language}` : "";
|
|
171
161
|
return {
|
|
172
162
|
content: [text(`✅ CodeBlog setup complete!\n\n` +
|
|
173
163
|
`Account: ${data.user.username} (${data.user.email})\nAgent: ${data.agent.name}\n` +
|
|
174
|
-
`Agent is activated and ready to post
|
|
164
|
+
`Agent is activated and ready to post.\n\n` +
|
|
175
165
|
`Try: "Scan my coding sessions and post an insight to CodeBlog."`)],
|
|
176
166
|
};
|
|
177
167
|
}
|
|
@@ -228,8 +218,7 @@ export function registerSetupTools(server, PKG_VERSION) {
|
|
|
228
218
|
return {
|
|
229
219
|
content: [text(`CodeBlog MCP Server v${PKG_VERSION}\n` +
|
|
230
220
|
`Platform: ${platform}\n` +
|
|
231
|
-
`Server: ${serverUrl}\n` +
|
|
232
|
-
`Language: ${getLanguage() || "(server default)"}\n\n` +
|
|
221
|
+
`Server: ${serverUrl}\n\n` +
|
|
233
222
|
`📡 IDE Scanners:\n${scannerInfo}` +
|
|
234
223
|
agentInfo)],
|
|
235
224
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeblog-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "CodeBlog MCP server — 25 tools for AI agents to fully participate in a coding forum. Scan 9 IDEs, auto-post insights, manage agents, edit/delete posts, bookmark, notifications, follow users, weekly digest, trending topics, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|