@levelup-log/mcp-server 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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(path, params) {
250
+ async function apiGet(path2, params) {
248
251
  const token = await getValidToken();
249
- const url = new URL(`${CONFIG.SUPABASE_URL}/functions/v1/${path}`);
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(path, body) {
276
+ async function apiPost(path2, body) {
274
277
  const token = await getValidToken();
275
- const url = `${CONFIG.SUPABASE_URL}/functions/v1/${path}`;
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-JROVEQX3.js.map
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":";;;;;;;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,SAAS;AAClB,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,UAAU;;;ACJtB,IAAM,uBAAuB;AAC7B,IAAM,2BAA2B;AAOjC,IAAM,qBAAkC,CAAC;AAElC,SAAS,eAAe,UAAyD;AACtF,QAAM,MAAM,KAAK,IAAI;AAGrB,MAAI,mBAAmB,UAAU,0BAA0B;AACzD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,0BAA0B,wBAAwB;AAAA,IAC5D;AAAA,EACF;AAGA,QAAM,mBAAmB,mBACtB,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,EACrC,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;AAE9C,MAAI,oBAAoB,MAAM,iBAAiB,YAAY,sBAAsB;AAC/E,UAAM,cAAc,KAAK;AAAA,OACtB,wBAAwB,MAAM,iBAAiB,cAAc;AAAA,IAChE;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,gCAAgC,WAAW,+BAA+B,QAAQ;AAAA,IAC5F;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,KAAK;AACzB;AAEO,SAAS,gBAAgB,UAAwB;AACtD,qBAAmB,KAAK,EAAE,UAAU,WAAW,KAAK,IAAI,EAAE,CAAC;AAC7D;;;ACzCA,SAAS,oBAAoB;;;ACA7B,SAAS,cAAc,eAAe,WAAW,kBAAkB;AACnE,SAAS,YAAY;AACrB,SAAS,eAAe;;;ACAjB,SAAS,OAAO,MAAuB;AAC5C,MAAI,OAAO,OAAO;AAChB,YAAQ,MAAM,aAAa,GAAG,IAAI;AAAA,EACpC;AACF;AAEO,SAAS,YAAY,MAAuB;AACjD,UAAQ,MAAM,mBAAmB,GAAG,IAAI;AAC1C;;;ADLA,IAAM,kBAAkB,KAAK,QAAQ,GAAG,UAAU;AAClD,IAAM,mBAAmB,KAAK,iBAAiB,kBAAkB;AAQ1D,SAAS,aAAkC;AAChD,MAAI;AACF,QAAI,CAAC,WAAW,gBAAgB,EAAG,QAAO;AAC1C,UAAM,OAAO,aAAa,kBAAkB,OAAO;AACnD,UAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAI,sBAAsB,gBAAgB;AAC1C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,aAAS,0BAA0B,KAAK;AACxC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,WAAW,QAA4B;AACrD,MAAI;AACF,QAAI,CAAC,WAAW,eAAe,GAAG;AAChC,gBAAU,iBAAiB,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,IAC7D;AACA,kBAAc,kBAAkB,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAChF,QAAI,mBAAmB,gBAAgB;AAAA,EACzC,SAAS,OAAO;AACd,aAAS,0BAA0B,KAAK;AAAA,EAC1C;AACF;;;AErCA,SAAS,oBAA4E;AACrF,SAAS,OAAAA,YAAW;AAcb,SAAS,2BAAiD;AAC/D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,OAAO,OAAO;AACpB,QAAI;AACJ,UAAM,UAAU,WAAW,MAAM;AAC/B,cAAQ,MAAM;AACd,aAAO,IAAI,MAAM,uCAAuC,CAAC;AAAA,IAC3D,GAAG,IAAI,KAAK,GAAI;AAEhB,aAAS,aAAa,CAAC,KAAsB,QAAwB;AACnE,YAAM,MAAM,IAAIC,KAAI,IAAI,OAAO,KAAK,oBAAoB,IAAI,EAAE;AAE9D,UAAI,IAAI,aAAa,aAAa;AAGhC,cAAM,cAAc,IAAI,aAAa,IAAI,cAAc;AACvD,cAAMC,gBAAe,IAAI,aAAa,IAAI,eAAe;AACzD,cAAM,YAAY,IAAI,aAAa,IAAI,YAAY;AAEnD,YAAI,eAAeA,eAAc;AAC/B,cAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,cAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAQP;AAED,uBAAa,OAAO;AACpB,iBAAO,MAAM;AACb,kBAAQ;AAAA,YACN,cAAc;AAAA,YACd,eAAeA;AAAA,YACf,YAAY,SAAS,aAAa,QAAQ,EAAE;AAAA,UAC9C,CAAC;AAAA,QACH,OAAO;AAEL,cAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,cAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAaP;AAAA,QACH;AAAA,MACF,OAAO;AACL,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI,WAAW;AAAA,MACrB;AAAA,IACF,CAAC;AAED,WAAO,OAAO,MAAM,aAAa,MAAM;AACrC,UAAI,uDAAuD,IAAI,EAAE;AAAA,IACnE,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,mBAAa,OAAO;AACpB,aAAO,IAAI,MAAM,wCAAwC,IAAI,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,IAClF,CAAC;AAAA,EACH,CAAC;AACH;;;ACtFA,SAAS,aAAa,kBAAkB;AAEjC,SAAS,uBAA+B;AAC7C,SAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAC7C;AAEO,SAAS,sBAAsB,UAA0B;AAC9D,SAAO,WAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,WAAW;AACjE;;;AJDA,IAAI,oBAAmC;AACvC,IAAI,iBAAyB;AAQ7B,eAAsB,gBAAiC;AAErD,MAAI,qBAAqB,KAAK,IAAI,IAAI,iBAAiB,KAAQ;AAC7D,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,WAAW;AAC1B,MAAI,QAAQ;AACV,QAAI,KAAK,IAAI,IAAI,OAAO,aAAa,KAAQ;AAC3C,0BAAoB,OAAO;AAC3B,uBAAiB,OAAO;AACxB,aAAO,OAAO;AAAA,IAChB;AAGA,QAAI,OAAO,eAAe;AACxB,UAAI;AACF,cAAM,YAAY,MAAM,aAAa,OAAO,aAAa;AACzD,YAAI,UAAW,QAAO;AAAA,MACxB,SAAS,OAAO;AACd,iBAAS,yBAAyB,KAAK;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAGA,SAAO,MAAM,MAAM;AACrB;AAEA,eAAe,aAAa,mBAAmD;AAC7E,QAAM,WAAW,aAAa,OAAO,cAAc,OAAO,iBAAiB;AAC3E,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,KAAK,eAAe;AAAA,IACzD,eAAe;AAAA,EACjB,CAAC;AAED,MAAI,SAAS,CAAC,KAAK,SAAS;AAC1B,aAAS,mBAAmB,OAAO,OAAO;AAC1C,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,KAAK,IAAI,KAAK,KAAK,QAAQ,cAAc,QAAQ;AACnE,sBAAoB,KAAK,QAAQ;AACjC,mBAAiB;AAEjB,aAAW;AAAA,IACT,cAAc,KAAK,QAAQ;AAAA,IAC3B,eAAe,KAAK,QAAQ,iBAAiB;AAAA,IAC7C,YAAY;AAAA,EACd,CAAC;AAED,MAAI,8BAA8B;AAClC,SAAO,KAAK,QAAQ;AACtB;AAEA,eAAe,QAAyB;AACtC,MAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,mBAAmB;AACrD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,aAAa,OAAO,cAAc,OAAO,iBAAiB;AAC3E,QAAM,eAAe,qBAAqB;AAC1C,QAAM,gBAAgB,sBAAsB,YAAY;AAGxD,QAAM,kBAAkB,yBAAyB;AAEjD,QAAM,aAAa,oBAAoB,OAAO,SAAS;AAEvD,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,KAAK,gBAAgB;AAAA,IAC1D,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,MACA,aAAa;AAAA,QACX,gBAAgB;AAAA,QAChB,uBAAuB;AAAA,MACzB;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,SAAS,CAAC,KAAK,KAAK;AACtB,UAAM,IAAI,MAAM,6BAA6B,OAAO,WAAW,iBAAiB,EAAE;AAAA,EACpF;AAGA,QAAM,OAAO,MAAM,OAAO,MAAM;AAChC,QAAM,KAAK,QAAQ,KAAK,GAAG;AAC3B,UAAQ,MAAM,qCAAqC;AAGnD,QAAM,SAAS,MAAM;AACrB,QAAM,YAAY,KAAK,IAAI,IAAI,OAAO,aAAa;AAEnD,sBAAoB,OAAO;AAC3B,mBAAiB;AAEjB,aAAW;AAAA,IACT,cAAc,OAAO;AAAA,IACrB,eAAe,OAAO;AAAA,IACtB,YAAY;AAAA,EACd,CAAC;AAED,MAAI,kBAAkB;AACtB,SAAO,OAAO;AAChB;;;AKhHA,eAAsB,OAAoBC,OAAc,QAA0D;AAChH,QAAM,QAAQ,MAAM,cAAc;AAClC,QAAM,MAAM,IAAI,IAAI,GAAG,OAAO,YAAY,iBAAiBA,KAAI,EAAE;AACjE,MAAI,QAAQ;AACV,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,UAAI,aAAa,IAAI,GAAG,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,IAAI,SAAS,GAAG;AAAA,MACtC,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,gBAAgB;AAAA,QAChB,QAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AAED,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,CAAC,IAAI,IAAI;AACX,aAAO,EAAE,OAAO,KAAK,SAAS,QAAQ,IAAI,MAAM,IAAI,QAAQ,IAAI,OAAO;AAAA,IACzE;AACA,WAAO,EAAE,MAAM,MAAW,QAAQ,IAAI,OAAO;AAAA,EAC/C,SAAS,OAAO;AACd,aAAS,kBAAkB,KAAK;AAChC,WAAO,EAAE,OAAQ,MAAgB,SAAS,QAAQ,EAAE;AAAA,EACtD;AACF;AAEA,eAAsB,QAAqBA,OAAc,MAAwC;AAC/F,QAAM,QAAQ,MAAM,cAAc;AAClC,QAAM,MAAM,GAAG,OAAO,YAAY,iBAAiBA,KAAI;AAEvD,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,gBAAgB;AAAA,QAChB,QAAQ,OAAO;AAAA,MACjB;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,CAAC,IAAI,IAAI;AACX,aAAO,EAAE,OAAO,KAAK,SAAS,QAAQ,IAAI,MAAM,IAAI,QAAQ,IAAI,OAAO;AAAA,IACzE;AACA,WAAO,EAAE,MAAiB,QAAQ,IAAI,OAAO;AAAA,EAC/C,SAAS,OAAO;AACd,aAAS,mBAAmB,KAAK;AACjC,WAAO,EAAE,OAAQ,MAAgB,SAAS,QAAQ,EAAE;AAAA,EACtD;AACF;;;APjDA,IAAM,YAAiB,UAAQ,WAAQ,GAAG,aAAa,OAAO,cAAI;AAElE,SAAS,UAAU,MAAsB;AACvC,SAAY,UAAK,WAAW,GAAG,IAAI,KAAK;AAC1C;AAEA,SAAS,YAAoB;AAC3B,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAC9C;AAEA,SAAS,aAAa,MAAc,SAAyB;AAC3D,SAAO;AAAA,QAAc,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAAgD,OAAO;AAAA;AAClF;AAuBA,IAAM,kBAA8C;AAAA,EAClD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,OAAO;AAAA,EACP,WAAW;AACb;AAEA,SAAS,eAAe,SAAyB;AAC/C,MAAI,UAAU,GAAI,QAAO;AACzB,MAAI,UAAU,GAAI,QAAO;AACzB,MAAI,UAAU,IAAK,QAAO;AAC1B,SAAO;AACT;AAGA,SAAS,YAAY,OAAuB;AAC1C,MAAI,SAAS,EAAG,QAAO;AACvB,SAAO,KAAK,IAAI,KAAK,MAAM,KAAK,KAAK,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE;AAC3D;AAGA,SAAS,WAAW,OAAuB;AACzC,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,EAAE;AACxC;AAGA,SAAS,YAAY,QAAwB;AAC3C,SAAO,KAAK,IAAI,KAAK,IAAI,QAAQ,CAAC,GAAG,EAAE;AACzC;AAEO,SAAS,YAAY,QAQjB;AACT,QAAM,YAAY,qBAAqB,OAAO,QAAQ,GAAG,aAAa;AAEtE,QAAM,OACJ,gBAAgB,OAAO,UAAU,IACjC,eAAe,OAAO,gBAAgB,EAAE;AAE1C,QAAM,UACJ,YAAY,OAAO,gBAAgB,CAAC,IACpC,WAAW,OAAO,eAAe,CAAC,IAClC,YAAY,OAAO,uBAAuB,CAAC;AAE7C,QAAM,MAAM,KAAK;AAAA,IACf;AAAA,IACA,KAAK,IAAI,GAAG,KAAK,OAAO,OAAO,WAAW,SAAS,CAAC;AAAA,EACtD;AACA,SAAO,OAAO,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI;AACtE;AAGA,SAAS,uBAA+B;AACtC,SAAO,OAAO,QAAQ,oBAAoB,EACvC,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC,WAAM,IAAI,WAAW,EAAE,EAChE,KAAK,IAAI;AACd;AAKA,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcrB,SAASC,gBAAe;AAC7B,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,eAAe,SAAS,QAAQ;AAAA,IACxC,EAAE,cAAc,oBAAoB;AAAA,EACtC;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWjB,qBAAqB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUlB,aAAa;AAAA,QACX,UAAU,EAAE,KAAK,sBAAsB;AAAA,QACvC,OAAO,EACJ,OAAO,EACP;AAAA,UACC;AAAA,QACF;AAAA,QACF,aAAa,EACV,OAAO,EACP,SAAS,mDAAmD;AAAA,QAC/D,YAAY,EACT,KAAK,CAAC,WAAW,UAAU,eAAe,SAAS,WAAW,CAAC,EAC/D;AAAA,UACC;AAAA,QACF;AAAA,QACF,cAAc,EACX,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,cAAc,EACX,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,aAAa,EACV,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,qBAAqB,EAClB,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,eAAe,EACZ,QAAQ,EACR,SAAS,EACT,QAAQ,KAAK,EACb;AAAA,UACC;AAAA,QACF;AAAA,QACF,MAAM,EACH,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SAAS,6BAA6B;AAAA,QACzC,WAAW,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,IAAI,EACZ,SAAS,qCAAqC;AAAA,MACnD;AAAA,IACF;AAAA,IACA,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAAM;AAEJ,YAAM,YAAY,eAAe,QAAQ;AACzC,UAAI,CAAC,UAAU,SAAS;AACtB,eAAO;AAAA,UACL,SAAS;AAAA,YACP,EAAE,MAAM,QAAQ,MAAM,iBAAiB,UAAU,MAAM,GAAG;AAAA,UAC5D;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAGA,YAAM,SAAS,MAAM,QAAQ,sBAAsB;AAAA,QACjD;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,MACnB,CAAC;AAED,UAAI,OAAO,OAAO;AAChB,eAAO;AAAA,UACL,SAAS;AAAA,YACP,EAAE,MAAM,QAAQ,MAAM,qBAAqB,OAAO,KAAK,GAAG;AAAA,UAC5D;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,sBAAgB,QAAQ;AAExB,YAAM,OAAO,OAAO;AACpB,YAAM,WACH,KAAK,MACN,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACH,YAAM,QAAQ,KAAK;AACnB,YAAM,YAAY,KAAK;AAIvB,UAAI,sBAAsB,EAAE,UAAU,OAAO,IAAI,SAAS,CAAC;AAE3D,YAAM,QAAQ;AAAA,QACZ,0BAA0B,QAAQ;AAAA,QAClC,QAAQ,aAAa,MAAM,QAAQ,eAAe,MAAM,OAAO,KAAK;AAAA,QACpE,OAAO,YAAY,aAAa,MAAM,SAAS,KAAK;AAAA,QACpD,OAAO,iBAAiB,WAAW,MAAM,cAAc,UAAU;AAAA,QACjE,GAAI,WAAW,SACX;AAAA,UACE;AAAA,iBAAa,UAAU,SAAS,IAAI,MAAM,EAAE;AAAA,UAC5C,GAAG,UAAU;AAAA,YACX,CAAC,MAAM,KAAK,EAAE,QAAQ,WAAI,IAAI,EAAE,IAAI,KAAK,EAAE,MAAM;AAAA,UACnD;AAAA,QACF,IACA,CAAC;AAAA,MACP,EAAE,OAAO,OAAO;AAEhB,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,IAAI,EAAE,CAAC;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa,CAAC;AAAA,IAChB;AAAA,IACA,YAAY;AACV,YAAM,SAAS,MAAM,OAAO,WAAW;AACvC,UAAI,OAAO,OAAO;AAChB,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,CAAC;AAAA,UAC1D,SAAS;AAAA,QACX;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,OAAO,MAAM,MAAM,CAAC,EAAE,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,QACX,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE;AAAA,QACtD,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,QACrD,UAAU,EAAE,KAAK,sBAAsB,EAAE,SAAS;AAAA,MACpD;AAAA,IACF;AAAA,IACA,OAAO,EAAE,OAAO,MAAM,SAAS,MAAM;AACnC,YAAM,SAAiC;AAAA,QACrC,OAAO,OAAO,KAAK;AAAA,QACnB,MAAM,OAAO,IAAI;AAAA,MACnB;AACA,UAAI,SAAU,QAAO,WAAW;AAEhC,YAAM,SAAS,MAAM,OAAO,cAAc,MAAM;AAChD,UAAI,OAAO,OAAO;AAChB,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,CAAC;AAAA,UAC1D,SAAS;AAAA,QACX;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,OAAO,MAAM,MAAM,CAAC,EAAE,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa,CAAC;AAAA,IAChB;AAAA,IACA,YAAY;AACV,YAAM,SAAS,MAAM,OAAO,eAAe;AAC3C,UAAI,OAAO,OAAO;AAChB,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,CAAC;AAAA,UAC1D,SAAS;AAAA,QACX;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,OAAO,MAAM,MAAM,CAAC,EAAE,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,QACX,MAAM,EACH,KAAK,CAAC,UAAU,SAAS,UAAU,CAAC,EACpC,SAAS,EACT,QAAQ,QAAQ;AAAA,QACnB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,IACA,OAAO,EAAE,MAAM,MAAM,MAAM;AACzB,YAAM,SAAS,MAAM,OAAO,eAAe;AAAA,QACzC;AAAA,QACA,OAAO,OAAO,KAAK;AAAA,MACrB,CAAC;AAED,UAAI,OAAO,OAAO;AAChB,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,CAAC;AAAA,UAC1D,SAAS;AAAA,QACX;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,OAAO,MAAM,MAAM,CAAC,EAAE,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAeb,aAAa;AAAA,QACX,SAAS,EACN,OAAO,EACP;AAAA,UACC;AAAA,QACF;AAAA,QACF,YAAY,EACT,OAAO,EACP,SAAS,EACT,SAAS,+CAA+C;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,OAAO,EAAE,SAAS,WAAW,MAAM;AACjC,YAAM,OAAO,cAAc,UAAU;AACrC,YAAM,WAAW,UAAU,IAAI;AAE/B,UAAI;AACF,QAAG,aAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAG,iBAAc,UAAU,aAAa,MAAM,OAAO,GAAG,MAAM;AAC9D,YAAI,eAAe,EAAE,MAAM,MAAM,SAAS,CAAC;AAC3C,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM;AAAA,oBAAsB,QAAQ;AAAA;AAAA,EAAO,OAAO;AAAA,YACpD;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,eAAO;AAAA,UACL,SAAS;AAAA,YACP,EAAE,MAAM,QAAQ,MAAM,iCAAS,IAAc,OAAO,GAAG;AAAA,UACzD;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,QACX,MAAM,EACH,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,MAAM,EACH,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,SAAS,EACT,QAAQ,CAAC,EACT;AAAA,UACC;AAAA,QACF;AAAA,MACJ;AAAA,IACF;AAAA,IACA,OAAO,EAAE,MAAM,KAAK,MAAM;AACxB,UAAI;AACF,YAAI,MAAM;AACR,gBAAM,WAAW,UAAU,IAAI;AAC/B,cAAI,CAAI,cAAW,QAAQ,GAAG;AAC5B,mBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,GAAG,IAAI,4BAAQ,CAAC,EAAE;AAAA,UAC7D;AACA,gBAAM,UAAa,gBAAa,UAAU,MAAM;AAChD,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAE;AAAA,QACtD;AAGA,YAAI,CAAI,cAAW,SAAS,GAAG;AAC7B,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,6CAAU,CAAC,EAAE;AAAA,QACxD;AACA,cAAM,SAAS,oBAAI,KAAK;AACxB,eAAO,QAAQ,OAAO,QAAQ,IAAI,IAAI;AAEtC,cAAM,QACH,eAAY,SAAS,EACrB,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAC/B,IAAI,CAAC,MAAM,EAAE,QAAQ,OAAO,EAAE,CAAC,EAC/B,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,MAAM,EACnC,KAAK,EACL,QAAQ;AAEX,YAAI,MAAM,WAAW,GAAG;AACtB,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,gBAAM,IAAI,kCAAS,CAAC;AAAA,UACtD;AAAA,QACF;AAEA,cAAM,UAAU,MAAM,IAAI,CAAC,MAAM;AAC/B,gBAAM,MAAS,gBAAa,UAAU,CAAC,GAAG,MAAM;AAEhD,gBAAM,OAAO,IAAI,QAAQ,wBAAwB,EAAE;AACnD,iBAAO,MAAM,CAAC;AAAA;AAAA,EAAO,IAAI;AAAA,QAC3B,CAAC;AAED,eAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,KAAK,WAAW,EAAE,CAAC,EAAE;AAAA,MACxE,SAAS,KAAK;AACZ,eAAO;AAAA,UACL,SAAS;AAAA,YACP,EAAE,MAAM,QAAQ,MAAM,iCAAS,IAAc,OAAO,GAAG;AAAA,UACzD;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,IACJ;AAAA,IACA,OAAO;AAAA,MACL,UAAU;AAAA,QACR;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,YACP,MAAM;AAAA,YACN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAUR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":["URL","URL","refreshToken","path","createServer"]}
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createServer,
4
4
  logError
5
- } from "./chunk-JROVEQX3.js";
5
+ } from "./chunk-VJKF2CNS.js";
6
6
  import "./chunk-FII2XEJ7.js";
7
7
 
8
8
  // src/cli.ts
@@ -15,7 +15,7 @@ async function serve() {
15
15
  console.error("LevelUp.log MCP server running on stdio");
16
16
  }
17
17
  async function init() {
18
- const { runInit } = await import("./init-GKLMB6BS.js");
18
+ const { runInit } = await import("./init-S6MGBOW7.js");
19
19
  await runInit();
20
20
  }
21
21
  async function heartbeat() {
@@ -266,9 +266,13 @@ function installSkill() {
266
266
  import { existsSync as existsSync4 } from "fs";
267
267
  import { join as join3 } from "path";
268
268
  import { homedir as homedir3, platform as platform2 } from "os";
269
+ import { createRequire } from "module";
270
+ var _require = createRequire(import.meta.url);
271
+ var _pkg = _require("../../package.json");
272
+ var PINNED_VERSION = _pkg.version;
269
273
  var MCP_CONFIG = {
270
274
  command: "npx",
271
- args: ["-y", "@levelup-log/mcp-server@latest", "serve"]
275
+ args: ["-y", `@levelup-log/mcp-server@${PINNED_VERSION}`]
272
276
  };
273
277
  function detectChatGptDesktop() {
274
278
  const home = homedir3();
@@ -405,4 +409,4 @@ function printManualInstructions() {
405
409
  export {
406
410
  runInit
407
411
  };
408
- //# sourceMappingURL=init-GKLMB6BS.js.map
412
+ //# sourceMappingURL=init-S6MGBOW7.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/init/detect.ts","../src/init/write-config.ts","../src/init/write-rules.ts","../src/init/write-skill.ts","../src/init/index.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir, platform } from 'os';\n\nexport interface DetectedTool {\n name: string;\n configPath: string;\n type: 'json-mcpServers';\n}\n\nexport interface DetectedRulesTarget {\n name: string;\n /** Global rules file path — write once, applies to all projects */\n globalRulesPath: string;\n /** How to write: 'append-section' adds a ## section, 'overwrite-if-absent' only creates if missing */\n writeMode: 'append-section' | 'overwrite-if-absent';\n}\n\nexport async function detectTools(): Promise<DetectedTool[]> {\n const home = homedir();\n const os = platform();\n const tools: DetectedTool[] = [];\n\n // ── Claude Desktop ────────────────────────────────────────────────────────\n const claudeDesktopConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json')\n : join(home, '.config', 'claude', 'claude_desktop_config.json');\n\n if (existsSync(claudeDesktopConfig)) {\n tools.push({ name: 'Claude Desktop', configPath: claudeDesktopConfig, type: 'json-mcpServers' });\n }\n\n // ── Claude Code ───────────────────────────────────────────────────────────\n const claudeCodeConfig = join(home, '.claude', 'settings.json');\n const claudeCodeAlt = join(home, '.claude.json');\n if (existsSync(claudeCodeConfig)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeConfig, type: 'json-mcpServers' });\n } else if (existsSync(claudeCodeAlt)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeAlt, type: 'json-mcpServers' });\n }\n\n // ── Cursor ────────────────────────────────────────────────────────────────\n const cursorConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : join(home, '.config', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json');\n const cursorSimple = join(home, '.cursor', 'mcp.json');\n\n if (existsSync(cursorConfig)) {\n tools.push({ name: 'Cursor', configPath: cursorConfig, type: 'json-mcpServers' });\n } else if (existsSync(cursorSimple)) {\n tools.push({ name: 'Cursor', configPath: cursorSimple, type: 'json-mcpServers' });\n }\n\n // ── Windsurf / Codeium ────────────────────────────────────────────────────\n const windsurfConfig = join(home, '.codeium', 'windsurf', 'mcp_config.json');\n if (existsSync(windsurfConfig)) {\n tools.push({ name: 'Windsurf', configPath: windsurfConfig, type: 'json-mcpServers' });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityConfig = join(home, '.antigravity', 'mcp.json');\n const antigravityAlt = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Antigravity', 'mcp.json')\n : join(home, '.config', 'antigravity', 'mcp.json');\n\n if (existsSync(antigravityConfig)) {\n tools.push({ name: 'Antigravity', configPath: antigravityConfig, type: 'json-mcpServers' });\n } else if (existsSync(antigravityAlt)) {\n tools.push({ name: 'Antigravity', configPath: antigravityAlt, type: 'json-mcpServers' });\n }\n\n // ── Continue (VS Code extension) ──────────────────────────────────────────\n const continueConfig = join(home, '.continue', 'config.json');\n if (existsSync(continueConfig)) {\n // Continue uses a different config format — detected but handled separately in writeConfig\n tools.push({ name: 'Continue', configPath: continueConfig, type: 'json-mcpServers' });\n }\n\n return tools;\n}\n\nexport async function detectRulesTargets(): Promise<DetectedRulesTarget[]> {\n const home = homedir();\n const targets: DetectedRulesTarget[] = [];\n\n // ── Cursor: global rules (~/.cursor/rules) ────────────────────────────────\n // Applies to ALL Cursor projects automatically\n if (existsSync(join(home, '.cursor')) || existsSync(join(home, '.cursor', 'mcp.json'))) {\n targets.push({\n name: 'Cursor',\n globalRulesPath: join(home, '.cursor', 'rules'),\n writeMode: 'append-section',\n });\n }\n\n // ── Windsurf: global rules ────────────────────────────────────────────────\n const windsurfDir = join(home, '.codeium', 'windsurf');\n if (existsSync(windsurfDir)) {\n targets.push({\n name: 'Windsurf',\n globalRulesPath: join(windsurfDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── Claude Code: global CLAUDE.md ─────────────────────────────────────────\n const claudeDir = join(home, '.claude');\n if (existsSync(claudeDir)) {\n targets.push({\n name: 'Claude Code',\n globalRulesPath: join(claudeDir, 'CLAUDE.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── GitHub Copilot (VS Code): global instructions ─────────────────────────\n // VS Code stores Copilot instructions in a user-level file\n const vscodeCopilot = join(home, '.vscode', 'copilot-instructions.md');\n if (existsSync(join(home, '.vscode'))) {\n targets.push({\n name: 'GitHub Copilot',\n globalRulesPath: vscodeCopilot,\n writeMode: 'append-section',\n });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityDir = join(home, '.antigravity');\n if (existsSync(antigravityDir)) {\n targets.push({\n name: 'Antigravity',\n globalRulesPath: join(antigravityDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n return targets;\n}\n","import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedTool } from './detect.js';\n\ninterface WriteResult {\n success: boolean;\n error?: string;\n}\n\nexport async function writeConfig(\n tool: DetectedTool,\n mcpConfig: { command: string; args: string[] }\n): Promise<WriteResult> {\n try {\n // Ensure directory exists\n mkdirSync(dirname(tool.configPath), { recursive: true });\n\n // Read existing config or start fresh\n let config: Record<string, any> = {};\n try {\n const raw = readFileSync(tool.configPath, 'utf-8');\n config = JSON.parse(raw);\n } catch {\n // File doesn't exist or invalid JSON — start fresh\n }\n\n // Ensure mcpServers key exists\n if (!config.mcpServers) {\n config.mcpServers = {};\n }\n\n // Don't overwrite existing levelup-log config\n if (config.mcpServers['levelup-log']) {\n return { success: true }; // Already configured\n }\n\n // Add levelup-log\n config.mcpServers['levelup-log'] = mcpConfig;\n\n // Write back\n writeFileSync(tool.configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedRulesTarget } from './detect.js';\n\n// ─── Rules Content ────────────────────────────────────────────────────────────\n// Injected into each platform's global rules file.\n// Keep short — this is appended to existing content.\n\nconst RULES_SECTION = `\n## LevelUp.log Achievement Tracking\n\nAfter completing any meaningful task, proactively call the \\`record_achievement\\` MCP tool.\n**Do not wait to be asked.**\n\nTrigger on: code written/fixed, deployment, tests, docs, ops, learning, life/health/finance tasks.\nSkip: casual Q&A, simple lookups, short explanations with no tangible output.\n\nWhen \\`time_minutes\\` is unclear, ask: \"大概花了多久?\"\nSet \\`self_reported=true\\` when user narrates a past event (\"I exercised this morning\").\n`;\n\nconst SECTION_MARKER = '## LevelUp.log Achievement Tracking';\n\n// ─── Writer ───────────────────────────────────────────────────────────────────\n\nexport interface RulesWriteResult {\n name: string;\n success: boolean;\n action: 'written' | 'already-present' | 'created';\n error?: string;\n}\n\nexport function writeRulesFile(target: DetectedRulesTarget): RulesWriteResult {\n const { name, globalRulesPath, writeMode } = target;\n\n try {\n // Ensure parent directory exists\n const dir = dirname(globalRulesPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Check if already written\n if (existsSync(globalRulesPath)) {\n const existing = readFileSync(globalRulesPath, 'utf-8');\n if (existing.includes(SECTION_MARKER)) {\n return { name, success: true, action: 'already-present' };\n }\n\n if (writeMode === 'overwrite-if-absent') {\n // File exists but no section — skip (don't want to stomp unrelated content)\n return { name, success: true, action: 'already-present' };\n }\n\n // append-section: add to end\n writeFileSync(globalRulesPath, existing.trimEnd() + '\\n' + RULES_SECTION, 'utf-8');\n return { name, success: true, action: 'written' };\n }\n\n // File doesn't exist — create it\n writeFileSync(globalRulesPath, RULES_SECTION.trimStart(), 'utf-8');\n return { name, success: true, action: 'created' };\n\n } catch (err) {\n return { name, success: false, action: 'written', error: String(err) };\n }\n}\n\nexport function writeAllRulesFiles(targets: DetectedRulesTarget[]): RulesWriteResult[] {\n return targets.map(writeRulesFile);\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n// ─── Skill Content ────────────────────────────────────────────────────────────\n// Agent Skills open standard: directory + SKILL.md with YAML frontmatter.\n// Compatible with Claude Code, Cursor, Gemini CLI, Codex CLI, Antigravity.\n//\n// Installed to ~/.claude/skills/levelup/SKILL.md\n// Invoked with: /levelup\n\nconst SKILL_CONTENT = `---\nname: LevelUp Achievement Coach\ndescription: Show your LevelUp.log stats dashboard (level, XP, streak, recent achievements, title progress) and activate full achievement coaching mode. Also proactively records achievements after any meaningful task during this session. Use at the start of a session or anytime you want a progress check.\n---\n\n## When activated, you should:\n\n1. **Call \\`get_my_stats\\`** — show level, XP, streak, active title\n2. **Call \\`get_recent\\`** — show last 7 days of achievements\n3. **Call \\`check_unlocks\\`** — show progress toward next titles\n4. Present this as a game dashboard, not a dry report\n\nExample output format:\n\\`\\`\\`\n⚔️ Lv.29 · Expert\n⚡ Total XP: 12,350 | Year XP: 4,520\n🔥 Streak: 15 days | 🏆 8 titles unlocked\n\nRecent achievements (7 days):\n 🪲 Bug Slayer +82 XP (fix)\n 🚀 Ship It +130 XP (deploy)\n 📝 Doc Wizard +45 XP (docs)\n\nNext title: Iron Body (health × 150) — you're at 87/150 💪\n\\`\\`\\`\n\n## Achievement tracking (always on)\n\nThroughout this session, **proactively call \\`record_achievement\\`** after any meaningful task.\nDon't wait to be asked. Don't ask for permission.\n\n| Trigger | Category | Example |\n|---------|----------|---------|\n| Code written / feature added | code | \"Built auth module\" |\n| Bug fixed | fix | \"Fixed race condition\" |\n| Deployed / published | deploy | \"Shipped v2.1.0 to prod\" |\n| Tests written | test | \"Added 23 unit tests\" |\n| Docs / README written | docs | \"Updated API docs\" |\n| Refactored code | refactor | \"Extracted shared utilities\" |\n| DevOps / CI done | ops | \"Set up GitHub Actions\" |\n| Learned something | learn | \"Studied Supabase RLS\" |\n| Life task done | life | \"Took kids to school\" |\n| Health activity | health | \"30min run\" |\n| Finance task | finance | \"Filed quarterly taxes\" |\n\n## Parameter guide\n\n\\`\\`\\`\ncomplexity:\n trivial → quick fix, single-line change\n normal → typical task, 30-60 min\n significant → multi-step, 1-3 hours\n major → large feature, half day+\n milestone → exceptional, major release\n\ntime_minutes: ask \"大概花了多久?\" if unclear\noutput_units: files changed / tests added / docs pages / tasks done\ninput_units: files read / docs consulted / searches done\nconversation_rounds: count of back-and-forth in this session\nself_reported: true if user narrates past event (\"I went for a run today\")\n\\`\\`\\`\n\n## Coaching behavior\n\n- Use game language: \"Quest complete!\", \"You defeated a bug!\", \"New skill unlocked!\"\n- When streak is active: mention it, encourage keeping it going\n- When user is frustrated/tired: acknowledge WHAT they did, not just \"you're doing great\"\n → \"You shipped X, fixed Y, and learned Z today — those all count.\"\n- Identity reinforcement: \"You're becoming someone who ships every day.\"\n- On birthday (if known): celebrate level-up with last year's stats\n`;\n\n// ─── Installer ────────────────────────────────────────────────────────────────\n\nexport interface SkillInstallResult {\n success: boolean;\n action: 'installed' | 'already-installed' | 'updated';\n path: string;\n error?: string;\n}\n\nexport function installSkill(): SkillInstallResult {\n // New format: directory-based skill (~/.claude/skills/levelup/SKILL.md)\n const skillDir = join(homedir(), '.claude', 'skills', 'levelup');\n const skillPath = join(skillDir, 'SKILL.md');\n\n // Migrate old flat file if it exists\n const legacyPath = join(homedir(), '.claude', 'skills', 'levelup.md');\n\n try {\n mkdirSync(skillDir, { recursive: true });\n\n // Remove legacy flat file if present\n if (existsSync(legacyPath)) {\n import('fs').then(({ unlinkSync }) => {\n try { unlinkSync(legacyPath); } catch { /* ignore */ }\n });\n }\n\n if (existsSync(skillPath)) {\n const existing = readFileSync(skillPath, 'utf-8');\n if (existing === SKILL_CONTENT) {\n return { success: true, action: 'already-installed', path: skillPath };\n }\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'updated', path: skillPath };\n }\n\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'installed', path: skillPath };\n\n } catch (err) {\n return { success: false, action: 'installed', path: skillPath, error: String(err) };\n }\n}\n","import { detectTools, detectRulesTargets } from \"./detect.js\";\nimport { writeConfig } from \"./write-config.js\";\nimport { writeAllRulesFiles } from \"./write-rules.js\";\nimport { installSkill } from \"./write-skill.js\";\nimport { existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { homedir, platform } from \"os\";\n\nconst MCP_CONFIG = {\n command: \"npx\",\n args: [\"-y\", \"@levelup-log/mcp-server@latest\", \"serve\"],\n};\n\n/** ChatGPT Desktop supports MCP on macOS (v1.2024.x+). Detect its config path. */\nfunction detectChatGptDesktop(): string | null {\n const home = homedir();\n const os = platform();\n\n const candidates =\n os === \"darwin\"\n ? [\n join(home, \"Library\", \"Application Support\", \"ChatGPT\", \"mcp.json\"),\n join(\n home,\n \"Library\",\n \"Application Support\",\n \"com.openai.ChatGPT\",\n \"mcp.json\",\n ),\n ]\n : os === \"win32\"\n ? [join(home, \"AppData\", \"Roaming\", \"ChatGPT\", \"mcp.json\")]\n : [];\n\n return candidates.find(existsSync) ?? null;\n}\n\nexport async function runInit() {\n console.log(\"\");\n console.log(\" ╔══════════════════════════════════════╗\");\n console.log(\" ║ LevelUp.log Setup Wizard ║\");\n console.log(\" ╚══════════════════════════════════════╝\");\n console.log(\"\");\n console.log(\" Detecting installed LLM tools...\");\n\n // ── Step 1: MCP config (all platforms) ──────────────────────────────────\n const tools = await detectTools();\n let mcpCount = 0;\n\n // Check ChatGPT Desktop separately (different config format handling)\n const chatGptPath = detectChatGptDesktop();\n if (chatGptPath) {\n tools.push({\n name: \"ChatGPT Desktop\",\n configPath: chatGptPath,\n type: \"json-mcpServers\",\n });\n }\n\n if (tools.length === 0) {\n console.log(\"\");\n console.log(\" No supported LLM tools detected. Manual config:\");\n console.log(\"\");\n console.log(\n JSON.stringify({ mcpServers: { \"levelup-log\": MCP_CONFIG } }, null, 2),\n );\n console.log(\"\");\n printManualInstructions();\n return;\n }\n\n console.log(` Found ${tools.length} tool(s):`);\n tools.forEach((t) => console.log(` ✓ ${t.name}`));\n console.log(\"\");\n\n for (const tool of tools) {\n const result = await writeConfig(tool, MCP_CONFIG);\n if (result.success) {\n console.log(` ✓ MCP configured: ${tool.name}`);\n mcpCount++;\n } else {\n console.log(` ✗ ${tool.name}: ${result.error}`);\n }\n }\n\n // ── Step 2: Global rules files (auto-trigger instructions) ──────────────\n console.log(\"\");\n console.log(\" Writing global rules (auto-trigger instructions)...\");\n console.log(\n \" These make the AI proactively record achievements in every session.\",\n );\n console.log(\"\");\n\n const rulesTargets = await detectRulesTargets();\n if (rulesTargets.length > 0) {\n const results = writeAllRulesFiles(rulesTargets);\n for (const r of results) {\n if (!r.success) {\n console.log(` ✗ ${r.name} rules: ${r.error}`);\n } else if (r.action === \"already-present\") {\n console.log(` ✓ ${r.name}: rules already installed`);\n } else {\n console.log(\n ` ✓ ${r.name}: rules ${r.action} → ${rulesTargets.find((t) => t.name === r.name)?.globalRulesPath}`,\n );\n }\n }\n } else {\n console.log(\" (no supported rules targets detected)\");\n }\n\n // ── Step 3: ChatGPT Desktop manual note ─────────────────────────────────\n if (chatGptPath) {\n console.log(\"\");\n console.log(\" ℹ ChatGPT Desktop detected.\");\n console.log(\" MCP config written. For auto-trigger, also paste into\");\n console.log(\" Settings → Personalization → Custom Instructions:\");\n console.log(\"\");\n console.log(\n ' \"When I complete any meaningful task, use the record_achievement',\n );\n console.log(\n \" MCP tool to log it as an achievement. Don't wait to be asked.\\\"\",\n );\n }\n\n // ── Step 4: Agent Skill (/levelup, cross-platform open standard) ────────\n console.log(\"\");\n console.log(\" Installing /levelup Agent Skill...\");\n const skillResult = installSkill();\n if (skillResult.success) {\n const label =\n skillResult.action === \"already-installed\"\n ? \"already installed\"\n : skillResult.action;\n console.log(` ✓ Skill ${label}: ${skillResult.path}`);\n console.log(\n \" Use /levelup in Claude Code, Cursor, Gemini CLI, Codex, Antigravity\",\n );\n console.log(\" to show stats + activate coach mode.\");\n } else {\n console.log(` ✗ Skill install failed: ${skillResult.error}`);\n }\n\n // ── Step 5: MCP server instructions note ─────────────────────────────────\n console.log(\"\");\n console.log(\" ✓ MCP server instructions active (Claude Desktop, Cursor,\");\n console.log(\" Windsurf, Antigravity auto-inject on every session).\");\n\n // ── Done ─────────────────────────────────────────────────────────────────\n console.log(\"\");\n if (mcpCount > 0) {\n console.log(\n ` Done! Installed in ${mcpCount} tool(s). Restart to activate.`,\n );\n console.log(\" On first use, you'll be prompted to sign in with Google.\");\n } else {\n console.log(\" Rules written. Please add MCP config manually (see above).\");\n }\n console.log(\"\");\n}\n\nfunction printManualInstructions() {\n console.log(\" ─── Manual Setup ───────────────────────────────────────────\");\n console.log(\" Add to your MCP tool's config (mcpServers section):\");\n console.log(\"\");\n console.log(' \"levelup-log\": {');\n console.log(' \"command\": \"npx\",');\n console.log(' \"args\": [\"-y\", \"@levelup-log/mcp-server@latest\", \"serve\"]');\n console.log(\" }\");\n console.log(\"\");\n console.log(\" For ChatGPT Desktop Custom Instructions, paste:\");\n console.log(' \"When I complete any meaningful task, use record_achievement');\n console.log(\" to log it. Don't wait to be asked.\\\"\");\n console.log(\" ────────────────────────────────────────────────────────────\");\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","mkdirSync","dirname","existsSync","readFileSync","writeFileSync","mkdirSync","join","homedir","existsSync","join","homedir","platform"]}
1
+ {"version":3,"sources":["../src/init/detect.ts","../src/init/write-config.ts","../src/init/write-rules.ts","../src/init/write-skill.ts","../src/init/index.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir, platform } from 'os';\n\nexport interface DetectedTool {\n name: string;\n configPath: string;\n type: 'json-mcpServers';\n}\n\nexport interface DetectedRulesTarget {\n name: string;\n /** Global rules file path — write once, applies to all projects */\n globalRulesPath: string;\n /** How to write: 'append-section' adds a ## section, 'overwrite-if-absent' only creates if missing */\n writeMode: 'append-section' | 'overwrite-if-absent';\n}\n\nexport async function detectTools(): Promise<DetectedTool[]> {\n const home = homedir();\n const os = platform();\n const tools: DetectedTool[] = [];\n\n // ── Claude Desktop ────────────────────────────────────────────────────────\n const claudeDesktopConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json')\n : join(home, '.config', 'claude', 'claude_desktop_config.json');\n\n if (existsSync(claudeDesktopConfig)) {\n tools.push({ name: 'Claude Desktop', configPath: claudeDesktopConfig, type: 'json-mcpServers' });\n }\n\n // ── Claude Code ───────────────────────────────────────────────────────────\n const claudeCodeConfig = join(home, '.claude', 'settings.json');\n const claudeCodeAlt = join(home, '.claude.json');\n if (existsSync(claudeCodeConfig)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeConfig, type: 'json-mcpServers' });\n } else if (existsSync(claudeCodeAlt)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeAlt, type: 'json-mcpServers' });\n }\n\n // ── Cursor ────────────────────────────────────────────────────────────────\n const cursorConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : join(home, '.config', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json');\n const cursorSimple = join(home, '.cursor', 'mcp.json');\n\n if (existsSync(cursorConfig)) {\n tools.push({ name: 'Cursor', configPath: cursorConfig, type: 'json-mcpServers' });\n } else if (existsSync(cursorSimple)) {\n tools.push({ name: 'Cursor', configPath: cursorSimple, type: 'json-mcpServers' });\n }\n\n // ── Windsurf / Codeium ────────────────────────────────────────────────────\n const windsurfConfig = join(home, '.codeium', 'windsurf', 'mcp_config.json');\n if (existsSync(windsurfConfig)) {\n tools.push({ name: 'Windsurf', configPath: windsurfConfig, type: 'json-mcpServers' });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityConfig = join(home, '.antigravity', 'mcp.json');\n const antigravityAlt = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Antigravity', 'mcp.json')\n : join(home, '.config', 'antigravity', 'mcp.json');\n\n if (existsSync(antigravityConfig)) {\n tools.push({ name: 'Antigravity', configPath: antigravityConfig, type: 'json-mcpServers' });\n } else if (existsSync(antigravityAlt)) {\n tools.push({ name: 'Antigravity', configPath: antigravityAlt, type: 'json-mcpServers' });\n }\n\n // ── Continue (VS Code extension) ──────────────────────────────────────────\n const continueConfig = join(home, '.continue', 'config.json');\n if (existsSync(continueConfig)) {\n // Continue uses a different config format — detected but handled separately in writeConfig\n tools.push({ name: 'Continue', configPath: continueConfig, type: 'json-mcpServers' });\n }\n\n return tools;\n}\n\nexport async function detectRulesTargets(): Promise<DetectedRulesTarget[]> {\n const home = homedir();\n const targets: DetectedRulesTarget[] = [];\n\n // ── Cursor: global rules (~/.cursor/rules) ────────────────────────────────\n // Applies to ALL Cursor projects automatically\n if (existsSync(join(home, '.cursor')) || existsSync(join(home, '.cursor', 'mcp.json'))) {\n targets.push({\n name: 'Cursor',\n globalRulesPath: join(home, '.cursor', 'rules'),\n writeMode: 'append-section',\n });\n }\n\n // ── Windsurf: global rules ────────────────────────────────────────────────\n const windsurfDir = join(home, '.codeium', 'windsurf');\n if (existsSync(windsurfDir)) {\n targets.push({\n name: 'Windsurf',\n globalRulesPath: join(windsurfDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── Claude Code: global CLAUDE.md ─────────────────────────────────────────\n const claudeDir = join(home, '.claude');\n if (existsSync(claudeDir)) {\n targets.push({\n name: 'Claude Code',\n globalRulesPath: join(claudeDir, 'CLAUDE.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── GitHub Copilot (VS Code): global instructions ─────────────────────────\n // VS Code stores Copilot instructions in a user-level file\n const vscodeCopilot = join(home, '.vscode', 'copilot-instructions.md');\n if (existsSync(join(home, '.vscode'))) {\n targets.push({\n name: 'GitHub Copilot',\n globalRulesPath: vscodeCopilot,\n writeMode: 'append-section',\n });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityDir = join(home, '.antigravity');\n if (existsSync(antigravityDir)) {\n targets.push({\n name: 'Antigravity',\n globalRulesPath: join(antigravityDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n return targets;\n}\n","import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedTool } from './detect.js';\n\ninterface WriteResult {\n success: boolean;\n error?: string;\n}\n\nexport async function writeConfig(\n tool: DetectedTool,\n mcpConfig: { command: string; args: string[] }\n): Promise<WriteResult> {\n try {\n // Ensure directory exists\n mkdirSync(dirname(tool.configPath), { recursive: true });\n\n // Read existing config or start fresh\n let config: Record<string, any> = {};\n try {\n const raw = readFileSync(tool.configPath, 'utf-8');\n config = JSON.parse(raw);\n } catch {\n // File doesn't exist or invalid JSON — start fresh\n }\n\n // Ensure mcpServers key exists\n if (!config.mcpServers) {\n config.mcpServers = {};\n }\n\n // Don't overwrite existing levelup-log config\n if (config.mcpServers['levelup-log']) {\n return { success: true }; // Already configured\n }\n\n // Add levelup-log\n config.mcpServers['levelup-log'] = mcpConfig;\n\n // Write back\n writeFileSync(tool.configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedRulesTarget } from './detect.js';\n\n// ─── Rules Content ────────────────────────────────────────────────────────────\n// Injected into each platform's global rules file.\n// Keep short — this is appended to existing content.\n\nconst RULES_SECTION = `\n## LevelUp.log Achievement Tracking\n\nAfter completing any meaningful task, proactively call the \\`record_achievement\\` MCP tool.\n**Do not wait to be asked.**\n\nTrigger on: code written/fixed, deployment, tests, docs, ops, learning, life/health/finance tasks.\nSkip: casual Q&A, simple lookups, short explanations with no tangible output.\n\nWhen \\`time_minutes\\` is unclear, ask: \"大概花了多久?\"\nSet \\`self_reported=true\\` when user narrates a past event (\"I exercised this morning\").\n`;\n\nconst SECTION_MARKER = '## LevelUp.log Achievement Tracking';\n\n// ─── Writer ───────────────────────────────────────────────────────────────────\n\nexport interface RulesWriteResult {\n name: string;\n success: boolean;\n action: 'written' | 'already-present' | 'created';\n error?: string;\n}\n\nexport function writeRulesFile(target: DetectedRulesTarget): RulesWriteResult {\n const { name, globalRulesPath, writeMode } = target;\n\n try {\n // Ensure parent directory exists\n const dir = dirname(globalRulesPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Check if already written\n if (existsSync(globalRulesPath)) {\n const existing = readFileSync(globalRulesPath, 'utf-8');\n if (existing.includes(SECTION_MARKER)) {\n return { name, success: true, action: 'already-present' };\n }\n\n if (writeMode === 'overwrite-if-absent') {\n // File exists but no section — skip (don't want to stomp unrelated content)\n return { name, success: true, action: 'already-present' };\n }\n\n // append-section: add to end\n writeFileSync(globalRulesPath, existing.trimEnd() + '\\n' + RULES_SECTION, 'utf-8');\n return { name, success: true, action: 'written' };\n }\n\n // File doesn't exist — create it\n writeFileSync(globalRulesPath, RULES_SECTION.trimStart(), 'utf-8');\n return { name, success: true, action: 'created' };\n\n } catch (err) {\n return { name, success: false, action: 'written', error: String(err) };\n }\n}\n\nexport function writeAllRulesFiles(targets: DetectedRulesTarget[]): RulesWriteResult[] {\n return targets.map(writeRulesFile);\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n// ─── Skill Content ────────────────────────────────────────────────────────────\n// Agent Skills open standard: directory + SKILL.md with YAML frontmatter.\n// Compatible with Claude Code, Cursor, Gemini CLI, Codex CLI, Antigravity.\n//\n// Installed to ~/.claude/skills/levelup/SKILL.md\n// Invoked with: /levelup\n\nconst SKILL_CONTENT = `---\nname: LevelUp Achievement Coach\ndescription: Show your LevelUp.log stats dashboard (level, XP, streak, recent achievements, title progress) and activate full achievement coaching mode. Also proactively records achievements after any meaningful task during this session. Use at the start of a session or anytime you want a progress check.\n---\n\n## When activated, you should:\n\n1. **Call \\`get_my_stats\\`** — show level, XP, streak, active title\n2. **Call \\`get_recent\\`** — show last 7 days of achievements\n3. **Call \\`check_unlocks\\`** — show progress toward next titles\n4. Present this as a game dashboard, not a dry report\n\nExample output format:\n\\`\\`\\`\n⚔️ Lv.29 · Expert\n⚡ Total XP: 12,350 | Year XP: 4,520\n🔥 Streak: 15 days | 🏆 8 titles unlocked\n\nRecent achievements (7 days):\n 🪲 Bug Slayer +82 XP (fix)\n 🚀 Ship It +130 XP (deploy)\n 📝 Doc Wizard +45 XP (docs)\n\nNext title: Iron Body (health × 150) — you're at 87/150 💪\n\\`\\`\\`\n\n## Achievement tracking (always on)\n\nThroughout this session, **proactively call \\`record_achievement\\`** after any meaningful task.\nDon't wait to be asked. Don't ask for permission.\n\n| Trigger | Category | Example |\n|---------|----------|---------|\n| Code written / feature added | code | \"Built auth module\" |\n| Bug fixed | fix | \"Fixed race condition\" |\n| Deployed / published | deploy | \"Shipped v2.1.0 to prod\" |\n| Tests written | test | \"Added 23 unit tests\" |\n| Docs / README written | docs | \"Updated API docs\" |\n| Refactored code | refactor | \"Extracted shared utilities\" |\n| DevOps / CI done | ops | \"Set up GitHub Actions\" |\n| Learned something | learn | \"Studied Supabase RLS\" |\n| Life task done | life | \"Took kids to school\" |\n| Health activity | health | \"30min run\" |\n| Finance task | finance | \"Filed quarterly taxes\" |\n\n## Parameter guide\n\n\\`\\`\\`\ncomplexity:\n trivial → quick fix, single-line change\n normal → typical task, 30-60 min\n significant → multi-step, 1-3 hours\n major → large feature, half day+\n milestone → exceptional, major release\n\ntime_minutes: ask \"大概花了多久?\" if unclear\noutput_units: files changed / tests added / docs pages / tasks done\ninput_units: files read / docs consulted / searches done\nconversation_rounds: count of back-and-forth in this session\nself_reported: true if user narrates past event (\"I went for a run today\")\n\\`\\`\\`\n\n## Coaching behavior\n\n- Use game language: \"Quest complete!\", \"You defeated a bug!\", \"New skill unlocked!\"\n- When streak is active: mention it, encourage keeping it going\n- When user is frustrated/tired: acknowledge WHAT they did, not just \"you're doing great\"\n → \"You shipped X, fixed Y, and learned Z today — those all count.\"\n- Identity reinforcement: \"You're becoming someone who ships every day.\"\n- On birthday (if known): celebrate level-up with last year's stats\n`;\n\n// ─── Installer ────────────────────────────────────────────────────────────────\n\nexport interface SkillInstallResult {\n success: boolean;\n action: 'installed' | 'already-installed' | 'updated';\n path: string;\n error?: string;\n}\n\nexport function installSkill(): SkillInstallResult {\n // New format: directory-based skill (~/.claude/skills/levelup/SKILL.md)\n const skillDir = join(homedir(), '.claude', 'skills', 'levelup');\n const skillPath = join(skillDir, 'SKILL.md');\n\n // Migrate old flat file if it exists\n const legacyPath = join(homedir(), '.claude', 'skills', 'levelup.md');\n\n try {\n mkdirSync(skillDir, { recursive: true });\n\n // Remove legacy flat file if present\n if (existsSync(legacyPath)) {\n import('fs').then(({ unlinkSync }) => {\n try { unlinkSync(legacyPath); } catch { /* ignore */ }\n });\n }\n\n if (existsSync(skillPath)) {\n const existing = readFileSync(skillPath, 'utf-8');\n if (existing === SKILL_CONTENT) {\n return { success: true, action: 'already-installed', path: skillPath };\n }\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'updated', path: skillPath };\n }\n\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'installed', path: skillPath };\n\n } catch (err) {\n return { success: false, action: 'installed', path: skillPath, error: String(err) };\n }\n}\n","import { detectTools, detectRulesTargets } from \"./detect.js\";\nimport { writeConfig } from \"./write-config.js\";\nimport { writeAllRulesFiles } from \"./write-rules.js\";\nimport { installSkill } from \"./write-skill.js\";\nimport { existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { homedir, platform } from \"os\";\n\nimport { createRequire } from \"module\";\nconst _require = createRequire(import.meta.url);\nconst _pkg = _require(\"../../package.json\") as { version: string };\nconst PINNED_VERSION = _pkg.version; // e.g. \"0.4.0\" — pinned at install time\n\nconst MCP_CONFIG = {\n command: \"npx\",\n args: [\"-y\", `@levelup-log/mcp-server@${PINNED_VERSION}`],\n};\n\n/** ChatGPT Desktop supports MCP on macOS (v1.2024.x+). Detect its config path. */\nfunction detectChatGptDesktop(): string | null {\n const home = homedir();\n const os = platform();\n\n const candidates =\n os === \"darwin\"\n ? [\n join(home, \"Library\", \"Application Support\", \"ChatGPT\", \"mcp.json\"),\n join(\n home,\n \"Library\",\n \"Application Support\",\n \"com.openai.ChatGPT\",\n \"mcp.json\",\n ),\n ]\n : os === \"win32\"\n ? [join(home, \"AppData\", \"Roaming\", \"ChatGPT\", \"mcp.json\")]\n : [];\n\n return candidates.find(existsSync) ?? null;\n}\n\nexport async function runInit() {\n console.log(\"\");\n console.log(\" ╔══════════════════════════════════════╗\");\n console.log(\" ║ LevelUp.log Setup Wizard ║\");\n console.log(\" ╚══════════════════════════════════════╝\");\n console.log(\"\");\n console.log(\" Detecting installed LLM tools...\");\n\n // ── Step 1: MCP config (all platforms) ──────────────────────────────────\n const tools = await detectTools();\n let mcpCount = 0;\n\n // Check ChatGPT Desktop separately (different config format handling)\n const chatGptPath = detectChatGptDesktop();\n if (chatGptPath) {\n tools.push({\n name: \"ChatGPT Desktop\",\n configPath: chatGptPath,\n type: \"json-mcpServers\",\n });\n }\n\n if (tools.length === 0) {\n console.log(\"\");\n console.log(\" No supported LLM tools detected. Manual config:\");\n console.log(\"\");\n console.log(\n JSON.stringify({ mcpServers: { \"levelup-log\": MCP_CONFIG } }, null, 2),\n );\n console.log(\"\");\n printManualInstructions();\n return;\n }\n\n console.log(` Found ${tools.length} tool(s):`);\n tools.forEach((t) => console.log(` ✓ ${t.name}`));\n console.log(\"\");\n\n for (const tool of tools) {\n const result = await writeConfig(tool, MCP_CONFIG);\n if (result.success) {\n console.log(` ✓ MCP configured: ${tool.name}`);\n mcpCount++;\n } else {\n console.log(` ✗ ${tool.name}: ${result.error}`);\n }\n }\n\n // ── Step 2: Global rules files (auto-trigger instructions) ──────────────\n console.log(\"\");\n console.log(\" Writing global rules (auto-trigger instructions)...\");\n console.log(\n \" These make the AI proactively record achievements in every session.\",\n );\n console.log(\"\");\n\n const rulesTargets = await detectRulesTargets();\n if (rulesTargets.length > 0) {\n const results = writeAllRulesFiles(rulesTargets);\n for (const r of results) {\n if (!r.success) {\n console.log(` ✗ ${r.name} rules: ${r.error}`);\n } else if (r.action === \"already-present\") {\n console.log(` ✓ ${r.name}: rules already installed`);\n } else {\n console.log(\n ` ✓ ${r.name}: rules ${r.action} → ${rulesTargets.find((t) => t.name === r.name)?.globalRulesPath}`,\n );\n }\n }\n } else {\n console.log(\" (no supported rules targets detected)\");\n }\n\n // ── Step 3: ChatGPT Desktop manual note ─────────────────────────────────\n if (chatGptPath) {\n console.log(\"\");\n console.log(\" ℹ ChatGPT Desktop detected.\");\n console.log(\" MCP config written. For auto-trigger, also paste into\");\n console.log(\" Settings → Personalization → Custom Instructions:\");\n console.log(\"\");\n console.log(\n ' \"When I complete any meaningful task, use the record_achievement',\n );\n console.log(\n \" MCP tool to log it as an achievement. Don't wait to be asked.\\\"\",\n );\n }\n\n // ── Step 4: Agent Skill (/levelup, cross-platform open standard) ────────\n console.log(\"\");\n console.log(\" Installing /levelup Agent Skill...\");\n const skillResult = installSkill();\n if (skillResult.success) {\n const label =\n skillResult.action === \"already-installed\"\n ? \"already installed\"\n : skillResult.action;\n console.log(` ✓ Skill ${label}: ${skillResult.path}`);\n console.log(\n \" Use /levelup in Claude Code, Cursor, Gemini CLI, Codex, Antigravity\",\n );\n console.log(\" to show stats + activate coach mode.\");\n } else {\n console.log(` ✗ Skill install failed: ${skillResult.error}`);\n }\n\n // ── Step 5: MCP server instructions note ─────────────────────────────────\n console.log(\"\");\n console.log(\" ✓ MCP server instructions active (Claude Desktop, Cursor,\");\n console.log(\" Windsurf, Antigravity auto-inject on every session).\");\n\n // ── Done ─────────────────────────────────────────────────────────────────\n console.log(\"\");\n if (mcpCount > 0) {\n console.log(\n ` Done! Installed in ${mcpCount} tool(s). Restart to activate.`,\n );\n console.log(\" On first use, you'll be prompted to sign in with Google.\");\n } else {\n console.log(\" Rules written. Please add MCP config manually (see above).\");\n }\n console.log(\"\");\n}\n\nfunction printManualInstructions() {\n console.log(\" ─── Manual Setup ───────────────────────────────────────────\");\n console.log(\" Add to your MCP tool's config (mcpServers section):\");\n console.log(\"\");\n console.log(' \"levelup-log\": {');\n console.log(' \"command\": \"npx\",');\n console.log(' \"args\": [\"-y\", \"@levelup-log/mcp-server@latest\", \"serve\"]');\n console.log(\" }\");\n console.log(\"\");\n console.log(\" For ChatGPT Desktop Custom Instructions, paste:\");\n console.log(' \"When I complete any meaningful task, use record_achievement');\n console.log(\" to log it. Don't wait to be asked.\\\"\");\n console.log(\" ────────────────────────────────────────────────────────────\");\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","mkdirSync","dirname","existsSync","readFileSync","writeFileSync","mkdirSync","join","homedir","existsSync","join","homedir","platform"]}
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  calculateXp,
4
4
  createServer
5
- } from "./chunk-JROVEQX3.js";
5
+ } from "./chunk-VJKF2CNS.js";
6
6
  import "./chunk-FII2XEJ7.js";
7
7
  export {
8
8
  calculateXp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levelup-log/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "MCP Server that turns your daily tasks into game-like achievements",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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"]}