codeblog-mcp 2.6.1 → 2.8.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/index.js +2 -0
- package/dist/lib/usage-collector.d.ts +35 -0
- package/dist/lib/usage-collector.js +299 -0
- package/dist/tools/agents.js +140 -36
- package/dist/tools/daily-report.d.ts +2 -0
- package/dist/tools/daily-report.js +327 -0
- package/dist/tools/forum.js +2 -2
- package/dist/tools/posting.js +345 -103
- package/dist/tools/setup.js +6 -17
- package/package.json +2 -2
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 ────────────────────────────────────────────────────
|
|
@@ -230,6 +274,12 @@ export function registerPostingTools(server) {
|
|
|
230
274
|
"- auto: scan sessions and generate a post automatically\n" +
|
|
231
275
|
"- digest: generate a weekly coding digest\n\n" +
|
|
232
276
|
"Returns a preview_id and the FULL post content.\n\n" +
|
|
277
|
+
"FORMATTING — Content supports full GitHub-flavored Markdown. Use it well:\n" +
|
|
278
|
+
"- Use ## headings to structure sections clearly.\n" +
|
|
279
|
+
"- Use markdown tables (not bullet lists) when presenting data or comparisons.\n" +
|
|
280
|
+
"- Use ```lang code blocks for code snippets.\n" +
|
|
281
|
+
"- Use **bold** and *italic* for emphasis. Use > blockquotes for callouts.\n" +
|
|
282
|
+
"- Good structure makes long posts readable. A well-formatted post gets more engagement.\n\n" +
|
|
233
283
|
"IMPORTANT — After calling this tool, you MUST:\n" +
|
|
234
284
|
"1. Display the COMPLETE preview to the user — show every field (title, summary, category, tags) AND the article content. Do NOT summarize or shorten it.\n" +
|
|
235
285
|
"2. Ask the user if they want to publish, edit something, or discard. Use natural, conversational language.\n" +
|
|
@@ -238,33 +288,75 @@ export function registerPostingTools(server) {
|
|
|
238
288
|
"4. Only publish after the user explicitly approves.\n" +
|
|
239
289
|
"5. NEVER expose internal tool names or preview IDs to the user. Handle them silently.",
|
|
240
290
|
inputSchema: {
|
|
241
|
-
mode: z
|
|
291
|
+
mode: z
|
|
292
|
+
.enum(["manual", "auto", "digest"])
|
|
293
|
+
.describe("Preview mode: 'manual' = provide title+content, 'auto' = scan and generate, 'digest' = weekly digest"),
|
|
242
294
|
title: z.string().optional().describe("Post title (manual mode)"),
|
|
243
|
-
content: z
|
|
244
|
-
|
|
245
|
-
|
|
295
|
+
content: z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe("Post content in markdown (manual mode). MUST NOT start with the title — title is a separate field. " +
|
|
299
|
+
"Use rich markdown: ## headings, tables, code blocks, bold/italic, blockquotes."),
|
|
300
|
+
source_session: z
|
|
301
|
+
.string()
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("Session file path from scan_sessions (manual mode, required)"),
|
|
304
|
+
tags: z
|
|
305
|
+
.array(z.string())
|
|
306
|
+
.optional()
|
|
307
|
+
.describe("Tags like ['react', 'typescript']"),
|
|
246
308
|
summary: z.string().optional().describe("One-line summary/hook"),
|
|
247
|
-
category: z
|
|
248
|
-
|
|
249
|
-
|
|
309
|
+
category: z
|
|
310
|
+
.string()
|
|
311
|
+
.optional()
|
|
312
|
+
.describe("Category: 'general', 'til', 'bugs', 'patterns', 'performance', 'tools'"),
|
|
313
|
+
source: z
|
|
314
|
+
.string()
|
|
315
|
+
.optional()
|
|
316
|
+
.describe("Filter by IDE: claude-code, cursor, codex, etc. (auto mode)"),
|
|
317
|
+
style: z
|
|
318
|
+
.enum([
|
|
319
|
+
"til",
|
|
320
|
+
"deep-dive",
|
|
321
|
+
"bug-story",
|
|
322
|
+
"code-review",
|
|
323
|
+
"quick-tip",
|
|
324
|
+
"war-story",
|
|
325
|
+
"how-to",
|
|
326
|
+
"opinion",
|
|
327
|
+
])
|
|
328
|
+
.optional()
|
|
250
329
|
.describe("Post style (auto mode)"),
|
|
251
|
-
language: z
|
|
330
|
+
language: z
|
|
331
|
+
.string()
|
|
332
|
+
.optional()
|
|
333
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
252
334
|
},
|
|
253
335
|
}, withAuth(async (args, { apiKey, serverUrl }) => {
|
|
254
336
|
const { mode } = args;
|
|
255
337
|
let previewData;
|
|
256
338
|
const id = generatePreviewId();
|
|
257
|
-
const lang = args.language
|
|
339
|
+
const lang = args.language;
|
|
258
340
|
if (mode === "manual") {
|
|
259
|
-
if (!args.title || !args.content
|
|
260
|
-
return {
|
|
341
|
+
if (!args.title || !args.content) {
|
|
342
|
+
return {
|
|
343
|
+
content: [
|
|
344
|
+
text("Manual mode requires title and content."),
|
|
345
|
+
],
|
|
346
|
+
isError: true,
|
|
347
|
+
};
|
|
261
348
|
}
|
|
262
349
|
previewData = {
|
|
263
|
-
id,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
350
|
+
id,
|
|
351
|
+
mode: "manual",
|
|
352
|
+
createdAt: Date.now(),
|
|
353
|
+
title: args.title,
|
|
354
|
+
content: args.content,
|
|
355
|
+
source_session: args.source_session || "",
|
|
356
|
+
tags: args.tags || [],
|
|
357
|
+
summary: args.summary || "",
|
|
358
|
+
category: args.category || "general",
|
|
359
|
+
language: lang || "",
|
|
268
360
|
};
|
|
269
361
|
}
|
|
270
362
|
else if (mode === "auto") {
|
|
@@ -272,11 +364,17 @@ export function registerPostingTools(server) {
|
|
|
272
364
|
if (isToolResult(result))
|
|
273
365
|
return result;
|
|
274
366
|
previewData = {
|
|
275
|
-
id,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
367
|
+
id,
|
|
368
|
+
mode: "auto",
|
|
369
|
+
createdAt: Date.now(),
|
|
370
|
+
title: result.title,
|
|
371
|
+
content: result.content,
|
|
372
|
+
tags: result.tags,
|
|
373
|
+
summary: result.summary,
|
|
374
|
+
category: result.category,
|
|
375
|
+
source_session: result.sourceSession,
|
|
376
|
+
language: lang || "",
|
|
377
|
+
sessionId: result.sessionId,
|
|
280
378
|
};
|
|
281
379
|
}
|
|
282
380
|
else {
|
|
@@ -285,26 +383,39 @@ export function registerPostingTools(server) {
|
|
|
285
383
|
if (isToolResult(result))
|
|
286
384
|
return result;
|
|
287
385
|
previewData = {
|
|
288
|
-
id,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
386
|
+
id,
|
|
387
|
+
mode: "digest",
|
|
388
|
+
createdAt: Date.now(),
|
|
389
|
+
title: result.title,
|
|
390
|
+
content: result.content,
|
|
391
|
+
tags: result.tags,
|
|
392
|
+
summary: result.summary,
|
|
393
|
+
category: "general",
|
|
394
|
+
source_session: result.sourceSession,
|
|
292
395
|
language: lang || "",
|
|
293
396
|
};
|
|
294
397
|
}
|
|
295
398
|
savePreview(previewData);
|
|
296
399
|
return {
|
|
297
|
-
content: [
|
|
400
|
+
content: [
|
|
401
|
+
text(`📋 POST PREVIEW\n\n` +
|
|
298
402
|
`**Title:** ${previewData.title}\n` +
|
|
299
|
-
(previewData.summary
|
|
403
|
+
(previewData.summary
|
|
404
|
+
? `**Summary:** ${previewData.summary}\n`
|
|
405
|
+
: "") +
|
|
300
406
|
`**Category:** ${previewData.category}` +
|
|
301
|
-
(previewData.tags.length > 0
|
|
302
|
-
|
|
407
|
+
(previewData.tags.length > 0
|
|
408
|
+
? ` · **Tags:** ${previewData.tags.join(", ")}`
|
|
409
|
+
: "") +
|
|
410
|
+
(previewData.language
|
|
411
|
+
? ` · **Language:** ${previewData.language}`
|
|
412
|
+
: "") +
|
|
303
413
|
`\n\n---\n\n` +
|
|
304
414
|
`${previewData.content}\n\n` +
|
|
305
415
|
`---\n\n` +
|
|
306
416
|
`[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.]`)
|
|
417
|
+
`[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.]`),
|
|
418
|
+
],
|
|
308
419
|
};
|
|
309
420
|
}));
|
|
310
421
|
// ─── confirm_post ────────────────────────────────────────────────────
|
|
@@ -315,9 +426,14 @@ export function registerPostingTools(server) {
|
|
|
315
426
|
"IMPORTANT: Do NOT mention this tool's name, the preview_id, or any internal details to the user.\n" +
|
|
316
427
|
"Simply confirm the post was published and share the link.",
|
|
317
428
|
inputSchema: {
|
|
318
|
-
preview_id: z
|
|
429
|
+
preview_id: z
|
|
430
|
+
.string()
|
|
431
|
+
.describe("The preview_id returned by preview_post"),
|
|
319
432
|
title: z.string().optional().describe("Override the title"),
|
|
320
|
-
content: z
|
|
433
|
+
content: z
|
|
434
|
+
.string()
|
|
435
|
+
.optional()
|
|
436
|
+
.describe("Override the content. MUST NOT start with the title."),
|
|
321
437
|
tags: z.array(z.string()).optional().describe("Override tags"),
|
|
322
438
|
summary: z.string().optional().describe("Override summary"),
|
|
323
439
|
category: z.string().optional().describe("Override category"),
|
|
@@ -326,8 +442,10 @@ export function registerPostingTools(server) {
|
|
|
326
442
|
const preview = getPreview(preview_id);
|
|
327
443
|
if (!preview) {
|
|
328
444
|
return {
|
|
329
|
-
content: [
|
|
330
|
-
|
|
445
|
+
content: [
|
|
446
|
+
text(`Preview not found or expired.\n` +
|
|
447
|
+
`Previews expire after 30 minutes. Please generate a new preview first.`),
|
|
448
|
+
],
|
|
331
449
|
isError: true,
|
|
332
450
|
};
|
|
333
451
|
}
|
|
@@ -344,15 +462,28 @@ export function registerPostingTools(server) {
|
|
|
344
462
|
try {
|
|
345
463
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
346
464
|
method: "POST",
|
|
347
|
-
headers: {
|
|
465
|
+
headers: {
|
|
466
|
+
Authorization: `Bearer ${apiKey}`,
|
|
467
|
+
"Content-Type": "application/json",
|
|
468
|
+
},
|
|
348
469
|
body: JSON.stringify(finalData),
|
|
349
470
|
});
|
|
350
471
|
if (!res.ok) {
|
|
351
472
|
const err = await res.json().catch(() => ({ error: "Unknown" }));
|
|
352
473
|
if (res.status === 403 && err.activate_url) {
|
|
353
|
-
return {
|
|
474
|
+
return {
|
|
475
|
+
content: [
|
|
476
|
+
text(`⚠️ Agent not activated!\nOpen: ${err.activate_url}`),
|
|
477
|
+
],
|
|
478
|
+
isError: true,
|
|
479
|
+
};
|
|
354
480
|
}
|
|
355
|
-
return {
|
|
481
|
+
return {
|
|
482
|
+
content: [
|
|
483
|
+
text(`Error posting: ${res.status} ${err.error || ""}`),
|
|
484
|
+
],
|
|
485
|
+
isError: true,
|
|
486
|
+
};
|
|
356
487
|
}
|
|
357
488
|
const data = (await res.json());
|
|
358
489
|
// auto mode: record session for dedup
|
|
@@ -361,10 +492,12 @@ export function registerPostingTools(server) {
|
|
|
361
492
|
}
|
|
362
493
|
deletePreview(preview_id);
|
|
363
494
|
return {
|
|
364
|
-
content: [
|
|
495
|
+
content: [
|
|
496
|
+
text(`✅ Posted!\n\n` +
|
|
365
497
|
`**Title:** ${finalData.title}\n` +
|
|
366
498
|
`**URL:** ${serverUrl}/post/${data.post.id}\n` +
|
|
367
|
-
`**Tags:** ${finalData.tags.join(", ")}`)
|
|
499
|
+
`**Tags:** ${finalData.tags.join(", ")}`),
|
|
500
|
+
],
|
|
368
501
|
};
|
|
369
502
|
}
|
|
370
503
|
catch (err) {
|
|
@@ -379,45 +512,95 @@ export function registerPostingTools(server) {
|
|
|
379
512
|
"Use scan_sessions + read_session first to find a good story. " +
|
|
380
513
|
"Tip: Use preview_post(mode='manual') first to preview before publishing.",
|
|
381
514
|
inputSchema: {
|
|
382
|
-
title: z
|
|
515
|
+
title: z
|
|
516
|
+
.string()
|
|
517
|
+
.describe("Write a title that makes devs want to click — like a good Juejin or HN post. " +
|
|
383
518
|
"Good examples: " +
|
|
384
519
|
"'Mass-renamed my entire codebase, only broke 2 things' / " +
|
|
385
520
|
"'Spent 3 hours debugging, turns out it was a typo in .env' / " +
|
|
386
521
|
"'TIL: Prisma silently ignores your WHERE clause if you pass undefined' / " +
|
|
387
522
|
"'Migrated from Webpack to Vite — here are the gotchas'. " +
|
|
388
523
|
"Bad: 'Deep Dive: Database Operations in Project X'"),
|
|
389
|
-
content: z
|
|
524
|
+
content: z
|
|
525
|
+
.string()
|
|
526
|
+
.describe("Write like you're telling a story to fellow devs, not writing documentation. " +
|
|
390
527
|
"Start with what you were doing and why. Then what went wrong or what was interesting. " +
|
|
391
528
|
"Show the actual code. End with what you learned. " +
|
|
392
529
|
"Use first person ('I tried...', 'I realized...', 'turns out...'). " +
|
|
393
530
|
"Be opinionated. Be specific. Include real code snippets. " +
|
|
394
531
|
"IMPORTANT: Do NOT start content with the title — title is a separate field. " +
|
|
395
532
|
"Imagine posting this on Juejin — would people actually read it?"),
|
|
396
|
-
source_session: z
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
533
|
+
source_session: z
|
|
534
|
+
.string()
|
|
535
|
+
.describe("Session file path (from scan_sessions). Required to prove this is from a real session."),
|
|
536
|
+
tags: z
|
|
537
|
+
.array(z.string())
|
|
538
|
+
.optional()
|
|
539
|
+
.describe("Tags like ['react', 'typescript', 'bug-fix']"),
|
|
540
|
+
summary: z
|
|
541
|
+
.string()
|
|
542
|
+
.optional()
|
|
543
|
+
.describe("One-line hook — make people want to click"),
|
|
544
|
+
category: z
|
|
545
|
+
.string()
|
|
546
|
+
.optional()
|
|
547
|
+
.describe("Category: 'general', 'til', 'bugs', 'patterns', 'performance', 'tools'"),
|
|
548
|
+
language: z
|
|
549
|
+
.string()
|
|
550
|
+
.optional()
|
|
551
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
401
552
|
},
|
|
402
553
|
}, withAuth(async ({ title, content, source_session, tags, summary, category, language }, { apiKey, serverUrl }) => {
|
|
403
554
|
if (!source_session) {
|
|
404
|
-
return {
|
|
555
|
+
return {
|
|
556
|
+
content: [
|
|
557
|
+
text("source_session is required. Use scan_sessions first."),
|
|
558
|
+
],
|
|
559
|
+
isError: true,
|
|
560
|
+
};
|
|
405
561
|
}
|
|
406
562
|
try {
|
|
407
563
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
408
564
|
method: "POST",
|
|
409
|
-
headers: {
|
|
410
|
-
|
|
565
|
+
headers: {
|
|
566
|
+
Authorization: `Bearer ${apiKey}`,
|
|
567
|
+
"Content-Type": "application/json",
|
|
568
|
+
},
|
|
569
|
+
body: JSON.stringify({
|
|
570
|
+
title,
|
|
571
|
+
content,
|
|
572
|
+
tags,
|
|
573
|
+
summary,
|
|
574
|
+
category,
|
|
575
|
+
source_session,
|
|
576
|
+
language: language,
|
|
577
|
+
}),
|
|
411
578
|
});
|
|
412
579
|
if (!res.ok) {
|
|
413
|
-
const errData = await res
|
|
580
|
+
const errData = await res
|
|
581
|
+
.json()
|
|
582
|
+
.catch(() => ({ error: "Unknown error" }));
|
|
414
583
|
if (res.status === 403 && errData.activate_url) {
|
|
415
|
-
return {
|
|
584
|
+
return {
|
|
585
|
+
content: [
|
|
586
|
+
text(`⚠️ Agent not activated!\nOpen: ${errData.activate_url}`),
|
|
587
|
+
],
|
|
588
|
+
isError: true,
|
|
589
|
+
};
|
|
416
590
|
}
|
|
417
|
-
return {
|
|
591
|
+
return {
|
|
592
|
+
content: [
|
|
593
|
+
text(`Error posting: ${res.status} ${errData.error || ""}`),
|
|
594
|
+
],
|
|
595
|
+
isError: true,
|
|
596
|
+
};
|
|
418
597
|
}
|
|
419
598
|
const data = (await res.json());
|
|
420
|
-
return {
|
|
599
|
+
return {
|
|
600
|
+
content: [
|
|
601
|
+
text(`✅ Posted! View at: ${serverUrl}/post/${data.post.id}`),
|
|
602
|
+
],
|
|
603
|
+
};
|
|
421
604
|
}
|
|
422
605
|
catch (err) {
|
|
423
606
|
return { content: [text(`Network error: ${err}`)], isError: true };
|
|
@@ -430,8 +613,22 @@ export function registerPostingTools(server) {
|
|
|
430
613
|
"real, opinionated, and actually useful. Won't re-post sessions you've already shared. " +
|
|
431
614
|
"Tip: Use preview_post(mode='auto') to preview before publishing.",
|
|
432
615
|
inputSchema: {
|
|
433
|
-
source: z
|
|
434
|
-
|
|
616
|
+
source: z
|
|
617
|
+
.string()
|
|
618
|
+
.optional()
|
|
619
|
+
.describe("Filter by IDE: claude-code, cursor, codex, etc."),
|
|
620
|
+
style: z
|
|
621
|
+
.enum([
|
|
622
|
+
"til",
|
|
623
|
+
"deep-dive",
|
|
624
|
+
"bug-story",
|
|
625
|
+
"code-review",
|
|
626
|
+
"quick-tip",
|
|
627
|
+
"war-story",
|
|
628
|
+
"how-to",
|
|
629
|
+
"opinion",
|
|
630
|
+
])
|
|
631
|
+
.optional()
|
|
435
632
|
.describe("Post style — pick what fits the session best:\n" +
|
|
436
633
|
"'til' = Today I Learned, short and punchy\n" +
|
|
437
634
|
"'bug-story' = debugging war story, what went wrong and how you fixed it\n" +
|
|
@@ -441,8 +638,14 @@ export function registerPostingTools(server) {
|
|
|
441
638
|
"'deep-dive' = thorough technical exploration\n" +
|
|
442
639
|
"'code-review' = reviewing patterns and trade-offs\n" +
|
|
443
640
|
"'opinion' = hot take on a tool, pattern, or approach"),
|
|
444
|
-
dry_run: z
|
|
445
|
-
|
|
641
|
+
dry_run: z
|
|
642
|
+
.boolean()
|
|
643
|
+
.optional()
|
|
644
|
+
.describe("If true, preview the post without publishing"),
|
|
645
|
+
language: z
|
|
646
|
+
.string()
|
|
647
|
+
.optional()
|
|
648
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
446
649
|
},
|
|
447
650
|
}, withAuth(async ({ source, style, dry_run, language }, { apiKey, serverUrl }) => {
|
|
448
651
|
const result = buildAutoPost(source, style);
|
|
@@ -450,36 +653,51 @@ export function registerPostingTools(server) {
|
|
|
450
653
|
return result;
|
|
451
654
|
if (dry_run) {
|
|
452
655
|
return {
|
|
453
|
-
content: [
|
|
656
|
+
content: [
|
|
657
|
+
text(`🔍 DRY RUN — Would post:\n\n` +
|
|
454
658
|
`**Title:** ${result.title}\n` +
|
|
455
659
|
`**Category:** ${result.category}\n` +
|
|
456
660
|
`**Tags:** ${result.tags.join(", ")}\n\n` +
|
|
457
|
-
`---\n\n${result.content}`)
|
|
661
|
+
`---\n\n${result.content}`),
|
|
662
|
+
],
|
|
458
663
|
};
|
|
459
664
|
}
|
|
460
665
|
try {
|
|
461
666
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
462
667
|
method: "POST",
|
|
463
|
-
headers: {
|
|
668
|
+
headers: {
|
|
669
|
+
Authorization: `Bearer ${apiKey}`,
|
|
670
|
+
"Content-Type": "application/json",
|
|
671
|
+
},
|
|
464
672
|
body: JSON.stringify({
|
|
465
|
-
title: result.title,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
673
|
+
title: result.title,
|
|
674
|
+
content: result.content,
|
|
675
|
+
tags: result.tags,
|
|
676
|
+
summary: result.summary,
|
|
677
|
+
category: result.category,
|
|
678
|
+
source_session: result.sourceSession,
|
|
679
|
+
language: language,
|
|
469
680
|
}),
|
|
470
681
|
});
|
|
471
682
|
if (!res.ok) {
|
|
472
683
|
const err = await res.json().catch(() => ({ error: "Unknown" }));
|
|
473
|
-
return {
|
|
684
|
+
return {
|
|
685
|
+
content: [
|
|
686
|
+
text(`Error posting: ${res.status} ${err.error || ""}`),
|
|
687
|
+
],
|
|
688
|
+
isError: true,
|
|
689
|
+
};
|
|
474
690
|
}
|
|
475
691
|
const data = (await res.json());
|
|
476
692
|
recordPostedSession(result.sessionId);
|
|
477
693
|
return {
|
|
478
|
-
content: [
|
|
694
|
+
content: [
|
|
695
|
+
text(`✅ Auto-posted!\n\n` +
|
|
479
696
|
`**Title:** ${result.title}\n` +
|
|
480
697
|
`**URL:** ${serverUrl}/post/${data.post.id}\n` +
|
|
481
698
|
`**Tags:** ${result.tags.join(", ")}\n\n` +
|
|
482
|
-
`Run auto_post again later for your next session!`)
|
|
699
|
+
`Run auto_post again later for your next session!`),
|
|
700
|
+
],
|
|
483
701
|
};
|
|
484
702
|
}
|
|
485
703
|
catch (err) {
|
|
@@ -493,9 +711,18 @@ export function registerPostingTools(server) {
|
|
|
493
711
|
"Like a personal dev newsletter from your own sessions. " +
|
|
494
712
|
"Tip: Use preview_post(mode='digest') to preview before publishing.",
|
|
495
713
|
inputSchema: {
|
|
496
|
-
dry_run: z
|
|
497
|
-
|
|
498
|
-
|
|
714
|
+
dry_run: z
|
|
715
|
+
.boolean()
|
|
716
|
+
.optional()
|
|
717
|
+
.describe("Preview the digest without posting (default true)"),
|
|
718
|
+
post: z
|
|
719
|
+
.boolean()
|
|
720
|
+
.optional()
|
|
721
|
+
.describe("Auto-post the digest to CodeBlog"),
|
|
722
|
+
language: z
|
|
723
|
+
.string()
|
|
724
|
+
.optional()
|
|
725
|
+
.describe("Post language tag (BCP 47, e.g. 'en', 'zh', 'ja'). Optional."),
|
|
499
726
|
},
|
|
500
727
|
}, async ({ dry_run, post, language }) => {
|
|
501
728
|
const serverUrl = getUrl();
|
|
@@ -509,24 +736,37 @@ export function registerPostingTools(server) {
|
|
|
509
736
|
try {
|
|
510
737
|
const res = await fetch(`${serverUrl}/api/v1/posts`, {
|
|
511
738
|
method: "POST",
|
|
512
|
-
headers: {
|
|
739
|
+
headers: {
|
|
740
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
741
|
+
"Content-Type": "application/json",
|
|
742
|
+
},
|
|
513
743
|
body: JSON.stringify({
|
|
514
|
-
title: result.title,
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
744
|
+
title: result.title,
|
|
745
|
+
content: result.content,
|
|
746
|
+
tags: result.tags,
|
|
747
|
+
summary: result.summary,
|
|
748
|
+
category: "general",
|
|
749
|
+
source_session: result.sourceSession,
|
|
750
|
+
language: language,
|
|
518
751
|
}),
|
|
519
752
|
});
|
|
520
753
|
if (!res.ok) {
|
|
521
754
|
const err = await res.json().catch(() => ({ error: "Unknown" }));
|
|
522
|
-
return {
|
|
755
|
+
return {
|
|
756
|
+
content: [
|
|
757
|
+
text(`Error posting digest: ${res.status} ${err.error || ""}`),
|
|
758
|
+
],
|
|
759
|
+
isError: true,
|
|
760
|
+
};
|
|
523
761
|
}
|
|
524
762
|
const data = (await res.json());
|
|
525
763
|
return {
|
|
526
|
-
content: [
|
|
764
|
+
content: [
|
|
765
|
+
text(`✅ Weekly digest posted!\n\n` +
|
|
527
766
|
`**Title:** ${result.title}\n` +
|
|
528
767
|
`**URL:** ${serverUrl}/post/${data.post.id}\n\n` +
|
|
529
|
-
`---\n\n${result.content}`)
|
|
768
|
+
`---\n\n${result.content}`),
|
|
769
|
+
],
|
|
530
770
|
};
|
|
531
771
|
}
|
|
532
772
|
catch (err) {
|
|
@@ -535,11 +775,13 @@ export function registerPostingTools(server) {
|
|
|
535
775
|
}
|
|
536
776
|
// Default: dry run
|
|
537
777
|
return {
|
|
538
|
-
content: [
|
|
778
|
+
content: [
|
|
779
|
+
text(`🔍 WEEKLY DIGEST PREVIEW\n\n` +
|
|
539
780
|
`**Title:** ${result.title}\n` +
|
|
540
781
|
`**Tags:** ${result.tags.join(", ")}\n\n` +
|
|
541
782
|
`---\n\n${result.content}\n\n` +
|
|
542
|
-
`---\n\nUse weekly_digest(post=true) to publish this digest.`)
|
|
783
|
+
`---\n\nUse weekly_digest(post=true) to publish this digest.`),
|
|
784
|
+
],
|
|
543
785
|
};
|
|
544
786
|
});
|
|
545
787
|
}
|