@levelup-log/mcp-server 0.3.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.
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
// src/server.ts
|
|
9
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
10
|
import { z } from "zod";
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
import * as os from "os";
|
|
13
|
+
import * as path from "path";
|
|
11
14
|
|
|
12
15
|
// src/utils/rate-limiter.ts
|
|
13
16
|
var CATEGORY_COOLDOWN_MS = 6e4;
|
|
@@ -244,9 +247,9 @@ async function login() {
|
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
// src/utils/api.ts
|
|
247
|
-
async function apiGet(
|
|
250
|
+
async function apiGet(path2, params) {
|
|
248
251
|
const token = await getValidToken();
|
|
249
|
-
const url = new URL(`${CONFIG.SUPABASE_URL}/functions/v1/${
|
|
252
|
+
const url = new URL(`${CONFIG.SUPABASE_URL}/functions/v1/${path2}`);
|
|
250
253
|
if (params) {
|
|
251
254
|
for (const [k, v] of Object.entries(params)) {
|
|
252
255
|
url.searchParams.set(k, v);
|
|
@@ -270,9 +273,9 @@ async function apiGet(path, params) {
|
|
|
270
273
|
return { error: error.message, status: 0 };
|
|
271
274
|
}
|
|
272
275
|
}
|
|
273
|
-
async function apiPost(
|
|
276
|
+
async function apiPost(path2, body) {
|
|
274
277
|
const token = await getValidToken();
|
|
275
|
-
const url = `${CONFIG.SUPABASE_URL}/functions/v1/${
|
|
278
|
+
const url = `${CONFIG.SUPABASE_URL}/functions/v1/${path2}`;
|
|
276
279
|
try {
|
|
277
280
|
const res = await fetch(url, {
|
|
278
281
|
method: "POST",
|
|
@@ -295,6 +298,23 @@ async function apiPost(path, body) {
|
|
|
295
298
|
}
|
|
296
299
|
|
|
297
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
|
+
}
|
|
298
318
|
var COMPLEXITY_BASE = {
|
|
299
319
|
trivial: 10,
|
|
300
320
|
normal: 30,
|
|
@@ -573,6 +593,115 @@ Keep descriptions abstract \u2014 no real names, client names, or source code.`,
|
|
|
573
593
|
};
|
|
574
594
|
}
|
|
575
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
|
+
);
|
|
576
705
|
server.registerPrompt(
|
|
577
706
|
"levelup_coach",
|
|
578
707
|
{
|
|
@@ -608,4 +737,4 @@ export {
|
|
|
608
737
|
calculateXp,
|
|
609
738
|
createServer2 as createServer
|
|
610
739
|
};
|
|
611
|
-
//# sourceMappingURL=chunk-
|
|
740
|
+
//# sourceMappingURL=chunk-VJKF2CNS.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 * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\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// ─── Diary helpers ────────────────────────────────────────────────────────────\nconst DIARY_DIR = path.join(os.homedir(), \"Documents\", \"tai\", \"日記\");\n\nfunction diaryPath(date: string): string {\n return path.join(DIARY_DIR, `${date}.md`);\n}\n\nfunction todayDate(): string {\n return new Date().toISOString().split(\"T\")[0];\n}\n\nfunction buildDiaryMd(date: string, content: string): string {\n return `---\\ndate: ${date}\\ntags: [日記, levelup]\\npublic: false\\n---\\n\\n${content}\\n`;\n}\n// ─────────────────────────────────────────────────────────────────────────────\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 // ─── Tool 6: write_diary ──────────────────────────────────────\n server.registerTool(\n \"write_diary\",\n {\n title: \"Write Diary Entry\",\n description: `Write a personal diary entry to Obsidian (~/Documents/tai/日記/YYYY-MM-DD.md).\n\nWHEN TO USE:\n• User says \"寫日記\", \"記錄今天\", \"diary\", or similar\n• Proactively suggest after 3+ achievements recorded in a session: \"今天做了不少,要寫一下日記嗎?\"\n\nHOW TO DRAFT (do this before calling the tool):\n1. Call get_recent with days=1 to fetch today's achievements\n2. Draft a Markdown diary entry in the user's language — feel like a real human diary, not a log:\n • First-person, with emotion and reflection\n • What was hard, what felt good, what was learned\n • Example:\n \"今天終於把那個卡了三天的 bug 修好了。說實話一開始以為要放棄了,但最後還是找到根本原因。部署上去的時候鬆了一口氣。\\n\\n下午設計了日記功能,比預期順,可能是因為之前 MCP 架構打好了。\"\n3. Show draft to user, confirm or let them edit\n4. Call write_diary with the final Markdown content`,\n inputSchema: {\n content: z\n .string()\n .describe(\n \"Diary content in Markdown format (first-person, reflective)\",\n ),\n entry_date: z\n .string()\n .optional()\n .describe(\"Date in YYYY-MM-DD format. Defaults to today.\"),\n },\n },\n async ({ content, entry_date }) => {\n const date = entry_date ?? todayDate();\n const filePath = diaryPath(date);\n\n try {\n fs.mkdirSync(DIARY_DIR, { recursive: true });\n fs.writeFileSync(filePath, buildDiaryMd(date, content), \"utf8\");\n log(\"write_diary\", { date, path: filePath });\n return {\n content: [\n {\n type: \"text\",\n text: `日記已寫入 Obsidian\\n路徑:${filePath}\\n\\n${content}`,\n },\n ],\n };\n } catch (err) {\n return {\n content: [\n { type: \"text\", text: `寫入失敗:${(err as Error).message}` },\n ],\n isError: true,\n };\n }\n },\n );\n\n // ─── Tool 7: read_diary ───────────────────────────────────────\n server.registerTool(\n \"read_diary\",\n {\n title: \"Read Diary\",\n description:\n \"Read diary entries from Obsidian (~/Documents/tai/日記/). Fetch a specific date or recent entries.\",\n inputSchema: {\n date: z\n .string()\n .optional()\n .describe(\n \"Specific date in YYYY-MM-DD format. If omitted, returns recent entries.\",\n ),\n days: z\n .number()\n .min(1)\n .max(90)\n .optional()\n .default(7)\n .describe(\n \"Number of recent days to fetch (used when date is not specified).\",\n ),\n },\n },\n async ({ date, days }) => {\n try {\n if (date) {\n const filePath = diaryPath(date);\n if (!fs.existsSync(filePath)) {\n return { content: [{ type: \"text\", text: `${date} 沒有日記` }] };\n }\n const content = fs.readFileSync(filePath, \"utf8\");\n return { content: [{ type: \"text\", text: content }] };\n }\n\n // Recent N days\n if (!fs.existsSync(DIARY_DIR)) {\n return { content: [{ type: \"text\", text: \"尚未有任何日記\" }] };\n }\n const cutoff = new Date();\n cutoff.setDate(cutoff.getDate() - days);\n\n const files = fs\n .readdirSync(DIARY_DIR)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(\".md\", \"\"))\n .filter((d) => new Date(d) >= cutoff)\n .sort()\n .reverse();\n\n if (files.length === 0) {\n return {\n content: [{ type: \"text\", text: `最近 ${days} 天沒有日記` }],\n };\n }\n\n const entries = files.map((d) => {\n const raw = fs.readFileSync(diaryPath(d), \"utf8\");\n // Strip frontmatter for display\n const body = raw.replace(/^---[\\s\\S]*?---\\n\\n?/, \"\");\n return `## ${d}\\n\\n${body}`;\n });\n\n return { content: [{ type: \"text\", text: entries.join(\"\\n---\\n\\n\") }] };\n } catch (err) {\n return {\n content: [\n { type: \"text\", text: `讀取失敗:${(err as Error).message}` },\n ],\n isError: true,\n };\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","path","createServer"]}
|
package/dist/cli.js
CHANGED
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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"]}
|