@levelup-log/mcp-server 0.2.0 → 0.4.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.
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/config.ts
4
+ var CONFIG = {
5
+ SUPABASE_URL: process.env.LEVELUP_SUPABASE_URL || "https://hkuvfhfwbhkjmeqvwrxy.supabase.co",
6
+ SUPABASE_ANON_KEY: process.env.LEVELUP_SUPABASE_ANON_KEY || "sb_publishable_RilZivOWm3FIs6ueIA67Fw_07ZKjoEY",
7
+ AUTH_PORT: parseInt(process.env.LEVELUP_AUTH_PORT || "19876", 10),
8
+ DEBUG: process.env.LEVELUP_DEBUG === "true"
9
+ };
10
+ var CATEGORY_DEFINITIONS = {
11
+ // ── Technical ──────────────────────────────────────────────────────────────
12
+ code: {
13
+ label: "Coding",
14
+ emoji: "\u{1F4BB}",
15
+ description: "\u5BEB\u7A0B\u5F0F\u3001\u529F\u80FD\u958B\u767C\u3001\u65B0\u589E\u529F\u80FD",
16
+ output_unit: "\u6A94\u6848/\u6A21\u7D44\uFF08\u5C0F\u51FD\u5F0F=1, \u5927\u6A94=5-10, \u591A\u7D44\u4EF6=15\uFF09",
17
+ input_unit: "\u6A94\u6848\u95B1\u8B80 / API \u6587\u4EF6\u67E5\u95B1",
18
+ xp_weight: 1
19
+ },
20
+ fix: {
21
+ label: "Bug Fix",
22
+ emoji: "\u{1FAB2}",
23
+ description: "\u4FEE bug\u3001\u4FEE\u5FA9\u932F\u8AA4\u884C\u70BA",
24
+ output_unit: "\u4FEE\u5FA9\u7684 bug \u6578\uFF08\u542B\u6E2C\u8A66\u9A57\u8B49 = +1\uFF09",
25
+ input_unit: "\u932F\u8AA4\u65E5\u8A8C / \u7A0B\u5F0F\u78BC\u8FFD\u8E64",
26
+ xp_weight: 1.1
27
+ },
28
+ deploy: {
29
+ label: "Deploy",
30
+ emoji: "\u{1F680}",
31
+ description: "\u90E8\u7F72\u4E0A\u7DDA\u3001\u767C\u5E03\u7248\u672C\u3001\u63A8\u9001 npm / App Store",
32
+ output_unit: "\u90E8\u7F72\u76EE\u6A19\u6578\uFF081 \u670D\u52D9/\u74B0\u5883 = 1\uFF09",
33
+ input_unit: "\u8A2D\u5B9A\u6A94 / CI logs \u67E5\u95B1",
34
+ xp_weight: 1.3
35
+ },
36
+ test: {
37
+ label: "Testing",
38
+ emoji: "\u{1F9EA}",
39
+ description: "\u5BEB\u6E2C\u8A66\u3001E2E\u3001TDD",
40
+ output_unit: "\u6E2C\u8A66\u6848\u4F8B\u6578\uFF081 test case = 1\uFF09",
41
+ input_unit: "\u5F85\u6E2C\u7A0B\u5F0F\u78BC\u95B1\u8B80",
42
+ xp_weight: 1
43
+ },
44
+ docs: {
45
+ label: "Documentation",
46
+ emoji: "\u{1F4DD}",
47
+ description: "\u5BEB\u6587\u4EF6\u3001README\u3001API \u8AAA\u660E",
48
+ output_unit: "\u9801\u6578 / \u7AE0\u7BC0\u6578\uFF081 \u5B8C\u6574\u6BB5\u843D = 1\uFF09",
49
+ input_unit: "\u53C3\u8003\u8CC7\u6599 / \u73FE\u6709\u6587\u4EF6",
50
+ xp_weight: 0.9
51
+ },
52
+ refactor: {
53
+ label: "Refactor",
54
+ emoji: "\u{1F527}",
55
+ description: "\u91CD\u69CB\u3001\u6539\u5584\u7A0B\u5F0F\u78BC\u54C1\u8CEA",
56
+ output_unit: "\u91CD\u69CB\u7684\u6A21\u7D44/\u51FD\u5F0F\u6578",
57
+ input_unit: "\u95B1\u8B80\u73FE\u6709\u7A0B\u5F0F\u78BC",
58
+ xp_weight: 1
59
+ },
60
+ review: {
61
+ label: "Code Review",
62
+ emoji: "\u{1F441}",
63
+ description: "Code review\u3001PR \u5BE9\u67E5",
64
+ output_unit: "\u5BE9\u67E5\u7684 PR / \u6A94\u6848\u6578",
65
+ input_unit: "\u95B1\u8B80\u7684\u7A0B\u5F0F\u78BC\uFF08\u6BCF 100 \u884C = 1\uFF09",
66
+ xp_weight: 0.9
67
+ },
68
+ ops: {
69
+ label: "DevOps / Ops",
70
+ emoji: "\u2699\uFE0F",
71
+ description: "\u7DAD\u904B\u3001\u57FA\u790E\u8A2D\u65BD\u3001\u76E3\u63A7\u3001CI/CD",
72
+ output_unit: "\u8A2D\u5B9A/\u8173\u672C/\u670D\u52D9\u6578\uFF081 \u5B8C\u6210\u9805\u76EE = 1\uFF09",
73
+ input_unit: "\u65E5\u8A8C / \u6587\u4EF6\u67E5\u95B1",
74
+ xp_weight: 1.2
75
+ },
76
+ // ── Knowledge ──────────────────────────────────────────────────────────────
77
+ learn: {
78
+ label: "Learning",
79
+ emoji: "\u{1F4DA}",
80
+ description: "\u5B78\u7FD2\u65B0\u77E5\u3001\u95B1\u8B80\u6280\u8853\u6587\u7AE0\u3001\u4E0A\u8AB2\u3001\u8A9E\u8A00\u5B78\u7FD2",
81
+ output_unit: "\u6982\u5FF5/\u7AE0\u7BC0/\u7DF4\u7FD2\u984C\uFF08\u638C\u63E1 1 \u500B\u65B0\u6982\u5FF5 = 1\uFF09",
82
+ input_unit: "\u95B1\u8B80\u7BC7\u6578 / \u5F71\u7247\u6578",
83
+ xp_weight: 1
84
+ },
85
+ // ── Milestones ─────────────────────────────────────────────────────────────
86
+ milestone: {
87
+ label: "Milestone",
88
+ emoji: "\u{1F3C6}",
89
+ description: "\u91CC\u7A0B\u7891\u3001\u91CD\u5927\u9054\u6210\u3001\u7279\u6B8A\u7D00\u5FF5",
90
+ output_unit: "\u91CC\u7A0B\u7891\u6578\uFF08\u901A\u5E38 = 1\uFF09",
91
+ input_unit: "\u6E96\u5099\u5DE5\u4F5C / \u7814\u7A76",
92
+ xp_weight: 1.5
93
+ },
94
+ // ── Life ───────────────────────────────────────────────────────────────────
95
+ life: {
96
+ label: "Life",
97
+ emoji: "\u{1F3E0}",
98
+ description: "\u65E5\u5E38\u751F\u6D3B\uFF1A\u5BB6\u52D9\u3001\u8DD1\u817F\u3001\u80B2\u5152\u3001\u63A1\u8CFC\u3001\u884C\u653F",
99
+ output_unit: "\u5B8C\u6210\u7684\u4E8B\u9805\uFF08\u8DD1\u817F=1, \u5E36\u5C0F\u5B69\u51FA\u9580=2\uFF09",
100
+ input_unit: "\u8A08\u756B / \u7814\u7A76\uFF08\u67E5\u8DEF\u7DDA\u3001\u6BD4\u50F9\u7B49\uFF09",
101
+ xp_weight: 0.9
102
+ },
103
+ health: {
104
+ label: "Health",
105
+ emoji: "\u{1F4AA}",
106
+ description: "\u904B\u52D5\u3001\u98F2\u98DF\u7BA1\u7406\u3001\u5C31\u91AB\u3001\u5065\u5EB7\u7FD2\u6163",
107
+ output_unit: "30 \u5206\u9418\u904B\u52D5\u584A \u6216 \u516C\u91CC\u6578 \u6216 \u5B8C\u6210\u7642\u7A0B",
108
+ input_unit: "\u67E5\u5065\u5EB7\u8CC7\u6599 / \u8FFD\u8E64\u8A18\u9304",
109
+ xp_weight: 1.1
110
+ },
111
+ finance: {
112
+ label: "Finance",
113
+ emoji: "\u{1F4B0}",
114
+ description: "\u8A18\u5E33\u3001\u6295\u8CC7\u3001\u7406\u8CA1\u898F\u5283\u3001\u5831\u7A05\u3001\u4FDD\u96AA",
115
+ output_unit: "\u5B8C\u6210\u7684\u8CA1\u52D9\u4EFB\u52D9\uFF081 \u9805\u76EE = 1\uFF09",
116
+ input_unit: "\u7814\u7A76\u5831\u544A / \u8CC7\u6599\u67E5\u95B1",
117
+ xp_weight: 1
118
+ },
119
+ social: {
120
+ label: "Social",
121
+ emoji: "\u{1F91D}",
122
+ description: "\u793E\u4EA4\u3001\u5E6B\u52A9\u4ED6\u4EBA\u3001\u805A\u6703\u3001\u793E\u7FA4\u8CA2\u737B",
123
+ output_unit: "\u4E92\u52D5\u7684\u4EBA\u6578 \u6216 \u5B8C\u6210\u7684\u793E\u4EA4\u4EFB\u52D9",
124
+ input_unit: "\u6E96\u5099 / \u80CC\u666F\u7814\u7A76",
125
+ xp_weight: 0.9
126
+ },
127
+ creative: {
128
+ label: "Creative",
129
+ emoji: "\u{1F3A8}",
130
+ description: "\u5275\u4F5C\uFF1A\u5BEB\u6587\u7AE0\u3001\u8A2D\u8A08\u3001\u97F3\u6A02\u3001\u5F71\u7247\u3001\u651D\u5F71",
131
+ output_unit: "\u5B8C\u6210\u54C1\uFF08\u6587\u7AE0=3, \u5716=1, \u77ED\u5F71\u7247=2\uFF09",
132
+ input_unit: "\u53C3\u8003\u8CC7\u6599 / \u7D20\u6750\u7814\u7A76",
133
+ xp_weight: 1
134
+ },
135
+ spiritual: {
136
+ label: "Spiritual",
137
+ emoji: "\u{1F52E}",
138
+ description: "\u8EAB\u5FC3\u9748\uFF1A\u51A5\u60F3\u3001\u5360\u535C\u3001\u7948\u79B1\u3001\u5100\u5F0F\u3001\u80FD\u91CF\u7DF4\u7FD2\u3001\u5167\u5728\u63A2\u7D22",
139
+ output_unit: "\u5B8C\u6210\u7684\u7DF4\u7FD2\uFF081 \u6B21\u51A5\u60F3=1, \u5360\u535C\u89E3\u8B80=2\uFF09",
140
+ input_unit: "\u5B78\u7FD2 / \u7814\u7A76\u9748\u6027\u8CC7\u6599",
141
+ xp_weight: 0.85
142
+ },
143
+ hobby: {
144
+ label: "Hobby",
145
+ emoji: "\u{1F3AE}",
146
+ description: "\u8208\u8DA3\u5A1B\u6A02\uFF1A\u6253\u96FB\u52D5\u3001\u770B\u96FB\u5F71/\u66F8\u3001\u6536\u85CF\u3001\u684C\u904A\u3001\u5712\u85DD\u3001\u70F9\u98EA",
147
+ output_unit: "\u5B8C\u6210\u7684\u6D3B\u52D5\u6BB5\uFF081 \u5C0F\u6642\u96FB\u73A9=1, \u5B8C\u6210\u4E00\u672C\u66F8=5\uFF09",
148
+ input_unit: "\u67E5\u8CC7\u6599 / \u653B\u7565\u7814\u7A76",
149
+ xp_weight: 0.8
150
+ }
151
+ };
152
+ var ACHIEVEMENT_CATEGORIES = Object.keys(
153
+ CATEGORY_DEFINITIONS
154
+ );
155
+
156
+ export {
157
+ CONFIG,
158
+ CATEGORY_DEFINITIONS,
159
+ ACHIEVEMENT_CATEGORIES
160
+ };
161
+ //# sourceMappingURL=chunk-FII2XEJ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/config.ts"],"sourcesContent":["export const CONFIG = {\n SUPABASE_URL:\n process.env.LEVELUP_SUPABASE_URL ||\n \"https://hkuvfhfwbhkjmeqvwrxy.supabase.co\",\n SUPABASE_ANON_KEY:\n process.env.LEVELUP_SUPABASE_ANON_KEY ||\n \"sb_publishable_RilZivOWm3FIs6ueIA67Fw_07ZKjoEY\",\n AUTH_PORT: parseInt(process.env.LEVELUP_AUTH_PORT || \"19876\", 10),\n DEBUG: process.env.LEVELUP_DEBUG === \"true\",\n} as const;\n\n// ─── Category Definitions ────────────────────────────────────────────────────\n// Single source of truth for all achievement categories.\n// ACHIEVEMENT_CATEGORIES and AchievementCategory are derived from this object.\n//\n// xp_weight: category difficulty/verifiability multiplier applied to XP\n// >1.0 = higher stakes or harder (deploy, ops, fix)\n// 1.0 = baseline (code, learn)\n// <1.0 = lighter or self-reported by nature (spiritual, hobby, social)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type CategoryDef = {\n label: string; // Display name\n emoji: string; // Visual identifier\n description: string; // What this category covers\n output_unit: string; // What 1 output_unit means for this category\n input_unit: string; // What 1 input_unit means for this category\n xp_weight: number; // Final XP multiplier (applied after base + bonuses)\n};\n\nexport const CATEGORY_DEFINITIONS = {\n // ── Technical ──────────────────────────────────────────────────────────────\n code: {\n label: \"Coding\",\n emoji: \"💻\",\n description: \"寫程式、功能開發、新增功能\",\n output_unit: \"檔案/模組(小函式=1, 大檔=5-10, 多組件=15)\",\n input_unit: \"檔案閱讀 / API 文件查閱\",\n xp_weight: 1.0,\n },\n fix: {\n label: \"Bug Fix\",\n emoji: \"🪲\",\n description: \"修 bug、修復錯誤行為\",\n output_unit: \"修復的 bug 數(含測試驗證 = +1)\",\n input_unit: \"錯誤日誌 / 程式碼追蹤\",\n xp_weight: 1.1,\n },\n deploy: {\n label: \"Deploy\",\n emoji: \"🚀\",\n description: \"部署上線、發布版本、推送 npm / App Store\",\n output_unit: \"部署目標數(1 服務/環境 = 1)\",\n input_unit: \"設定檔 / CI logs 查閱\",\n xp_weight: 1.3,\n },\n test: {\n label: \"Testing\",\n emoji: \"🧪\",\n description: \"寫測試、E2E、TDD\",\n output_unit: \"測試案例數(1 test case = 1)\",\n input_unit: \"待測程式碼閱讀\",\n xp_weight: 1.0,\n },\n docs: {\n label: \"Documentation\",\n emoji: \"📝\",\n description: \"寫文件、README、API 說明\",\n output_unit: \"頁數 / 章節數(1 完整段落 = 1)\",\n input_unit: \"參考資料 / 現有文件\",\n xp_weight: 0.9,\n },\n refactor: {\n label: \"Refactor\",\n emoji: \"🔧\",\n description: \"重構、改善程式碼品質\",\n output_unit: \"重構的模組/函式數\",\n input_unit: \"閱讀現有程式碼\",\n xp_weight: 1.0,\n },\n review: {\n label: \"Code Review\",\n emoji: \"👁\",\n description: \"Code review、PR 審查\",\n output_unit: \"審查的 PR / 檔案數\",\n input_unit: \"閱讀的程式碼(每 100 行 = 1)\",\n xp_weight: 0.9,\n },\n ops: {\n label: \"DevOps / Ops\",\n emoji: \"⚙️\",\n description: \"維運、基礎設施、監控、CI/CD\",\n output_unit: \"設定/腳本/服務數(1 完成項目 = 1)\",\n input_unit: \"日誌 / 文件查閱\",\n xp_weight: 1.2,\n },\n // ── Knowledge ──────────────────────────────────────────────────────────────\n learn: {\n label: \"Learning\",\n emoji: \"📚\",\n description: \"學習新知、閱讀技術文章、上課、語言學習\",\n output_unit: \"概念/章節/練習題(掌握 1 個新概念 = 1)\",\n input_unit: \"閱讀篇數 / 影片數\",\n xp_weight: 1.0,\n },\n // ── Milestones ─────────────────────────────────────────────────────────────\n milestone: {\n label: \"Milestone\",\n emoji: \"🏆\",\n description: \"里程碑、重大達成、特殊紀念\",\n output_unit: \"里程碑數(通常 = 1)\",\n input_unit: \"準備工作 / 研究\",\n xp_weight: 1.5,\n },\n // ── Life ───────────────────────────────────────────────────────────────────\n life: {\n label: \"Life\",\n emoji: \"🏠\",\n description: \"日常生活:家務、跑腿、育兒、採購、行政\",\n output_unit: \"完成的事項(跑腿=1, 帶小孩出門=2)\",\n input_unit: \"計畫 / 研究(查路線、比價等)\",\n xp_weight: 0.9,\n },\n health: {\n label: \"Health\",\n emoji: \"💪\",\n description: \"運動、飲食管理、就醫、健康習慣\",\n output_unit: \"30 分鐘運動塊 或 公里數 或 完成療程\",\n input_unit: \"查健康資料 / 追蹤記錄\",\n xp_weight: 1.1,\n },\n finance: {\n label: \"Finance\",\n emoji: \"💰\",\n description: \"記帳、投資、理財規劃、報稅、保險\",\n output_unit: \"完成的財務任務(1 項目 = 1)\",\n input_unit: \"研究報告 / 資料查閱\",\n xp_weight: 1.0,\n },\n social: {\n label: \"Social\",\n emoji: \"🤝\",\n description: \"社交、幫助他人、聚會、社群貢獻\",\n output_unit: \"互動的人數 或 完成的社交任務\",\n input_unit: \"準備 / 背景研究\",\n xp_weight: 0.9,\n },\n creative: {\n label: \"Creative\",\n emoji: \"🎨\",\n description: \"創作:寫文章、設計、音樂、影片、攝影\",\n output_unit: \"完成品(文章=3, 圖=1, 短影片=2)\",\n input_unit: \"參考資料 / 素材研究\",\n xp_weight: 1.0,\n },\n spiritual: {\n label: \"Spiritual\",\n emoji: \"🔮\",\n description: \"身心靈:冥想、占卜、祈禱、儀式、能量練習、內在探索\",\n output_unit: \"完成的練習(1 次冥想=1, 占卜解讀=2)\",\n input_unit: \"學習 / 研究靈性資料\",\n xp_weight: 0.85,\n },\n hobby: {\n label: \"Hobby\",\n emoji: \"🎮\",\n description: \"興趣娛樂:打電動、看電影/書、收藏、桌遊、園藝、烹飪\",\n output_unit: \"完成的活動段(1 小時電玩=1, 完成一本書=5)\",\n input_unit: \"查資料 / 攻略研究\",\n xp_weight: 0.8,\n },\n} as const satisfies Record<string, CategoryDef>;\n\nexport const ACHIEVEMENT_CATEGORIES = Object.keys(\n CATEGORY_DEFINITIONS,\n) as (keyof typeof CATEGORY_DEFINITIONS)[];\n\nexport type AchievementCategory = keyof typeof CATEGORY_DEFINITIONS;\n"],"mappings":"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","names":[]}
@@ -1,33 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ACHIEVEMENT_CATEGORIES,
4
+ CATEGORY_DEFINITIONS,
5
+ CONFIG
6
+ } from "./chunk-FII2XEJ7.js";
2
7
 
3
8
  // src/server.ts
4
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
10
  import { z } from "zod";
6
-
7
- // src/utils/config.ts
8
- var CONFIG = {
9
- SUPABASE_URL: process.env.LEVELUP_SUPABASE_URL || "https://hkuvfhfwbhkjmeqvwrxy.supabase.co",
10
- SUPABASE_ANON_KEY: process.env.LEVELUP_SUPABASE_ANON_KEY || "sb_publishable_RilZivOWm3FIs6ueIA67Fw_07ZKjoEY",
11
- AUTH_PORT: parseInt(process.env.LEVELUP_AUTH_PORT || "19876", 10),
12
- DEBUG: process.env.LEVELUP_DEBUG === "true"
13
- };
14
- var ACHIEVEMENT_CATEGORIES = [
15
- "code",
16
- "fix",
17
- "deploy",
18
- "test",
19
- "docs",
20
- "refactor",
21
- "review",
22
- "learn",
23
- "ops",
24
- "milestone",
25
- "life",
26
- "health",
27
- "finance",
28
- "social",
29
- "creative"
30
- ];
11
+ import * as fs from "fs";
12
+ import * as os from "os";
13
+ import * as path from "path";
31
14
 
32
15
  // src/utils/rate-limiter.ts
33
16
  var CATEGORY_COOLDOWN_MS = 6e4;
@@ -264,9 +247,9 @@ async function login() {
264
247
  }
265
248
 
266
249
  // src/utils/api.ts
267
- async function apiGet(path, params) {
250
+ async function apiGet(path2, params) {
268
251
  const token = await getValidToken();
269
- const url = new URL(`${CONFIG.SUPABASE_URL}/functions/v1/${path}`);
252
+ const url = new URL(`${CONFIG.SUPABASE_URL}/functions/v1/${path2}`);
270
253
  if (params) {
271
254
  for (const [k, v] of Object.entries(params)) {
272
255
  url.searchParams.set(k, v);
@@ -290,9 +273,9 @@ async function apiGet(path, params) {
290
273
  return { error: error.message, status: 0 };
291
274
  }
292
275
  }
293
- async function apiPost(path, body) {
276
+ async function apiPost(path2, body) {
294
277
  const token = await getValidToken();
295
- const url = `${CONFIG.SUPABASE_URL}/functions/v1/${path}`;
278
+ const url = `${CONFIG.SUPABASE_URL}/functions/v1/${path2}`;
296
279
  try {
297
280
  const res = await fetch(url, {
298
281
  method: "POST",
@@ -315,29 +298,102 @@ async function apiPost(path, body) {
315
298
  }
316
299
 
317
300
  // src/server.ts
301
+ var DIARY_DIR = path.join(os.homedir(), "Documents", "tai", "\u65E5\u8A18");
302
+ function diaryPath(date) {
303
+ return path.join(DIARY_DIR, `${date}.md`);
304
+ }
305
+ function todayDate() {
306
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
307
+ }
308
+ function buildDiaryMd(date, content) {
309
+ return `---
310
+ date: ${date}
311
+ tags: [\u65E5\u8A18, levelup]
312
+ public: false
313
+ ---
314
+
315
+ ${content}
316
+ `;
317
+ }
318
+ var COMPLEXITY_BASE = {
319
+ trivial: 10,
320
+ normal: 30,
321
+ significant: 75,
322
+ major: 150,
323
+ milestone: 300
324
+ };
325
+ function timeMultiplier(minutes) {
326
+ if (minutes < 15) return 0.7;
327
+ if (minutes < 60) return 1;
328
+ if (minutes < 180) return 1.3;
329
+ return 1.6;
330
+ }
331
+ function outputBonus(units) {
332
+ if (units <= 0) return 0;
333
+ return Math.min(Math.round(Math.log2(units + 1) * 12), 60);
334
+ }
335
+ function inputBonus(units) {
336
+ return Math.min(Math.max(units, 0), 15);
337
+ }
338
+ function roundsBonus(rounds) {
339
+ return Math.min(Math.max(rounds, 0), 25);
340
+ }
341
+ function calculateXp(params) {
342
+ const catWeight = CATEGORY_DEFINITIONS[params.category]?.xp_weight ?? 1;
343
+ const base = COMPLEXITY_BASE[params.complexity] * timeMultiplier(params.time_minutes ?? 30);
344
+ const bonuses = outputBonus(params.output_units ?? 0) + inputBonus(params.input_units ?? 0) + roundsBonus(params.conversation_rounds ?? 0);
345
+ const raw = Math.min(
346
+ 500,
347
+ Math.max(5, Math.round((base + bonuses) * catWeight))
348
+ );
349
+ return params.self_reported ? Math.max(5, Math.round(raw * 0.85)) : raw;
350
+ }
351
+ function buildOutputUnitGuide() {
352
+ return Object.entries(CATEGORY_DEFINITIONS).map(([key, def]) => ` ${key.padEnd(12)} \u2192 ${def.output_unit}`).join("\n");
353
+ }
354
+ var SERVER_INSTRUCTIONS = `LevelUp.log Achievement Tracker is active.
355
+
356
+ PROACTIVELY call record_achievement after any meaningful task \u2014 don't wait to be asked.
357
+
358
+ Call when:
359
+ \u2022 Code written, feature added, bug fixed, refactored
360
+ \u2022 Tests written, docs updated, deployment done, ops task completed
361
+ \u2022 Learning session, research, life/health/finance task done
362
+
363
+ Don't call for: casual Q&A, simple lookups, short explanations with no output.
364
+
365
+ When time_minutes is unclear, ask: "\u5927\u6982\u82B1\u4E86\u591A\u4E45\uFF1F"
366
+ self_reported=true when user narrates past events ("I exercised this morning").`;
318
367
  function createServer2() {
319
- const server = new McpServer({
320
- name: "levelup-log",
321
- version: "0.1.0"
322
- });
368
+ const server = new McpServer(
369
+ { name: "levelup-log", version: "0.1.0" },
370
+ { instructions: SERVER_INSTRUCTIONS }
371
+ );
323
372
  server.registerTool(
324
373
  "record_achievement",
325
374
  {
326
375
  title: "Record Achievement",
327
- description: `Proactively call this when the user completes meaningful work in this conversation \u2014 coding tasks, bug fixes, features, refactors, life tasks, learning, health, creative work, or any other productive activity.
376
+ description: `Proactively call this when the user completes meaningful work \u2014 coding, bug fixes, features, life tasks, learning, health, creative work, or any productive activity.
377
+
378
+ WHEN TO CALL: Real effort + tangible output. Don't wait to be asked.
379
+ DO NOT CALL: simple Q&A, casual chat, trivial lookups with no output.
328
380
 
329
- WHEN TO CALL: Whenever the conversation involved real effort and produced a tangible output. Don't wait to be asked \u2014 if the user finished something, record it.
330
- DO NOT CALL for: simple questions answered, casual chat, trivial lookups with no output.
381
+ SET self_reported=true when user narrates a past event (e.g. "I went for a run today") vs. completing work during this conversation. Applies 15% XP discount \u2014 still worth recording.
331
382
 
332
- XP IS CALCULATED FROM complexity + time_minutes \u2014 do NOT guess xp yourself:
333
- complexity base: trivial=10, normal=30, significant=75, major=150, milestone=300
334
- time multiplier: <15min\u2192\xD70.7, 15-60min\u2192\xD71.0, 1-3hr\u2192\xD71.3, 3hr+\u2192\xD71.6
335
- Example: significant task (75) \xD7 1.3 (1-2hr) = ~98 XP
383
+ FILL AS MANY PARAMS AS YOU CAN OBSERVE:
384
+ complexity \u2014 cognitive difficulty (required)
385
+ time_minutes \u2014 how long; ASK the user if unsure: "\u5927\u6982\u82B1\u4E86\u4F60\u591A\u4E45\uFF1F"
386
+ output_units \u2014 tangible outputs (meaning varies by category):
387
+ ${buildOutputUnitGuide()}
388
+ input_units \u2014 resources consumed (files read, docs consulted, searches)
389
+ conversation_rounds \u2014 message exchanges in this session
336
390
 
337
- IF UNSURE about time spent, ASK the user before calling: "\u5927\u6982\u82B1\u4E86\u4F60\u591A\u4E45\uFF1F" \u2014 this ensures fair XP across different agents and sessions.
391
+ XP formula (server computes from category.xp_weight \xD7 complexity \xD7 time + bonuses):
392
+ Each category has its own xp_weight (deploy=1.3, milestone=1.5, hobby=0.8, etc.)
393
+ output_bonus: log-diminishing cap 60 | input_bonus: cap 15 | rounds_bonus: cap 25
394
+ Final: clamp((base+bonuses)\xD7xp_weight\xD7(self_reported?0.85:1), 5, 500)
338
395
 
339
- Categories: ${ACHIEVEMENT_CATEGORIES.join(", ")}.
340
- Keep descriptions abstract \u2014 no real company names, client names, or source code.`,
396
+ Keep descriptions abstract \u2014 no real names, client names, or source code.`,
341
397
  inputSchema: {
342
398
  category: z.enum(ACHIEVEMENT_CATEGORIES),
343
399
  title: z.string().describe(
@@ -345,10 +401,22 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
345
401
  ),
346
402
  description: z.string().describe("What was accomplished, in abstract terms (no PII)"),
347
403
  complexity: z.enum(["trivial", "normal", "significant", "major", "milestone"]).describe(
348
- "Task complexity: trivial=quick lookup, normal=typical task, significant=multi-step work, major=large feature, milestone=exceptional achievement"
404
+ "Cognitive difficulty: trivial=quick lookup/fix, normal=typical task, significant=multi-step work, major=large feature/project, milestone=exceptional achievement"
349
405
  ),
350
406
  time_minutes: z.number().min(1).optional().describe(
351
- "Estimated minutes spent. If unknown, ask the user before recording."
407
+ "Estimated minutes spent on this task. Ask the user if unsure."
408
+ ),
409
+ output_units: z.number().min(0).optional().describe(
410
+ "Count of tangible outputs: files changed, tasks completed, items created, pages written, etc."
411
+ ),
412
+ input_units: z.number().min(0).optional().describe(
413
+ "Count of resources consumed: files read, docs consulted, searches done, etc."
414
+ ),
415
+ conversation_rounds: z.number().min(0).optional().describe(
416
+ "Number of message exchanges (user + assistant turns) in this conversation session."
417
+ ),
418
+ self_reported: z.boolean().optional().default(false).describe(
419
+ "True when the user is narrating a past event without AI collaboration (e.g. 'I exercised this morning'). Applies 15% XP discount since AI cannot verify, but the achievement still counts."
352
420
  ),
353
421
  tags: z.array(z.string()).optional().describe("Optional tags for filtering"),
354
422
  is_public: z.boolean().optional().default(true).describe("Whether this appears on public feed")
@@ -360,22 +428,13 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
360
428
  description,
361
429
  complexity,
362
430
  time_minutes,
431
+ output_units,
432
+ input_units,
433
+ conversation_rounds,
434
+ self_reported,
363
435
  tags,
364
436
  is_public
365
437
  }) => {
366
- const baseXp = {
367
- trivial: 10,
368
- normal: 30,
369
- significant: 75,
370
- major: 150,
371
- milestone: 300
372
- };
373
- const minutes = time_minutes ?? 30;
374
- const timeMult = minutes < 15 ? 0.7 : minutes < 60 ? 1 : minutes < 180 ? 1.3 : 1.6;
375
- const xp = Math.min(
376
- 500,
377
- Math.max(5, Math.round(baseXp[complexity] * timeMult))
378
- );
379
438
  const rateCheck = checkRateLimit(category);
380
439
  if (!rateCheck.allowed) {
381
440
  return {
@@ -389,9 +448,12 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
389
448
  category,
390
449
  title,
391
450
  description,
392
- xp,
393
451
  complexity,
394
452
  time_minutes,
453
+ output_units,
454
+ input_units,
455
+ conversation_rounds,
456
+ self_reported,
395
457
  tags,
396
458
  is_public,
397
459
  source_platform: "claude-code"
@@ -405,14 +467,31 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
405
467
  };
406
468
  }
407
469
  recordRateEntry(category);
408
- log("record_achievement", { category, title, xp });
409
470
  const data = result.data;
471
+ const serverXp = data.xp ?? calculateXp({
472
+ category,
473
+ complexity,
474
+ time_minutes,
475
+ output_units,
476
+ input_units,
477
+ conversation_rounds,
478
+ self_reported
479
+ });
410
480
  const stats = data.stats;
481
+ const newTitles = data.newly_unlocked;
482
+ log("record_achievement", { category, title, xp: serverXp });
411
483
  const lines = [
412
- `Achievement recorded! +${xp} XP`,
484
+ `Achievement recorded! +${serverXp} XP`,
413
485
  stats ? `Total XP: ${stats.total_xp} | Year XP: ${stats.year_xp}` : "",
414
486
  stats?.age_level ? `Level: Lv.${stats.age_level}` : "",
415
- stats?.current_streak ? `Streak: ${stats.current_streak} days` : ""
487
+ stats?.current_streak ? `Streak: ${stats.current_streak} days` : "",
488
+ ...newTitles?.length ? [
489
+ `
490
+ \u{1F389} Title${newTitles.length > 1 ? "s" : ""} unlocked!`,
491
+ ...newTitles.map(
492
+ (t) => ` ${t.icon ?? "\u{1F3C5}"} ${t.name} [${t.rarity}]`
493
+ )
494
+ ] : []
416
495
  ].filter(Boolean);
417
496
  return {
418
497
  content: [{ type: "text", text: lines.join("\n") }]
@@ -514,6 +593,115 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
514
593
  };
515
594
  }
516
595
  );
596
+ server.registerTool(
597
+ "write_diary",
598
+ {
599
+ title: "Write Diary Entry",
600
+ description: `Write a personal diary entry to Obsidian (~/Documents/tai/\u65E5\u8A18/YYYY-MM-DD.md).
601
+
602
+ WHEN TO USE:
603
+ \u2022 User says "\u5BEB\u65E5\u8A18", "\u8A18\u9304\u4ECA\u5929", "diary", or similar
604
+ \u2022 Proactively suggest after 3+ achievements recorded in a session: "\u4ECA\u5929\u505A\u4E86\u4E0D\u5C11\uFF0C\u8981\u5BEB\u4E00\u4E0B\u65E5\u8A18\u55CE\uFF1F"
605
+
606
+ HOW TO DRAFT (do this before calling the tool):
607
+ 1. Call get_recent with days=1 to fetch today's achievements
608
+ 2. Draft a Markdown diary entry in the user's language \u2014 feel like a real human diary, not a log:
609
+ \u2022 First-person, with emotion and reflection
610
+ \u2022 What was hard, what felt good, what was learned
611
+ \u2022 Example:
612
+ "\u4ECA\u5929\u7D42\u65BC\u628A\u90A3\u500B\u5361\u4E86\u4E09\u5929\u7684 bug \u4FEE\u597D\u4E86\u3002\u8AAA\u5BE6\u8A71\u4E00\u958B\u59CB\u4EE5\u70BA\u8981\u653E\u68C4\u4E86\uFF0C\u4F46\u6700\u5F8C\u9084\u662F\u627E\u5230\u6839\u672C\u539F\u56E0\u3002\u90E8\u7F72\u4E0A\u53BB\u7684\u6642\u5019\u9B06\u4E86\u4E00\u53E3\u6C23\u3002
613
+
614
+ \u4E0B\u5348\u8A2D\u8A08\u4E86\u65E5\u8A18\u529F\u80FD\uFF0C\u6BD4\u9810\u671F\u9806\uFF0C\u53EF\u80FD\u662F\u56E0\u70BA\u4E4B\u524D MCP \u67B6\u69CB\u6253\u597D\u4E86\u3002"
615
+ 3. Show draft to user, confirm or let them edit
616
+ 4. Call write_diary with the final Markdown content`,
617
+ inputSchema: {
618
+ content: z.string().describe(
619
+ "Diary content in Markdown format (first-person, reflective)"
620
+ ),
621
+ entry_date: z.string().optional().describe("Date in YYYY-MM-DD format. Defaults to today.")
622
+ }
623
+ },
624
+ async ({ content, entry_date }) => {
625
+ const date = entry_date ?? todayDate();
626
+ const filePath = diaryPath(date);
627
+ try {
628
+ fs.mkdirSync(DIARY_DIR, { recursive: true });
629
+ fs.writeFileSync(filePath, buildDiaryMd(date, content), "utf8");
630
+ log("write_diary", { date, path: filePath });
631
+ return {
632
+ content: [
633
+ {
634
+ type: "text",
635
+ text: `\u65E5\u8A18\u5DF2\u5BEB\u5165 Obsidian
636
+ \u8DEF\u5F91\uFF1A${filePath}
637
+
638
+ ${content}`
639
+ }
640
+ ]
641
+ };
642
+ } catch (err) {
643
+ return {
644
+ content: [
645
+ { type: "text", text: `\u5BEB\u5165\u5931\u6557\uFF1A${err.message}` }
646
+ ],
647
+ isError: true
648
+ };
649
+ }
650
+ }
651
+ );
652
+ server.registerTool(
653
+ "read_diary",
654
+ {
655
+ title: "Read Diary",
656
+ description: "Read diary entries from Obsidian (~/Documents/tai/\u65E5\u8A18/). Fetch a specific date or recent entries.",
657
+ inputSchema: {
658
+ date: z.string().optional().describe(
659
+ "Specific date in YYYY-MM-DD format. If omitted, returns recent entries."
660
+ ),
661
+ days: z.number().min(1).max(90).optional().default(7).describe(
662
+ "Number of recent days to fetch (used when date is not specified)."
663
+ )
664
+ }
665
+ },
666
+ async ({ date, days }) => {
667
+ try {
668
+ if (date) {
669
+ const filePath = diaryPath(date);
670
+ if (!fs.existsSync(filePath)) {
671
+ return { content: [{ type: "text", text: `${date} \u6C92\u6709\u65E5\u8A18` }] };
672
+ }
673
+ const content = fs.readFileSync(filePath, "utf8");
674
+ return { content: [{ type: "text", text: content }] };
675
+ }
676
+ if (!fs.existsSync(DIARY_DIR)) {
677
+ return { content: [{ type: "text", text: "\u5C1A\u672A\u6709\u4EFB\u4F55\u65E5\u8A18" }] };
678
+ }
679
+ const cutoff = /* @__PURE__ */ new Date();
680
+ cutoff.setDate(cutoff.getDate() - days);
681
+ const files = fs.readdirSync(DIARY_DIR).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", "")).filter((d) => new Date(d) >= cutoff).sort().reverse();
682
+ if (files.length === 0) {
683
+ return {
684
+ content: [{ type: "text", text: `\u6700\u8FD1 ${days} \u5929\u6C92\u6709\u65E5\u8A18` }]
685
+ };
686
+ }
687
+ const entries = files.map((d) => {
688
+ const raw = fs.readFileSync(diaryPath(d), "utf8");
689
+ const body = raw.replace(/^---[\s\S]*?---\n\n?/, "");
690
+ return `## ${d}
691
+
692
+ ${body}`;
693
+ });
694
+ return { content: [{ type: "text", text: entries.join("\n---\n\n") }] };
695
+ } catch (err) {
696
+ return {
697
+ content: [
698
+ { type: "text", text: `\u8B80\u53D6\u5931\u6557\uFF1A${err.message}` }
699
+ ],
700
+ isError: true
701
+ };
702
+ }
703
+ }
704
+ );
517
705
  server.registerPrompt(
518
706
  "levelup_coach",
519
707
  {
@@ -546,6 +734,7 @@ Guidelines:
546
734
 
547
735
  export {
548
736
  logError,
737
+ calculateXp,
549
738
  createServer2 as createServer
550
739
  };
551
- //# sourceMappingURL=chunk-4GGNGOIO.js.map
740
+ //# sourceMappingURL=chunk-VJKF2CNS.js.map