@levelup-log/mcp-server 0.1.0 → 0.2.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.
|
@@ -324,21 +324,64 @@ function createServer2() {
|
|
|
324
324
|
"record_achievement",
|
|
325
325
|
{
|
|
326
326
|
title: "Record Achievement",
|
|
327
|
-
description: `
|
|
327
|
+
description: `Proactively call this when the user completes meaningful work in this conversation \u2014 coding tasks, bug fixes, features, refactors, life tasks, learning, health, creative work, or any other productive activity.
|
|
328
|
+
|
|
329
|
+
WHEN TO CALL: Whenever the conversation involved real effort and produced a tangible output. Don't wait to be asked \u2014 if the user finished something, record it.
|
|
330
|
+
DO NOT CALL for: simple questions answered, casual chat, trivial lookups with no output.
|
|
331
|
+
|
|
332
|
+
XP IS CALCULATED FROM complexity + time_minutes \u2014 do NOT guess xp yourself:
|
|
333
|
+
complexity base: trivial=10, normal=30, significant=75, major=150, milestone=300
|
|
334
|
+
time multiplier: <15min\u2192\xD70.7, 15-60min\u2192\xD71.0, 1-3hr\u2192\xD71.3, 3hr+\u2192\xD71.6
|
|
335
|
+
Example: significant task (75) \xD7 1.3 (1-2hr) = ~98 XP
|
|
336
|
+
|
|
337
|
+
IF UNSURE about time spent, ASK the user before calling: "\u5927\u6982\u82B1\u4E86\u4F60\u591A\u4E45\uFF1F" \u2014 this ensures fair XP across different agents and sessions.
|
|
338
|
+
|
|
339
|
+
Categories: ${ACHIEVEMENT_CATEGORIES.join(", ")}.
|
|
340
|
+
Keep descriptions abstract \u2014 no real company names, client names, or source code.`,
|
|
328
341
|
inputSchema: {
|
|
329
342
|
category: z.enum(ACHIEVEMENT_CATEGORIES),
|
|
330
|
-
title: z.string().describe(
|
|
343
|
+
title: z.string().describe(
|
|
344
|
+
'Game-style achievement title (e.g. "Bug Slayer", "Morning Warrior")'
|
|
345
|
+
),
|
|
331
346
|
description: z.string().describe("What was accomplished, in abstract terms (no PII)"),
|
|
332
|
-
|
|
347
|
+
complexity: z.enum(["trivial", "normal", "significant", "major", "milestone"]).describe(
|
|
348
|
+
"Task complexity: trivial=quick lookup, normal=typical task, significant=multi-step work, major=large feature, milestone=exceptional achievement"
|
|
349
|
+
),
|
|
350
|
+
time_minutes: z.number().min(1).optional().describe(
|
|
351
|
+
"Estimated minutes spent. If unknown, ask the user before recording."
|
|
352
|
+
),
|
|
333
353
|
tags: z.array(z.string()).optional().describe("Optional tags for filtering"),
|
|
334
354
|
is_public: z.boolean().optional().default(true).describe("Whether this appears on public feed")
|
|
335
355
|
}
|
|
336
356
|
},
|
|
337
|
-
async ({
|
|
357
|
+
async ({
|
|
358
|
+
category,
|
|
359
|
+
title,
|
|
360
|
+
description,
|
|
361
|
+
complexity,
|
|
362
|
+
time_minutes,
|
|
363
|
+
tags,
|
|
364
|
+
is_public
|
|
365
|
+
}) => {
|
|
366
|
+
const baseXp = {
|
|
367
|
+
trivial: 10,
|
|
368
|
+
normal: 30,
|
|
369
|
+
significant: 75,
|
|
370
|
+
major: 150,
|
|
371
|
+
milestone: 300
|
|
372
|
+
};
|
|
373
|
+
const minutes = time_minutes ?? 30;
|
|
374
|
+
const timeMult = minutes < 15 ? 0.7 : minutes < 60 ? 1 : minutes < 180 ? 1.3 : 1.6;
|
|
375
|
+
const xp = Math.min(
|
|
376
|
+
500,
|
|
377
|
+
Math.max(5, Math.round(baseXp[complexity] * timeMult))
|
|
378
|
+
);
|
|
338
379
|
const rateCheck = checkRateLimit(category);
|
|
339
380
|
if (!rateCheck.allowed) {
|
|
340
381
|
return {
|
|
341
|
-
content: [
|
|
382
|
+
content: [
|
|
383
|
+
{ type: "text", text: `Rate limited: ${rateCheck.reason}` }
|
|
384
|
+
],
|
|
342
385
|
isError: true
|
|
343
386
|
};
|
|
344
387
|
}
|
|
@@ -347,13 +390,17 @@ function createServer2() {
|
|
|
347
390
|
title,
|
|
348
391
|
description,
|
|
349
392
|
xp,
|
|
393
|
+
complexity,
|
|
394
|
+
time_minutes,
|
|
350
395
|
tags,
|
|
351
396
|
is_public,
|
|
352
397
|
source_platform: "claude-code"
|
|
353
398
|
});
|
|
354
399
|
if (result.error) {
|
|
355
400
|
return {
|
|
356
|
-
content: [
|
|
401
|
+
content: [
|
|
402
|
+
{ type: "text", text: `Failed to record: ${result.error}` }
|
|
403
|
+
],
|
|
357
404
|
isError: true
|
|
358
405
|
};
|
|
359
406
|
}
|
|
@@ -501,4 +548,4 @@ export {
|
|
|
501
548
|
logError,
|
|
502
549
|
createServer2 as createServer
|
|
503
550
|
};
|
|
504
|
-
//# sourceMappingURL=chunk-
|
|
551
|
+
//# sourceMappingURL=chunk-4GGNGOIO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
package/dist/cli.js
CHANGED
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/utils/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: `Record a daily achievement. Use this to log what the user accomplished — work tasks, life events, learning, health, creative projects, etc. The description should be abstracted (no real company names, client names, or code). Categories: ${ACHIEVEMENT_CATEGORIES.join(', ')}. XP range: 5-500 (5=trivial, 25=normal, 50=significant, 100=major, 200+=milestone).`,\n inputSchema: {\n category: z.enum(ACHIEVEMENT_CATEGORIES),\n title: z.string().describe('Game-style achievement title (e.g. \"Bug Slayer\", \"Morning Warrior\")'),\n description: z.string().describe('What was accomplished, in abstract terms (no PII)'),\n xp: z.number().min(5).max(500).describe('Experience points (5-500)'),\n tags: z.array(z.string()).optional().describe('Optional tags for filtering'),\n is_public: z.boolean().optional().default(true).describe('Whether this appears on public feed'),\n },\n },\n async ({ category, title, description, xp, tags, is_public }) => {\n // Local rate limit check\n const rateCheck = checkRateLimit(category);\n if (!rateCheck.allowed) {\n return {\n content: [{ type: 'text', text: `Rate limited: ${rateCheck.reason}` }],\n isError: true,\n };\n }\n\n const result = await apiPost('record-achievement', {\n category,\n title,\n description,\n xp,\n tags,\n is_public,\n source_platform: 'claude-code',\n });\n\n if (result.error) {\n return {\n content: [{ type: 'text', text: `Failed to record: ${result.error}` }],\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: '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: '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: '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: '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.enum(['season', 'month', 'all_time']).optional().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: '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"]}
|