@levelup-log/mcp-server 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.d.ts CHANGED
@@ -1,5 +1,154 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
 
3
+ declare const CATEGORY_DEFINITIONS: {
4
+ readonly code: {
5
+ readonly label: "Coding";
6
+ readonly emoji: "💻";
7
+ readonly description: "寫程式、功能開發、新增功能";
8
+ readonly output_unit: "檔案/模組(小函式=1, 大檔=5-10, 多組件=15)";
9
+ readonly input_unit: "檔案閱讀 / API 文件查閱";
10
+ readonly xp_weight: 1;
11
+ };
12
+ readonly fix: {
13
+ readonly label: "Bug Fix";
14
+ readonly emoji: "🪲";
15
+ readonly description: "修 bug、修復錯誤行為";
16
+ readonly output_unit: "修復的 bug 數(含測試驗證 = +1)";
17
+ readonly input_unit: "錯誤日誌 / 程式碼追蹤";
18
+ readonly xp_weight: 1.1;
19
+ };
20
+ readonly deploy: {
21
+ readonly label: "Deploy";
22
+ readonly emoji: "🚀";
23
+ readonly description: "部署上線、發布版本、推送 npm / App Store";
24
+ readonly output_unit: "部署目標數(1 服務/環境 = 1)";
25
+ readonly input_unit: "設定檔 / CI logs 查閱";
26
+ readonly xp_weight: 1.3;
27
+ };
28
+ readonly test: {
29
+ readonly label: "Testing";
30
+ readonly emoji: "🧪";
31
+ readonly description: "寫測試、E2E、TDD";
32
+ readonly output_unit: "測試案例數(1 test case = 1)";
33
+ readonly input_unit: "待測程式碼閱讀";
34
+ readonly xp_weight: 1;
35
+ };
36
+ readonly docs: {
37
+ readonly label: "Documentation";
38
+ readonly emoji: "📝";
39
+ readonly description: "寫文件、README、API 說明";
40
+ readonly output_unit: "頁數 / 章節數(1 完整段落 = 1)";
41
+ readonly input_unit: "參考資料 / 現有文件";
42
+ readonly xp_weight: 0.9;
43
+ };
44
+ readonly refactor: {
45
+ readonly label: "Refactor";
46
+ readonly emoji: "🔧";
47
+ readonly description: "重構、改善程式碼品質";
48
+ readonly output_unit: "重構的模組/函式數";
49
+ readonly input_unit: "閱讀現有程式碼";
50
+ readonly xp_weight: 1;
51
+ };
52
+ readonly review: {
53
+ readonly label: "Code Review";
54
+ readonly emoji: "👁";
55
+ readonly description: "Code review、PR 審查";
56
+ readonly output_unit: "審查的 PR / 檔案數";
57
+ readonly input_unit: "閱讀的程式碼(每 100 行 = 1)";
58
+ readonly xp_weight: 0.9;
59
+ };
60
+ readonly ops: {
61
+ readonly label: "DevOps / Ops";
62
+ readonly emoji: "⚙️";
63
+ readonly description: "維運、基礎設施、監控、CI/CD";
64
+ readonly output_unit: "設定/腳本/服務數(1 完成項目 = 1)";
65
+ readonly input_unit: "日誌 / 文件查閱";
66
+ readonly xp_weight: 1.2;
67
+ };
68
+ readonly learn: {
69
+ readonly label: "Learning";
70
+ readonly emoji: "📚";
71
+ readonly description: "學習新知、閱讀技術文章、上課、語言學習";
72
+ readonly output_unit: "概念/章節/練習題(掌握 1 個新概念 = 1)";
73
+ readonly input_unit: "閱讀篇數 / 影片數";
74
+ readonly xp_weight: 1;
75
+ };
76
+ readonly milestone: {
77
+ readonly label: "Milestone";
78
+ readonly emoji: "🏆";
79
+ readonly description: "里程碑、重大達成、特殊紀念";
80
+ readonly output_unit: "里程碑數(通常 = 1)";
81
+ readonly input_unit: "準備工作 / 研究";
82
+ readonly xp_weight: 1.5;
83
+ };
84
+ readonly life: {
85
+ readonly label: "Life";
86
+ readonly emoji: "🏠";
87
+ readonly description: "日常生活:家務、跑腿、育兒、採購、行政";
88
+ readonly output_unit: "完成的事項(跑腿=1, 帶小孩出門=2)";
89
+ readonly input_unit: "計畫 / 研究(查路線、比價等)";
90
+ readonly xp_weight: 0.9;
91
+ };
92
+ readonly health: {
93
+ readonly label: "Health";
94
+ readonly emoji: "💪";
95
+ readonly description: "運動、飲食管理、就醫、健康習慣";
96
+ readonly output_unit: "30 分鐘運動塊 或 公里數 或 完成療程";
97
+ readonly input_unit: "查健康資料 / 追蹤記錄";
98
+ readonly xp_weight: 1.1;
99
+ };
100
+ readonly finance: {
101
+ readonly label: "Finance";
102
+ readonly emoji: "💰";
103
+ readonly description: "記帳、投資、理財規劃、報稅、保險";
104
+ readonly output_unit: "完成的財務任務(1 項目 = 1)";
105
+ readonly input_unit: "研究報告 / 資料查閱";
106
+ readonly xp_weight: 1;
107
+ };
108
+ readonly social: {
109
+ readonly label: "Social";
110
+ readonly emoji: "🤝";
111
+ readonly description: "社交、幫助他人、聚會、社群貢獻";
112
+ readonly output_unit: "互動的人數 或 完成的社交任務";
113
+ readonly input_unit: "準備 / 背景研究";
114
+ readonly xp_weight: 0.9;
115
+ };
116
+ readonly creative: {
117
+ readonly label: "Creative";
118
+ readonly emoji: "🎨";
119
+ readonly description: "創作:寫文章、設計、音樂、影片、攝影";
120
+ readonly output_unit: "完成品(文章=3, 圖=1, 短影片=2)";
121
+ readonly input_unit: "參考資料 / 素材研究";
122
+ readonly xp_weight: 1;
123
+ };
124
+ readonly spiritual: {
125
+ readonly label: "Spiritual";
126
+ readonly emoji: "🔮";
127
+ readonly description: "身心靈:冥想、占卜、祈禱、儀式、能量練習、內在探索";
128
+ readonly output_unit: "完成的練習(1 次冥想=1, 占卜解讀=2)";
129
+ readonly input_unit: "學習 / 研究靈性資料";
130
+ readonly xp_weight: 0.85;
131
+ };
132
+ readonly hobby: {
133
+ readonly label: "Hobby";
134
+ readonly emoji: "🎮";
135
+ readonly description: "興趣娛樂:打電動、看電影/書、收藏、桌遊、園藝、烹飪";
136
+ readonly output_unit: "完成的活動段(1 小時電玩=1, 完成一本書=5)";
137
+ readonly input_unit: "查資料 / 攻略研究";
138
+ readonly xp_weight: 0.8;
139
+ };
140
+ };
141
+
142
+ type Complexity = "trivial" | "normal" | "significant" | "major" | "milestone";
143
+ declare function calculateXp(params: {
144
+ category: keyof typeof CATEGORY_DEFINITIONS;
145
+ complexity: Complexity;
146
+ time_minutes?: number;
147
+ output_units?: number;
148
+ input_units?: number;
149
+ conversation_rounds?: number;
150
+ self_reported?: boolean;
151
+ }): number;
3
152
  declare function createServer(): McpServer;
4
153
 
5
- export { createServer };
154
+ export { calculateXp, createServer };
package/dist/server.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ calculateXp,
3
4
  createServer
4
- } from "./chunk-4GGNGOIO.js";
5
+ } from "./chunk-VJKF2CNS.js";
6
+ import "./chunk-FII2XEJ7.js";
5
7
  export {
8
+ calculateXp,
6
9
  createServer
7
10
  };
8
11
  //# sourceMappingURL=server.js.map
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@levelup-log/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "MCP Server that turns your daily tasks into game-like achievements",
5
5
  "type": "module",
6
6
  "bin": {
7
- "levelup-log": "./dist/cli.js"
7
+ "levelup-log": "dist/cli.js"
8
8
  },
9
9
  "main": "./dist/server.js",
10
10
  "files": [
@@ -14,6 +14,17 @@
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  },
17
+ "scripts": {
18
+ "dev": "tsx watch src/cli.ts serve",
19
+ "build": "tsup",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "lint": "tsc --noEmit",
23
+ "clean": "rm -rf dist",
24
+ "inspect": "npx @modelcontextprotocol/inspector tsx src/cli.ts serve",
25
+ "heartbeat": "tsx src/scripts/heartbeat.ts",
26
+ "prepublishOnly": "pnpm build"
27
+ },
17
28
  "dependencies": {
18
29
  "@modelcontextprotocol/sdk": "^1.27.0",
19
30
  "@supabase/supabase-js": "^2.49.0",
@@ -37,19 +48,10 @@
37
48
  "license": "MIT",
38
49
  "repository": {
39
50
  "type": "git",
40
- "url": "https://github.com/lgscvb/levelup-log.git",
51
+ "url": "git+https://github.com/lgscvb/levelup-log.git",
41
52
  "directory": "packages/mcp-server"
42
53
  },
43
54
  "engines": {
44
55
  "node": ">=20"
45
- },
46
- "scripts": {
47
- "dev": "tsx watch src/cli.ts serve",
48
- "build": "tsup",
49
- "test": "vitest run",
50
- "test:watch": "vitest",
51
- "lint": "tsc --noEmit",
52
- "clean": "rm -rf dist",
53
- "inspect": "npx @modelcontextprotocol/inspector tsx src/cli.ts serve"
54
56
  }
55
- }
57
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server.ts","../src/utils/config.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 { ACHIEVEMENT_CATEGORIES } 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\nexport function createServer() {\n const server = new McpServer({\n name: \"levelup-log\",\n version: \"0.1.0\",\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 in this conversation — coding tasks, bug fixes, features, refactors, life tasks, learning, health, creative work, or any other productive activity.\n\nWHEN TO CALL: Whenever the conversation involved real effort and produced a tangible output. Don't wait to be asked — if the user finished something, record it.\nDO NOT CALL for: simple questions answered, casual chat, trivial lookups with no output.\n\nXP IS CALCULATED FROM complexity + time_minutes — do NOT guess xp yourself:\n complexity base: trivial=10, normal=30, significant=75, major=150, milestone=300\n time multiplier: <15min→×0.7, 15-60min→×1.0, 1-3hr→×1.3, 3hr+→×1.6\n Example: significant task (75) × 1.3 (1-2hr) = ~98 XP\n\nIF UNSURE about time spent, ASK the user before calling: \"大概花了你多久?\" — this ensures fair XP across different agents and sessions.\n\nCategories: ${ACHIEVEMENT_CATEGORIES.join(\", \")}.\nKeep descriptions abstract — no real company 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 \"Task complexity: trivial=quick lookup, normal=typical task, significant=multi-step work, major=large feature, milestone=exceptional achievement\",\n ),\n time_minutes: z\n .number()\n .min(1)\n .optional()\n .describe(\n \"Estimated minutes spent. If unknown, ask the user before recording.\",\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 tags,\n is_public,\n }) => {\n // Calculate XP from complexity + time (consistent across agents)\n const baseXp: Record<string, number> = {\n trivial: 10,\n normal: 30,\n significant: 75,\n major: 150,\n milestone: 300,\n };\n const minutes = time_minutes ?? 30; // default 30 min if not provided\n const timeMult =\n minutes < 15 ? 0.7 : minutes < 60 ? 1.0 : minutes < 180 ? 1.3 : 1.6;\n const xp = Math.min(\n 500,\n Math.max(5, Math.round(baseXp[complexity] * timeMult)),\n );\n // Local rate limit check\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 const result = await apiPost(\"record-achievement\", {\n category,\n title,\n description,\n xp,\n complexity,\n time_minutes,\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 log(\"record_achievement\", { category, title, xp });\n\n const data = result.data as Record<string, unknown>;\n const stats = data.stats as Record<string, unknown> | undefined;\n\n const lines = [\n `Achievement recorded! +${xp} 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 ].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","export const CONFIG = {\n SUPABASE_URL:\n process.env.LEVELUP_SUPABASE_URL ||\n \"https://hkuvfhfwbhkjmeqvwrxy.supabase.co\",\n SUPABASE_ANON_KEY:\n process.env.LEVELUP_SUPABASE_ANON_KEY ||\n \"sb_publishable_RilZivOWm3FIs6ueIA67Fw_07ZKjoEY\",\n AUTH_PORT: parseInt(process.env.LEVELUP_AUTH_PORT || \"19876\", 10),\n DEBUG: process.env.LEVELUP_DEBUG === \"true\",\n} as const;\n\nexport const ACHIEVEMENT_CATEGORIES = [\n \"code\",\n \"fix\",\n \"deploy\",\n \"test\",\n \"docs\",\n \"refactor\",\n \"review\",\n \"learn\",\n \"ops\",\n \"milestone\",\n \"life\",\n \"health\",\n \"finance\",\n \"social\",\n \"creative\",\n] as const;\n\nexport type AchievementCategory = (typeof ACHIEVEMENT_CATEGORIES)[number];\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"]}
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/init/detect.ts
4
- import { existsSync } from "fs";
5
- import { join } from "path";
6
- import { homedir, platform } from "os";
7
- async function detectTools() {
8
- const home = homedir();
9
- const os = platform();
10
- const tools = [];
11
- const claudeDesktopConfig = os === "darwin" ? join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : os === "win32" ? join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json") : join(home, ".config", "claude", "claude_desktop_config.json");
12
- if (existsSync(claudeDesktopConfig)) {
13
- tools.push({ name: "Claude Desktop", configPath: claudeDesktopConfig, type: "json-mcpServers" });
14
- }
15
- const claudeCodeConfig = join(home, ".claude", "settings.json");
16
- const claudeCodeAlt = join(home, ".claude.json");
17
- if (existsSync(claudeCodeConfig)) {
18
- tools.push({ name: "Claude Code", configPath: claudeCodeConfig, type: "json-mcpServers" });
19
- } else if (existsSync(claudeCodeAlt)) {
20
- tools.push({ name: "Claude Code", configPath: claudeCodeAlt, type: "json-mcpServers" });
21
- }
22
- const cursorConfig = os === "darwin" ? join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json") : os === "win32" ? join(home, "AppData", "Roaming", "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json") : join(home, ".config", "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json");
23
- const cursorSimple = join(home, ".cursor", "mcp.json");
24
- if (existsSync(cursorConfig)) {
25
- tools.push({ name: "Cursor", configPath: cursorConfig, type: "json-mcpServers" });
26
- } else if (existsSync(cursorSimple)) {
27
- tools.push({ name: "Cursor", configPath: cursorSimple, type: "json-mcpServers" });
28
- }
29
- const windsurfConfig = join(home, ".codeium", "windsurf", "mcp_config.json");
30
- if (existsSync(windsurfConfig)) {
31
- tools.push({ name: "Windsurf", configPath: windsurfConfig, type: "json-mcpServers" });
32
- }
33
- return tools;
34
- }
35
-
36
- // src/init/write-config.ts
37
- import { readFileSync, writeFileSync, mkdirSync } from "fs";
38
- import { dirname } from "path";
39
- async function writeConfig(tool, mcpConfig) {
40
- try {
41
- mkdirSync(dirname(tool.configPath), { recursive: true });
42
- let config = {};
43
- try {
44
- const raw = readFileSync(tool.configPath, "utf-8");
45
- config = JSON.parse(raw);
46
- } catch {
47
- }
48
- if (!config.mcpServers) {
49
- config.mcpServers = {};
50
- }
51
- if (config.mcpServers["levelup-log"]) {
52
- return { success: true };
53
- }
54
- config.mcpServers["levelup-log"] = mcpConfig;
55
- writeFileSync(tool.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
56
- return { success: true };
57
- } catch (error) {
58
- return { success: false, error: error.message };
59
- }
60
- }
61
-
62
- // src/init/index.ts
63
- var MCP_CONFIG = {
64
- command: "npx",
65
- args: ["-y", "@levelup-log/mcp-server@latest", "serve"]
66
- };
67
- async function runInit() {
68
- console.log("");
69
- console.log(" LevelUp.log Setup Wizard");
70
- console.log(" ========================");
71
- console.log("");
72
- console.log(" Detecting installed LLM tools...");
73
- const tools = await detectTools();
74
- if (tools.length === 0) {
75
- console.log("");
76
- console.log(" No supported LLM tools detected.");
77
- console.log(" You can manually add LevelUp.log to your MCP config:");
78
- console.log("");
79
- console.log(JSON.stringify({ mcpServers: { "levelup-log": MCP_CONFIG } }, null, 2));
80
- return;
81
- }
82
- console.log(` Found ${tools.length} tool(s):`);
83
- tools.forEach((t) => console.log(` \u2713 ${t.name}`));
84
- console.log("");
85
- let successCount = 0;
86
- for (const tool of tools) {
87
- const result = await writeConfig(tool, MCP_CONFIG);
88
- if (result.success) {
89
- console.log(` \u2713 Configured ${tool.name}`);
90
- successCount++;
91
- } else {
92
- console.log(` \u2717 ${tool.name}: ${result.error}`);
93
- }
94
- }
95
- console.log("");
96
- if (successCount > 0) {
97
- console.log(` Done! LevelUp.log installed in ${successCount} tool(s).`);
98
- console.log(" Restart your LLM tool to activate.");
99
- console.log("");
100
- console.log(" On first use, you'll be prompted to sign in with Google.");
101
- } else {
102
- console.log(" No tools were configured. Please add manually.");
103
- }
104
- console.log("");
105
- }
106
- export {
107
- runInit
108
- };
109
- //# sourceMappingURL=init-GNFWFY5S.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/init/detect.ts","../src/init/write-config.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'; // All use the same format\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 (global settings)\n const claudeCodeConfig = join(home, '.claude', 'settings.json');\n // Claude Code uses ~/.claude.json or ~/.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\n // Also check simpler Cursor config path\n const cursorSimple = join(home, '.cursor', 'mcp.json');\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 return tools;\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 { detectTools } from './detect.js';\nimport { writeConfig } from './write-config.js';\n\nconst MCP_CONFIG = {\n command: 'npx',\n args: ['-y', '@levelup-log/mcp-server@latest', 'serve'],\n};\n\nexport async function runInit() {\n console.log('');\n console.log(' LevelUp.log Setup Wizard');\n console.log(' ========================');\n console.log('');\n\n // Step 1: Detect installed LLM tools\n console.log(' Detecting installed LLM tools...');\n const tools = await detectTools();\n\n if (tools.length === 0) {\n console.log('');\n console.log(' No supported LLM tools detected.');\n console.log(' You can manually add LevelUp.log to your MCP config:');\n console.log('');\n console.log(JSON.stringify({ mcpServers: { 'levelup-log': MCP_CONFIG } }, null, 2));\n return;\n }\n\n console.log(` Found ${tools.length} tool(s):`);\n tools.forEach(t => console.log(` \\u2713 ${t.name}`));\n console.log('');\n\n // Step 2: Write config to each detected tool\n let successCount = 0;\n for (const tool of tools) {\n const result = await writeConfig(tool, MCP_CONFIG);\n if (result.success) {\n console.log(` \\u2713 Configured ${tool.name}`);\n successCount++;\n } else {\n console.log(` \\u2717 ${tool.name}: ${result.error}`);\n }\n }\n\n console.log('');\n if (successCount > 0) {\n console.log(` Done! LevelUp.log installed in ${successCount} tool(s).`);\n console.log(' Restart your LLM tool to activate.');\n console.log('');\n console.log(' On first use, you\\'ll be prompted to sign in with Google.');\n } else {\n console.log(' No tools were configured. Please add manually.');\n }\n console.log('');\n}\n"],"mappings":"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","names":[]}