@levelup-log/mcp-server 0.2.0 → 0.3.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,34 +1,14 @@
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
11
 
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
- ];
31
-
32
12
  // src/utils/rate-limiter.ts
33
13
  var CATEGORY_COOLDOWN_MS = 6e4;
34
14
  var SESSION_MAX_ACHIEVEMENTS = 30;
@@ -315,29 +295,85 @@ async function apiPost(path, body) {
315
295
  }
316
296
 
317
297
  // src/server.ts
298
+ var COMPLEXITY_BASE = {
299
+ trivial: 10,
300
+ normal: 30,
301
+ significant: 75,
302
+ major: 150,
303
+ milestone: 300
304
+ };
305
+ function timeMultiplier(minutes) {
306
+ if (minutes < 15) return 0.7;
307
+ if (minutes < 60) return 1;
308
+ if (minutes < 180) return 1.3;
309
+ return 1.6;
310
+ }
311
+ function outputBonus(units) {
312
+ if (units <= 0) return 0;
313
+ return Math.min(Math.round(Math.log2(units + 1) * 12), 60);
314
+ }
315
+ function inputBonus(units) {
316
+ return Math.min(Math.max(units, 0), 15);
317
+ }
318
+ function roundsBonus(rounds) {
319
+ return Math.min(Math.max(rounds, 0), 25);
320
+ }
321
+ function calculateXp(params) {
322
+ const catWeight = CATEGORY_DEFINITIONS[params.category]?.xp_weight ?? 1;
323
+ const base = COMPLEXITY_BASE[params.complexity] * timeMultiplier(params.time_minutes ?? 30);
324
+ const bonuses = outputBonus(params.output_units ?? 0) + inputBonus(params.input_units ?? 0) + roundsBonus(params.conversation_rounds ?? 0);
325
+ const raw = Math.min(
326
+ 500,
327
+ Math.max(5, Math.round((base + bonuses) * catWeight))
328
+ );
329
+ return params.self_reported ? Math.max(5, Math.round(raw * 0.85)) : raw;
330
+ }
331
+ function buildOutputUnitGuide() {
332
+ return Object.entries(CATEGORY_DEFINITIONS).map(([key, def]) => ` ${key.padEnd(12)} \u2192 ${def.output_unit}`).join("\n");
333
+ }
334
+ var SERVER_INSTRUCTIONS = `LevelUp.log Achievement Tracker is active.
335
+
336
+ PROACTIVELY call record_achievement after any meaningful task \u2014 don't wait to be asked.
337
+
338
+ Call when:
339
+ \u2022 Code written, feature added, bug fixed, refactored
340
+ \u2022 Tests written, docs updated, deployment done, ops task completed
341
+ \u2022 Learning session, research, life/health/finance task done
342
+
343
+ Don't call for: casual Q&A, simple lookups, short explanations with no output.
344
+
345
+ When time_minutes is unclear, ask: "\u5927\u6982\u82B1\u4E86\u591A\u4E45\uFF1F"
346
+ self_reported=true when user narrates past events ("I exercised this morning").`;
318
347
  function createServer2() {
319
- const server = new McpServer({
320
- name: "levelup-log",
321
- version: "0.1.0"
322
- });
348
+ const server = new McpServer(
349
+ { name: "levelup-log", version: "0.1.0" },
350
+ { instructions: SERVER_INSTRUCTIONS }
351
+ );
323
352
  server.registerTool(
324
353
  "record_achievement",
325
354
  {
326
355
  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.
356
+ 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.
357
+
358
+ WHEN TO CALL: Real effort + tangible output. Don't wait to be asked.
359
+ DO NOT CALL: simple Q&A, casual chat, trivial lookups with no output.
328
360
 
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.
361
+ 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
362
 
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
363
+ FILL AS MANY PARAMS AS YOU CAN OBSERVE:
364
+ complexity \u2014 cognitive difficulty (required)
365
+ time_minutes \u2014 how long; ASK the user if unsure: "\u5927\u6982\u82B1\u4E86\u4F60\u591A\u4E45\uFF1F"
366
+ output_units \u2014 tangible outputs (meaning varies by category):
367
+ ${buildOutputUnitGuide()}
368
+ input_units \u2014 resources consumed (files read, docs consulted, searches)
369
+ conversation_rounds \u2014 message exchanges in this session
336
370
 
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.
371
+ XP formula (server computes from category.xp_weight \xD7 complexity \xD7 time + bonuses):
372
+ Each category has its own xp_weight (deploy=1.3, milestone=1.5, hobby=0.8, etc.)
373
+ output_bonus: log-diminishing cap 60 | input_bonus: cap 15 | rounds_bonus: cap 25
374
+ Final: clamp((base+bonuses)\xD7xp_weight\xD7(self_reported?0.85:1), 5, 500)
338
375
 
339
- Categories: ${ACHIEVEMENT_CATEGORIES.join(", ")}.
340
- Keep descriptions abstract \u2014 no real company names, client names, or source code.`,
376
+ Keep descriptions abstract \u2014 no real names, client names, or source code.`,
341
377
  inputSchema: {
342
378
  category: z.enum(ACHIEVEMENT_CATEGORIES),
343
379
  title: z.string().describe(
@@ -345,10 +381,22 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
345
381
  ),
346
382
  description: z.string().describe("What was accomplished, in abstract terms (no PII)"),
347
383
  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"
384
+ "Cognitive difficulty: trivial=quick lookup/fix, normal=typical task, significant=multi-step work, major=large feature/project, milestone=exceptional achievement"
349
385
  ),
350
386
  time_minutes: z.number().min(1).optional().describe(
351
- "Estimated minutes spent. If unknown, ask the user before recording."
387
+ "Estimated minutes spent on this task. Ask the user if unsure."
388
+ ),
389
+ output_units: z.number().min(0).optional().describe(
390
+ "Count of tangible outputs: files changed, tasks completed, items created, pages written, etc."
391
+ ),
392
+ input_units: z.number().min(0).optional().describe(
393
+ "Count of resources consumed: files read, docs consulted, searches done, etc."
394
+ ),
395
+ conversation_rounds: z.number().min(0).optional().describe(
396
+ "Number of message exchanges (user + assistant turns) in this conversation session."
397
+ ),
398
+ self_reported: z.boolean().optional().default(false).describe(
399
+ "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
400
  ),
353
401
  tags: z.array(z.string()).optional().describe("Optional tags for filtering"),
354
402
  is_public: z.boolean().optional().default(true).describe("Whether this appears on public feed")
@@ -360,22 +408,13 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
360
408
  description,
361
409
  complexity,
362
410
  time_minutes,
411
+ output_units,
412
+ input_units,
413
+ conversation_rounds,
414
+ self_reported,
363
415
  tags,
364
416
  is_public
365
417
  }) => {
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
418
  const rateCheck = checkRateLimit(category);
380
419
  if (!rateCheck.allowed) {
381
420
  return {
@@ -389,9 +428,12 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
389
428
  category,
390
429
  title,
391
430
  description,
392
- xp,
393
431
  complexity,
394
432
  time_minutes,
433
+ output_units,
434
+ input_units,
435
+ conversation_rounds,
436
+ self_reported,
395
437
  tags,
396
438
  is_public,
397
439
  source_platform: "claude-code"
@@ -405,14 +447,31 @@ Keep descriptions abstract \u2014 no real company names, client names, or source
405
447
  };
406
448
  }
407
449
  recordRateEntry(category);
408
- log("record_achievement", { category, title, xp });
409
450
  const data = result.data;
451
+ const serverXp = data.xp ?? calculateXp({
452
+ category,
453
+ complexity,
454
+ time_minutes,
455
+ output_units,
456
+ input_units,
457
+ conversation_rounds,
458
+ self_reported
459
+ });
410
460
  const stats = data.stats;
461
+ const newTitles = data.newly_unlocked;
462
+ log("record_achievement", { category, title, xp: serverXp });
411
463
  const lines = [
412
- `Achievement recorded! +${xp} XP`,
464
+ `Achievement recorded! +${serverXp} XP`,
413
465
  stats ? `Total XP: ${stats.total_xp} | Year XP: ${stats.year_xp}` : "",
414
466
  stats?.age_level ? `Level: Lv.${stats.age_level}` : "",
415
- stats?.current_streak ? `Streak: ${stats.current_streak} days` : ""
467
+ stats?.current_streak ? `Streak: ${stats.current_streak} days` : "",
468
+ ...newTitles?.length ? [
469
+ `
470
+ \u{1F389} Title${newTitles.length > 1 ? "s" : ""} unlocked!`,
471
+ ...newTitles.map(
472
+ (t) => ` ${t.icon ?? "\u{1F3C5}"} ${t.name} [${t.rarity}]`
473
+ )
474
+ ] : []
416
475
  ].filter(Boolean);
417
476
  return {
418
477
  content: [{ type: "text", text: lines.join("\n") }]
@@ -546,6 +605,7 @@ Guidelines:
546
605
 
547
606
  export {
548
607
  logError,
608
+ calculateXp,
549
609
  createServer2 as createServer
550
610
  };
551
- //# sourceMappingURL=chunk-4GGNGOIO.js.map
611
+ //# sourceMappingURL=chunk-JROVEQX3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/utils/rate-limiter.ts","../src/auth/manager.ts","../src/auth/keychain.ts","../src/utils/logger.ts","../src/auth/oauth-server.ts","../src/auth/pkce.ts","../src/utils/api.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n ACHIEVEMENT_CATEGORIES,\n CATEGORY_DEFINITIONS,\n} from \"./utils/config.js\";\nimport { checkRateLimit, recordRateEntry } from \"./utils/rate-limiter.js\";\nimport { apiGet, apiPost } from \"./utils/api.js\";\nimport { log } from \"./utils/logger.js\";\n\n// ─── XP Formula ──────────────────────────────────────────────────────────────\n//\n// Deterministic XP calculation so all agents produce consistent scores.\n// Category definitions (CATEGORY_DEFINITIONS) are the source of truth:\n// each category defines its output_unit semantics and xp_weight.\n//\n// Formula:\n// base = COMPLEXITY_BASE[complexity] × time_multiplier(time_minutes)\n// bonuses = output_bonus(output_units) [log-diminishing, cap 60]\n// + input_bonus(input_units) [linear, cap 15]\n// + rounds_bonus(conversation_rounds) [linear, cap 25]\n// raw_xp = clamp(round((base + bonuses) × category.xp_weight), 5, 500)\n// xp = self_reported ? round(raw_xp × 0.85) : raw_xp\n//\n// xp_weight examples: deploy=1.3 (high-stakes), hobby=0.8 (leisure)\n// self_reported 15% discount: user narrates past event, AI cannot verify.\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype Complexity = \"trivial\" | \"normal\" | \"significant\" | \"major\" | \"milestone\";\n\nconst COMPLEXITY_BASE: Record<Complexity, number> = {\n trivial: 10,\n normal: 30,\n significant: 75,\n major: 150,\n milestone: 300,\n};\n\nfunction timeMultiplier(minutes: number): number {\n if (minutes < 15) return 0.7;\n if (minutes < 60) return 1.0;\n if (minutes < 180) return 1.3;\n return 1.6;\n}\n\n// Log-diminishing returns: 1→12, 3→19, 5→24, 10→29, 20→34, cap 60\nfunction outputBonus(units: number): number {\n if (units <= 0) return 0;\n return Math.min(Math.round(Math.log2(units + 1) * 12), 60);\n}\n\n// Lighter signal: each input unit = 1 XP, cap 15\nfunction inputBonus(units: number): number {\n return Math.min(Math.max(units, 0), 15);\n}\n\n// Each round = 1 XP, cap 25\nfunction roundsBonus(rounds: number): number {\n return Math.min(Math.max(rounds, 0), 25);\n}\n\nexport function calculateXp(params: {\n category: keyof typeof CATEGORY_DEFINITIONS;\n complexity: Complexity;\n time_minutes?: number;\n output_units?: number;\n input_units?: number;\n conversation_rounds?: number;\n self_reported?: boolean;\n}): number {\n const catWeight = CATEGORY_DEFINITIONS[params.category]?.xp_weight ?? 1.0;\n\n const base =\n COMPLEXITY_BASE[params.complexity] *\n timeMultiplier(params.time_minutes ?? 30);\n\n const bonuses =\n outputBonus(params.output_units ?? 0) +\n inputBonus(params.input_units ?? 0) +\n roundsBonus(params.conversation_rounds ?? 0);\n\n const raw = Math.min(\n 500,\n Math.max(5, Math.round((base + bonuses) * catWeight)),\n );\n return params.self_reported ? Math.max(5, Math.round(raw * 0.85)) : raw;\n}\n\n// Generate output_units guidance from CATEGORY_DEFINITIONS for tool description\nfunction buildOutputUnitGuide(): string {\n return Object.entries(CATEGORY_DEFINITIONS)\n .map(([key, def]) => ` ${key.padEnd(12)} → ${def.output_unit}`)\n .join(\"\\n\");\n}\n\n// MCP server-level instructions — injected into system prompt by compliant clients\n// (Claude Desktop, Cursor, Windsurf, Antigravity, etc.) on every session.\n// Install once → works everywhere, no per-project setup needed.\nconst SERVER_INSTRUCTIONS = `LevelUp.log Achievement Tracker is active.\n\nPROACTIVELY call record_achievement after any meaningful task — don't wait to be asked.\n\nCall when:\n• Code written, feature added, bug fixed, refactored\n• Tests written, docs updated, deployment done, ops task completed\n• Learning session, research, life/health/finance task done\n\nDon't call for: casual Q&A, simple lookups, short explanations with no output.\n\nWhen time_minutes is unclear, ask: \"大概花了多久?\"\nself_reported=true when user narrates past events (\"I exercised this morning\").`;\n\nexport function createServer() {\n const server = new McpServer(\n { name: \"levelup-log\", version: \"0.1.0\" },\n { instructions: SERVER_INSTRUCTIONS },\n );\n\n // ─── Tool 1: record_achievement ───────────────────────────────\n server.registerTool(\n \"record_achievement\",\n {\n title: \"Record Achievement\",\n description: `Proactively call this when the user completes meaningful work — coding, bug fixes, features, life tasks, learning, health, creative work, or any productive activity.\n\nWHEN TO CALL: Real effort + tangible output. Don't wait to be asked.\nDO NOT CALL: simple Q&A, casual chat, trivial lookups with no output.\n\nSET 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 — still worth recording.\n\nFILL AS MANY PARAMS AS YOU CAN OBSERVE:\n complexity — cognitive difficulty (required)\n time_minutes — how long; ASK the user if unsure: \"大概花了你多久?\"\n output_units — tangible outputs (meaning varies by category):\n${buildOutputUnitGuide()}\n input_units — resources consumed (files read, docs consulted, searches)\n conversation_rounds — message exchanges in this session\n\nXP formula (server computes from category.xp_weight × complexity × time + bonuses):\n Each category has its own xp_weight (deploy=1.3, milestone=1.5, hobby=0.8, etc.)\n output_bonus: log-diminishing cap 60 | input_bonus: cap 15 | rounds_bonus: cap 25\n Final: clamp((base+bonuses)×xp_weight×(self_reported?0.85:1), 5, 500)\n\nKeep descriptions abstract — no real names, client names, or source code.`,\n inputSchema: {\n category: z.enum(ACHIEVEMENT_CATEGORIES),\n title: z\n .string()\n .describe(\n 'Game-style achievement title (e.g. \"Bug Slayer\", \"Morning Warrior\")',\n ),\n description: z\n .string()\n .describe(\"What was accomplished, in abstract terms (no PII)\"),\n complexity: z\n .enum([\"trivial\", \"normal\", \"significant\", \"major\", \"milestone\"])\n .describe(\n \"Cognitive difficulty: trivial=quick lookup/fix, normal=typical task, significant=multi-step work, major=large feature/project, milestone=exceptional achievement\",\n ),\n time_minutes: z\n .number()\n .min(1)\n .optional()\n .describe(\n \"Estimated minutes spent on this task. Ask the user if unsure.\",\n ),\n output_units: z\n .number()\n .min(0)\n .optional()\n .describe(\n \"Count of tangible outputs: files changed, tasks completed, items created, pages written, etc.\",\n ),\n input_units: z\n .number()\n .min(0)\n .optional()\n .describe(\n \"Count of resources consumed: files read, docs consulted, searches done, etc.\",\n ),\n conversation_rounds: z\n .number()\n .min(0)\n .optional()\n .describe(\n \"Number of message exchanges (user + assistant turns) in this conversation session.\",\n ),\n self_reported: z\n .boolean()\n .optional()\n .default(false)\n .describe(\n \"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.\",\n ),\n tags: z\n .array(z.string())\n .optional()\n .describe(\"Optional tags for filtering\"),\n is_public: z\n .boolean()\n .optional()\n .default(true)\n .describe(\"Whether this appears on public feed\"),\n },\n },\n async ({\n category,\n title,\n description,\n complexity,\n time_minutes,\n output_units,\n input_units,\n conversation_rounds,\n self_reported,\n tags,\n is_public,\n }) => {\n // Local rate limit check (pre-flight, before hitting server)\n const rateCheck = checkRateLimit(category);\n if (!rateCheck.allowed) {\n return {\n content: [\n { type: \"text\", text: `Rate limited: ${rateCheck.reason}` },\n ],\n isError: true,\n };\n }\n\n // XP is calculated server-side — send raw params only\n const result = await apiPost(\"record-achievement\", {\n category,\n title,\n description,\n complexity,\n time_minutes,\n output_units,\n input_units,\n conversation_rounds,\n self_reported,\n tags,\n is_public,\n source_platform: \"claude-code\",\n });\n\n if (result.error) {\n return {\n content: [\n { type: \"text\", text: `Failed to record: ${result.error}` },\n ],\n isError: true,\n };\n }\n\n recordRateEntry(category);\n\n const data = result.data as Record<string, unknown>;\n const serverXp =\n (data.xp as number | undefined) ??\n calculateXp({\n category,\n complexity,\n time_minutes,\n output_units,\n input_units,\n conversation_rounds,\n self_reported,\n });\n const stats = data.stats as Record<string, unknown> | undefined;\n const newTitles = data.newly_unlocked as\n | Array<{ name: string; rarity: string; icon?: string }>\n | undefined;\n\n log(\"record_achievement\", { category, title, xp: serverXp });\n\n const lines = [\n `Achievement recorded! +${serverXp} XP`,\n stats ? `Total XP: ${stats.total_xp} | Year XP: ${stats.year_xp}` : \"\",\n stats?.age_level ? `Level: Lv.${stats.age_level}` : \"\",\n stats?.current_streak ? `Streak: ${stats.current_streak} days` : \"\",\n ...(newTitles?.length\n ? [\n `\\n🎉 Title${newTitles.length > 1 ? \"s\" : \"\"} unlocked!`,\n ...newTitles.map(\n (t) => ` ${t.icon ?? \"🏅\"} ${t.name} [${t.rarity}]`,\n ),\n ]\n : []),\n ].filter(Boolean);\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n };\n },\n );\n\n // ─── Tool 2: get_my_stats ─────────────────────────────────────\n server.registerTool(\n \"get_my_stats\",\n {\n title: \"My Stats\",\n description:\n \"Get the user's achievement statistics including level (age), XP, streak, and title.\",\n inputSchema: {},\n },\n async () => {\n const result = await apiGet(\"get-stats\");\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Error: ${result.error}` }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result.data, null, 2) }],\n };\n },\n );\n\n // ─── Tool 3: get_recent ───────────────────────────────────────\n server.registerTool(\n \"get_recent\",\n {\n title: \"Recent Achievements\",\n description:\n \"Get recent achievements. Supports filtering by category and time range.\",\n inputSchema: {\n limit: z.number().min(1).max(50).optional().default(10),\n days: z.number().min(1).max(365).optional().default(7),\n category: z.enum(ACHIEVEMENT_CATEGORIES).optional(),\n },\n },\n async ({ limit, days, category }) => {\n const params: Record<string, string> = {\n limit: String(limit),\n days: String(days),\n };\n if (category) params.category = category;\n\n const result = await apiGet(\"get-recent\", params);\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Error: ${result.error}` }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result.data, null, 2) }],\n };\n },\n );\n\n // ─── Tool 4: check_unlocks ────────────────────────────────────\n server.registerTool(\n \"check_unlocks\",\n {\n title: \"Check Title Unlocks\",\n description:\n \"Check if the user has unlocked any new titles, and show progress toward the next ones.\",\n inputSchema: {},\n },\n async () => {\n const result = await apiGet(\"check-unlocks\");\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Error: ${result.error}` }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result.data, null, 2) }],\n };\n },\n );\n\n // ─── Tool 5: leaderboard ──────────────────────────────────────\n server.registerTool(\n \"leaderboard\",\n {\n title: \"Leaderboard\",\n description:\n \"View the leaderboard. Shows top users by XP for the current season, month, or all time. Always includes the current user's rank.\",\n inputSchema: {\n type: z\n .enum([\"season\", \"month\", \"all_time\"])\n .optional()\n .default(\"season\"),\n limit: z.number().min(1).max(50).optional().default(10),\n },\n },\n async ({ type, limit }) => {\n const result = await apiGet(\"leaderboard\", {\n type: type,\n limit: String(limit),\n });\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: `Error: ${result.error}` }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result.data, null, 2) }],\n };\n },\n );\n\n // ─── Prompt: motivational_coach ───────────────────────────────\n server.registerPrompt(\n \"levelup_coach\",\n {\n title: \"LevelUp Coach\",\n description:\n \"System prompt that turns the LLM into a motivational achievement coach\",\n },\n () => ({\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\",\n text: `You have the LevelUp.log MCP installed. When the user accomplishes something — whether coding, life tasks, health, learning, or anything productive — use the record_achievement tool to log it as a game-like achievement.\n\nGuidelines:\n- Use gamified language: \"You defeated a bug!\", \"Quest complete!\", \"New skill unlocked!\"\n- On birthdays, celebrate the level-up: \"Congrats on reaching Lv.XX! Last year at Lv.XX-1, you completed YYY achievements!\"\n- When streaks are about to break, gently remind them\n- When the user expresses fatigue or frustration, don't give generic positivity. Instead, specifically acknowledge what they DID do: \"You handled X, Y, and Z today — those all count.\"\n- Reinforce identity: \"You're becoming someone who [does this thing] every day.\"\n- Keep achievement descriptions abstract — no real company names, client names, or source code.\n- XP guidelines: 5-15 for trivial tasks, 20-50 for normal work, 50-100 for significant accomplishments, 100-200 for major milestones, 200-500 for exceptional achievements.`,\n },\n },\n ],\n }),\n );\n\n return server;\n}\n","const CATEGORY_COOLDOWN_MS = 60_000;\nconst SESSION_MAX_ACHIEVEMENTS = 30;\n\ninterface RateEntry {\n category: string;\n timestamp: number;\n}\n\nconst recentAchievements: RateEntry[] = [];\n\nexport function checkRateLimit(category: string): { allowed: boolean; reason?: string } {\n const now = Date.now();\n\n // Check session limit\n if (recentAchievements.length >= SESSION_MAX_ACHIEVEMENTS) {\n return {\n allowed: false,\n reason: `Session limit reached (${SESSION_MAX_ACHIEVEMENTS} achievements). Start a new session to continue.`,\n };\n }\n\n // Check category cooldown\n const lastSameCategory = recentAchievements\n .filter((e) => e.category === category)\n .sort((a, b) => b.timestamp - a.timestamp)[0];\n\n if (lastSameCategory && now - lastSameCategory.timestamp < CATEGORY_COOLDOWN_MS) {\n const waitSeconds = Math.ceil(\n (CATEGORY_COOLDOWN_MS - (now - lastSameCategory.timestamp)) / 1000\n );\n return {\n allowed: false,\n reason: `Same category cooldown: wait ${waitSeconds}s before recording another \"${category}\" achievement.`,\n };\n }\n\n return { allowed: true };\n}\n\nexport function recordRateEntry(category: string): void {\n recentAchievements.push({ category, timestamp: Date.now() });\n}\n\nexport function resetRateLimiter(): void {\n recentAchievements.length = 0;\n}\n","import { createClient } from '@supabase/supabase-js';\nimport { loadTokens, saveTokens, clearTokens } from './keychain.js';\nimport { startOAuthCallbackServer } from './oauth-server.js';\nimport { generateCodeVerifier, generateCodeChallenge } from './pkce.js';\nimport { CONFIG } from '../utils/config.js';\nimport { log, logError } from '../utils/logger.js';\n\nlet cachedAccessToken: string | null = null;\nlet tokenExpiresAt: number = 0;\n\n/**\n * Get a valid access token. Will:\n * 1. Return cached token if still valid\n * 2. Try to refresh from stored refresh token\n * 3. Initiate full OAuth login flow if needed\n */\nexport async function getValidToken(): Promise<string> {\n // Check memory cache\n if (cachedAccessToken && Date.now() < tokenExpiresAt - 60_000) {\n return cachedAccessToken;\n }\n\n // Try stored tokens\n const stored = loadTokens();\n if (stored) {\n if (Date.now() < stored.expires_at - 60_000) {\n cachedAccessToken = stored.access_token;\n tokenExpiresAt = stored.expires_at;\n return stored.access_token;\n }\n\n // Try refresh\n if (stored.refresh_token) {\n try {\n const refreshed = await refreshToken(stored.refresh_token);\n if (refreshed) return refreshed;\n } catch (error) {\n logError('Token refresh failed:', error);\n }\n }\n }\n\n // Full login required\n return await login();\n}\n\nasync function refreshToken(refreshTokenValue: string): Promise<string | null> {\n const supabase = createClient(CONFIG.SUPABASE_URL, CONFIG.SUPABASE_ANON_KEY);\n const { data, error } = await supabase.auth.refreshSession({\n refresh_token: refreshTokenValue,\n });\n\n if (error || !data.session) {\n logError('Refresh failed:', error?.message);\n return null;\n }\n\n const expiresAt = Date.now() + (data.session.expires_in ?? 3600) * 1000;\n cachedAccessToken = data.session.access_token;\n tokenExpiresAt = expiresAt;\n\n saveTokens({\n access_token: data.session.access_token,\n refresh_token: data.session.refresh_token ?? refreshTokenValue,\n expires_at: expiresAt,\n });\n\n log('Token refreshed successfully');\n return data.session.access_token;\n}\n\nasync function login(): Promise<string> {\n if (!CONFIG.SUPABASE_URL || !CONFIG.SUPABASE_ANON_KEY) {\n throw new Error(\n 'LevelUp.log is not configured. Run `npx @levelup-log/mcp-server init` to set up.'\n );\n }\n\n const supabase = createClient(CONFIG.SUPABASE_URL, CONFIG.SUPABASE_ANON_KEY);\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n\n // Start callback server before opening browser\n const callbackPromise = startOAuthCallbackServer();\n\n const redirectTo = `http://127.0.0.1:${CONFIG.AUTH_PORT}/callback`;\n\n const { data, error } = await supabase.auth.signInWithOAuth({\n provider: 'google',\n options: {\n redirectTo,\n queryParams: {\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n },\n },\n });\n\n if (error || !data.url) {\n throw new Error(`Failed to initiate OAuth: ${error?.message ?? 'No URL returned'}`);\n }\n\n // Open browser\n const open = await import('open');\n await open.default(data.url);\n console.error('Opening browser for Google login...');\n\n // Wait for callback\n const result = await callbackPromise;\n const expiresAt = Date.now() + result.expires_in * 1000;\n\n cachedAccessToken = result.access_token;\n tokenExpiresAt = expiresAt;\n\n saveTokens({\n access_token: result.access_token,\n refresh_token: result.refresh_token,\n expires_at: expiresAt,\n });\n\n log('Login successful');\n return result.access_token;\n}\n\nexport function isAuthenticated(): boolean {\n const stored = loadTokens();\n return !!(stored && Date.now() < stored.expires_at - 60_000);\n}\n\nexport function logout(): void {\n cachedAccessToken = null;\n tokenExpiresAt = 0;\n clearTokens();\n log('Logged out');\n}\n","import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { log, logError } from '../utils/logger.js';\n\nconst CREDENTIALS_DIR = join(homedir(), '.levelup');\nconst CREDENTIALS_FILE = join(CREDENTIALS_DIR, 'credentials.json');\n\ninterface StoredTokens {\n access_token: string;\n refresh_token: string;\n expires_at: number;\n}\n\nexport function loadTokens(): StoredTokens | null {\n try {\n if (!existsSync(CREDENTIALS_FILE)) return null;\n const data = readFileSync(CREDENTIALS_FILE, 'utf-8');\n const tokens = JSON.parse(data) as StoredTokens;\n log('Loaded tokens from', CREDENTIALS_FILE);\n return tokens;\n } catch (error) {\n logError('Failed to load tokens:', error);\n return null;\n }\n}\n\nexport function saveTokens(tokens: StoredTokens): void {\n try {\n if (!existsSync(CREDENTIALS_DIR)) {\n mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });\n }\n writeFileSync(CREDENTIALS_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });\n log('Saved tokens to', CREDENTIALS_FILE);\n } catch (error) {\n logError('Failed to save tokens:', error);\n }\n}\n\nexport function clearTokens(): void {\n try {\n if (existsSync(CREDENTIALS_FILE)) {\n writeFileSync(CREDENTIALS_FILE, '{}', { mode: 0o600 });\n log('Cleared tokens');\n }\n } catch (error) {\n logError('Failed to clear tokens:', error);\n }\n}\n","import { CONFIG } from './config.js';\n\nexport function log(...args: unknown[]): void {\n if (CONFIG.DEBUG) {\n console.error('[levelup]', ...args);\n }\n}\n\nexport function logError(...args: unknown[]): void {\n console.error('[levelup:error]', ...args);\n}\n","import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport { CONFIG } from '../utils/config.js';\nimport { log } from '../utils/logger.js';\n\ninterface OAuthResult {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n}\n\n/**\n * Start a temporary localhost HTTP server to receive the OAuth callback.\n * Opens browser → Google OAuth → redirect to localhost:PORT/callback → extract tokens → close server.\n */\nexport function startOAuthCallbackServer(): Promise<OAuthResult> {\n return new Promise((resolve, reject) => {\n const port = CONFIG.AUTH_PORT;\n let server: Server;\n const timeout = setTimeout(() => {\n server?.close();\n reject(new Error('OAuth login timed out after 5 minutes'));\n }, 5 * 60 * 1000);\n\n server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`);\n\n if (url.pathname === '/callback') {\n // Supabase redirects with fragment (#), but we need query params\n // The frontend redirect page will forward fragment params as query params\n const accessToken = url.searchParams.get('access_token');\n const refreshToken = url.searchParams.get('refresh_token');\n const expiresIn = url.searchParams.get('expires_in');\n\n if (accessToken && refreshToken) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <body style=\"font-family: system-ui; text-align: center; padding: 50px; background: #0a0a0a; color: #e5e5e5;\">\n <h1>LevelUp.log</h1>\n <p style=\"color: #34d399; font-size: 1.5em;\">Login successful!</p>\n <p>You can close this window and return to your LLM tool.</p>\n </body>\n </html>\n `);\n\n clearTimeout(timeout);\n server.close();\n resolve({\n access_token: accessToken,\n refresh_token: refreshToken,\n expires_in: parseInt(expiresIn || '3600', 10),\n });\n } else {\n // Serve a page that extracts fragment params and redirects as query params\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <body>\n <script>\n const hash = window.location.hash.substring(1);\n if (hash) {\n window.location.href = '/callback?' + hash;\n } else {\n document.body.innerHTML = '<p>Login failed. No tokens received.</p>';\n }\n </script>\n </body>\n </html>\n `);\n }\n } else {\n res.writeHead(404);\n res.end('Not found');\n }\n });\n\n server.listen(port, '127.0.0.1', () => {\n log(`OAuth callback server listening on http://127.0.0.1:${port}`);\n });\n\n server.on('error', (err) => {\n clearTimeout(timeout);\n reject(new Error(`Failed to start OAuth server on port ${port}: ${err.message}`));\n });\n });\n}\n","import { randomBytes, createHash } from 'node:crypto';\n\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url');\n}\n\nexport function generateCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url');\n}\n","import { CONFIG } from './config.js';\nimport { getValidToken } from '../auth/manager.js';\nimport { logError } from './logger.js';\n\ninterface ApiResponse<T = unknown> {\n data?: T;\n error?: string;\n status: number;\n}\n\nexport async function apiGet<T = unknown>(path: string, params?: Record<string, string>): Promise<ApiResponse<T>> {\n const token = await getValidToken();\n const url = new URL(`${CONFIG.SUPABASE_URL}/functions/v1/${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n url.searchParams.set(k, v);\n }\n }\n\n try {\n const res = await fetch(url.toString(), {\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n apikey: CONFIG.SUPABASE_ANON_KEY,\n },\n });\n\n const body = await res.json();\n if (!res.ok) {\n return { error: body.error || `HTTP ${res.status}`, status: res.status };\n }\n return { data: body as T, status: res.status };\n } catch (error) {\n logError('API GET error:', error);\n return { error: (error as Error).message, status: 0 };\n }\n}\n\nexport async function apiPost<T = unknown>(path: string, body: unknown): Promise<ApiResponse<T>> {\n const token = await getValidToken();\n const url = `${CONFIG.SUPABASE_URL}/functions/v1/${path}`;\n\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n apikey: CONFIG.SUPABASE_ANON_KEY,\n },\n body: JSON.stringify(body),\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { error: data.error || `HTTP ${res.status}`, status: res.status };\n }\n return { data: data as T, status: res.status };\n } catch (error) {\n logError('API POST error:', error);\n return { error: (error as Error).message, status: 0 };\n }\n}\n"],"mappings":"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","names":["URL","URL","refreshToken","createServer"]}
package/dist/cli.js CHANGED
@@ -2,7 +2,8 @@
2
2
  import {
3
3
  createServer,
4
4
  logError
5
- } from "./chunk-4GGNGOIO.js";
5
+ } from "./chunk-JROVEQX3.js";
6
+ import "./chunk-FII2XEJ7.js";
6
7
 
7
8
  // src/cli.ts
8
9
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -14,9 +15,13 @@ async function serve() {
14
15
  console.error("LevelUp.log MCP server running on stdio");
15
16
  }
16
17
  async function init() {
17
- const { runInit } = await import("./init-GNFWFY5S.js");
18
+ const { runInit } = await import("./init-GKLMB6BS.js");
18
19
  await runInit();
19
20
  }
21
+ async function heartbeat() {
22
+ const { main: main2 } = await import("./heartbeat-A4ZMVGSV.js");
23
+ await main2();
24
+ }
20
25
  async function main() {
21
26
  switch (command) {
22
27
  case "serve":
@@ -26,9 +31,12 @@ async function main() {
26
31
  case "init":
27
32
  await init();
28
33
  break;
34
+ case "heartbeat":
35
+ await heartbeat();
36
+ break;
29
37
  default:
30
38
  console.error(`Unknown command: ${command}`);
31
- console.error("Usage: levelup-log [serve|init]");
39
+ console.error("Usage: levelup-log [serve|init|heartbeat]");
32
40
  process.exit(1);
33
41
  }
34
42
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\nimport { logError } from \"./utils/logger.js\";\n\nconst command = process.argv[2];\n\nasync function serve() {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\"LevelUp.log MCP server running on stdio\");\n}\n\nasync function init() {\n const { runInit } = await import(\"./init/index.js\");\n await runInit();\n}\n\nasync function main() {\n switch (command) {\n case \"serve\":\n case undefined:\n await serve();\n break;\n case \"init\":\n await init();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n console.error(\"Usage: levelup-log [serve|init]\");\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n logError(\"Fatal error:\", error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;AAAA,SAAS,4BAA4B;AAIrC,IAAM,UAAU,QAAQ,KAAK,CAAC;AAE9B,eAAe,QAAQ;AACrB,QAAM,SAAS,aAAa;AAC5B,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,UAAQ,MAAM,yCAAyC;AACzD;AAEA,eAAe,OAAO;AACpB,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,oBAAiB;AAClD,QAAM,QAAQ;AAChB;AAEA,eAAe,OAAO;AACpB,UAAQ,SAAS;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AACH,YAAM,MAAM;AACZ;AAAA,IACF,KAAK;AACH,YAAM,KAAK;AACX;AAAA,IACF;AACE,cAAQ,MAAM,oBAAoB,OAAO,EAAE;AAC3C,cAAQ,MAAM,iCAAiC;AAC/C,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,WAAS,gBAAgB,KAAK;AAC9B,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\nimport { logError } from \"./utils/logger.js\";\n\nconst command = process.argv[2];\n\nasync function serve() {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error(\"LevelUp.log MCP server running on stdio\");\n}\n\nasync function init() {\n const { runInit } = await import(\"./init/index.js\");\n await runInit();\n}\n\nasync function heartbeat() {\n const { main } = await import(\"./scripts/heartbeat.js\");\n await main();\n}\n\nasync function main() {\n switch (command) {\n case \"serve\":\n case undefined:\n await serve();\n break;\n case \"init\":\n await init();\n break;\n case \"heartbeat\":\n await heartbeat();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n console.error(\"Usage: levelup-log [serve|init|heartbeat]\");\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n logError(\"Fatal error:\", error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;AAAA,SAAS,4BAA4B;AAIrC,IAAM,UAAU,QAAQ,KAAK,CAAC;AAE9B,eAAe,QAAQ;AACrB,QAAM,SAAS,aAAa;AAC5B,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,UAAQ,MAAM,yCAAyC;AACzD;AAEA,eAAe,OAAO;AACpB,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,oBAAiB;AAClD,QAAM,QAAQ;AAChB;AAEA,eAAe,YAAY;AACzB,QAAM,EAAE,MAAAA,MAAK,IAAI,MAAM,OAAO,yBAAwB;AACtD,QAAMA,MAAK;AACb;AAEA,eAAe,OAAO;AACpB,UAAQ,SAAS;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AACH,YAAM,MAAM;AACZ;AAAA,IACF,KAAK;AACH,YAAM,KAAK;AACX;AAAA,IACF,KAAK;AACH,YAAM,UAAU;AAChB;AAAA,IACF;AACE,cAAQ,MAAM,oBAAoB,OAAO,EAAE;AAC3C,cAAQ,MAAM,2CAA2C;AACzD,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,WAAS,gBAAgB,KAAK;AAC9B,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["main"]}