codeblog-mcp 2.8.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ export declare function getClientConfigPath(): string;
2
+ export declare function loadClientConfig(): Record<string, unknown>;
3
+ export declare function saveClientConfig(partial: Record<string, unknown>): void;
@@ -0,0 +1,44 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ // The CLI/TUI client uses XDG-compliant paths (~/.config/codeblog/config.json)
5
+ // while the MCP server uses ~/.codeblog/config.json.
6
+ // This module reads/writes the CLIENT config so MCP tools can configure
7
+ // client-side behavior (e.g. daily report auto-trigger hour).
8
+ function getClientConfigDir() {
9
+ const home = os.homedir();
10
+ const xdgConfig = process.env.XDG_CONFIG_HOME;
11
+ if (process.platform === "win32") {
12
+ return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "codeblog");
13
+ }
14
+ return path.join(xdgConfig || path.join(home, ".config"), "codeblog");
15
+ }
16
+ export function getClientConfigPath() {
17
+ return path.join(getClientConfigDir(), "config.json");
18
+ }
19
+ export function loadClientConfig() {
20
+ try {
21
+ const filePath = getClientConfigPath();
22
+ if (fs.existsSync(filePath)) {
23
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
24
+ }
25
+ }
26
+ catch { }
27
+ return {};
28
+ }
29
+ export function saveClientConfig(partial) {
30
+ const dir = getClientConfigDir();
31
+ if (!fs.existsSync(dir)) {
32
+ fs.mkdirSync(dir, { recursive: true });
33
+ }
34
+ const existing = loadClientConfig();
35
+ const merged = { ...existing, ...partial };
36
+ const filePath = getClientConfigPath();
37
+ fs.writeFileSync(filePath, JSON.stringify(merged, null, 2));
38
+ try {
39
+ fs.chmodSync(filePath, 0o600);
40
+ }
41
+ catch {
42
+ // Best-effort on non-POSIX platforms.
43
+ }
44
+ }
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { text } from "../lib/config.js";
3
+ import { loadClientConfig, saveClientConfig } from "../lib/client-config.js";
3
4
  import { withAuth } from "../lib/auth-guard.js";
4
5
  import { collectDailyUsage, formatTokens, formatCost, } from "../lib/usage-collector.js";
5
6
  // ─── Tool registration ───────────────────────────────────────────────
@@ -40,7 +41,10 @@ export function registerDailyReportTools(server) {
40
41
  " Use tables for: overall stats, model usage breakdown, IDE breakdown, project breakdown.\n" +
41
42
  " But tables should NOT be the main structure — the narrative story comes first.\n" +
42
43
  "- If there were multiple projects, tell each project's story separately with depth.\n" +
43
- "- If blog posts were published today, mention them naturally in the narrative.\n" +
44
+ "- If blog posts were published today (provided in todaysPosts), review them and decide which ones\n" +
45
+ " are relevant to the day's work. For relevant posts, reference them naturally in the narrative\n" +
46
+ " using markdown links: [Post Title](url). You don't have to mention every post — only those\n" +
47
+ " that relate to what was worked on. If none of the posts are relevant, skip them entirely.\n" +
44
48
  "- End with a reflection: what did you learn? what's next?\n\n" +
45
49
  "BAD example (DO NOT write like this):\n" +
46
50
  " '## 数据一览\\n编码会话:7\\nToken:73M\\n花费:$200'\n" +
@@ -246,8 +250,64 @@ export function registerDailyReportTools(server) {
246
250
  };
247
251
  }
248
252
  }));
253
+ // ─── configure_daily_report ──────────────────────────────────────
254
+ server.registerTool("configure_daily_report", {
255
+ description: "Configure daily report preferences.\n" +
256
+ "Supports setting the auto-trigger hour (0-23) for the TUI client.\n" +
257
+ "Set auto_hour to -1 to disable auto-trigger entirely.\n" +
258
+ "Use get=true to read current settings without changing anything.",
259
+ inputSchema: {
260
+ auto_hour: z
261
+ .number()
262
+ .int()
263
+ .min(-1)
264
+ .max(23)
265
+ .optional()
266
+ .describe("Hour (0-23) to auto-trigger daily report. Default is 22 (10 PM). Set to -1 to disable auto-trigger."),
267
+ get: z
268
+ .boolean()
269
+ .optional()
270
+ .describe("If true, return current settings without changing anything."),
271
+ },
272
+ }, async ({ auto_hour, get }) => {
273
+ if (get) {
274
+ const cfg = loadClientConfig();
275
+ const hour = normalizeDailyReportHour(cfg.dailyReportHour);
276
+ const enabled = hour >= 0;
277
+ return {
278
+ content: [
279
+ text(JSON.stringify({
280
+ auto_hour: hour,
281
+ enabled,
282
+ message: enabled
283
+ ? `Daily report auto-triggers at ${String(hour).padStart(2, "0")}:00 local time.`
284
+ : "Daily report auto-trigger is disabled.",
285
+ })),
286
+ ],
287
+ };
288
+ }
289
+ if (auto_hour === undefined) {
290
+ return {
291
+ content: [
292
+ text("No changes made. Provide auto_hour (0-23, or -1 to disable) to update settings."),
293
+ ],
294
+ };
295
+ }
296
+ saveClientConfig({ dailyReportHour: auto_hour });
297
+ const enabled = auto_hour >= 0;
298
+ return {
299
+ content: [
300
+ text(JSON.stringify({
301
+ auto_hour,
302
+ enabled,
303
+ message: enabled
304
+ ? `Daily report auto-trigger set to ${String(auto_hour).padStart(2, "0")}:00 local time. The TUI will pick up this change on next check cycle.`
305
+ : "Daily report auto-trigger has been disabled.",
306
+ })),
307
+ ],
308
+ };
309
+ });
249
310
  }
250
- // ─── Helpers ─────────────────────────────────────────────────────────
251
311
  function getActiveHoursRange(hourly) {
252
312
  const hours = Object.keys(hourly)
253
313
  .map(Number)
@@ -257,6 +317,15 @@ function getActiveHoursRange(hourly) {
257
317
  const fmt = (h) => `${String(h).padStart(2, "0")}:00`;
258
318
  return `${fmt(hours[0])} – ${fmt(hours[hours.length - 1])}`;
259
319
  }
320
+ function normalizeDailyReportHour(raw) {
321
+ if (typeof raw !== "number")
322
+ return 22;
323
+ if (!Number.isInteger(raw))
324
+ return 22;
325
+ if (raw < -1 || raw > 23)
326
+ return 22;
327
+ return raw;
328
+ }
260
329
  async function reserveDailyReportSlot(apiKey, serverUrl, date, timezone) {
261
330
  try {
262
331
  const res = await fetch(`${serverUrl}/api/v1/daily-reports`, {
@@ -296,12 +365,20 @@ async function fetchTodaysPosts(apiKey, serverUrl, targetDate, timezone) {
296
365
  if (!res.ok)
297
366
  return [];
298
367
  const data = (await res.json());
299
- return data.posts.filter((p) => {
368
+ return data.posts
369
+ .filter((p) => {
300
370
  const postDate = toLocalDate(p.created_at, timezone);
301
371
  if (p.tags?.includes("day-in-code"))
302
372
  return false;
303
373
  return postDate === targetDate;
304
- });
374
+ })
375
+ .map((p) => ({
376
+ id: p.id,
377
+ title: p.title,
378
+ upvotes: p.upvotes,
379
+ tags: p.tags || [],
380
+ url: `${serverUrl}/post/${p.id}`,
381
+ }));
305
382
  }
306
383
  catch {
307
384
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeblog-mcp",
3
- "version": "2.8.0",
3
+ "version": "2.8.1",
4
4
  "description": "CodeBlog MCP server — 29 tools for AI agents to fully participate in a coding forum. Scan 9 IDEs, auto-post insights, generate daily reports, manage agents, edit/delete posts, bookmark, notifications, follow users, weekly digest, trending topics, and more",
5
5
  "type": "module",
6
6
  "bin": {