@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scripts/heartbeat.ts"],"sourcesContent":["/**\n * LevelUp.log Bi-Weekly Heartbeat Script\n *\n * 每兩週在本機執行一次,負責:\n * 1. 雙週報(用戶數、成就數、XP、熱門類別)\n * 2. Streak 健康檢查\n * 3. 賽季管理(結束過期賽季、自動建立下一季)\n * 4. 稱號分布報告\n * 5. 年度快照觸發(1/1 才執行)\n * 6. 結果寫入 Obsidian Vault\n * 7. Google 日曆事件\n *\n * 使用方式:\n * LEVELUP_SERVICE_ROLE_KEY=xxx pnpm heartbeat\n * crontab(每兩週一的早上 9 點):\n * 0 9 * * 1 [ $(( $(date +\\%V) \\% 2 )) -eq 1 ] && LEVELUP_SERVICE_ROLE_KEY=xxx pnpm --filter mcp-server heartbeat\n */\n\nimport { createClient, type SupabaseClient } from \"@supabase/supabase-js\";\nimport { execSync } from \"node:child_process\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { CONFIG } from \"../utils/config.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype StatsResult = {\n totalUsers: number;\n activeThisWeek: number;\n weekAchievements: number;\n weekXp: number;\n topCategories: Array<{ cat: string; count: number }>;\n};\n\ntype StreakResult = {\n healthy: number;\n atRisk: number;\n over30: number;\n over7: number;\n};\n\ntype SeasonResult = {\n status: \"active\" | \"expired_fixed\" | \"created\" | \"upcoming\" | \"none\";\n name: string;\n daysLeft?: number;\n participants?: number;\n endsAt?: string;\n};\n\ntype TitleEntry = {\n icon: string | null;\n name: string;\n rarity: string;\n unlockCount: number;\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction hr(char = \"─\", width = 60) {\n console.log(char.repeat(width));\n}\n\nfunction section(title: string) {\n console.log();\n hr();\n console.log(` ${title}`);\n hr();\n}\n\nfunction pad(s: string | number, n: number) {\n return String(s).padEnd(n);\n}\n\nfunction nDaysAgo(n: number): string {\n const d = new Date();\n d.setDate(d.getDate() - n);\n return d.toISOString();\n}\n\nfunction formatDate(iso: string) {\n return new Date(iso).toLocaleDateString(\"zh-TW\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n });\n}\n\nfunction formatDatetime(iso: string) {\n return new Date(iso).toLocaleString(\"zh-TW\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/** ISO 8601 week number */\nfunction getISOWeek(date: Date): number {\n const d = new Date(\n Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),\n );\n const day = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - day);\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);\n}\n\n// ─── Task 1: Weekly Stats ─────────────────────────────────────────────────────\n\nasync function weeklyStats(db: SupabaseClient): Promise<StatsResult> {\n section(\"📊 雙週報(過去 14 天)\");\n\n const since = nDaysAgo(14);\n\n const { count: totalUsers } = await db\n .from(\"profiles\")\n .select(\"*\", { count: \"exact\", head: true });\n\n const { data: activeUsersData } = await db\n .from(\"achievements\")\n .select(\"user_id\")\n .gte(\"created_at\", since);\n\n const activeThisWeek = new Set(activeUsersData?.map((r) => r.user_id)).size;\n\n const { data: weekAchievements } = await db\n .from(\"achievements\")\n .select(\"xp, category\")\n .gte(\"created_at\", since);\n\n const weekCount = weekAchievements?.length ?? 0;\n const weekXp = weekAchievements?.reduce((sum, a) => sum + a.xp, 0) ?? 0;\n\n const catCounts: Record<string, number> = {};\n for (const a of weekAchievements ?? []) {\n catCounts[a.category] = (catCounts[a.category] ?? 0) + 1;\n }\n const topCategories = Object.entries(catCounts)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5)\n .map(([cat, count]) => ({ cat, count }));\n\n console.log(` 👥 總用戶數:${totalUsers ?? 0}`);\n console.log(` 🔥 雙週活躍:${activeThisWeek} 人`);\n console.log(` 🏅 雙週成就:${weekCount} 項`);\n console.log(` ⚡ 雙週 XP:${weekXp.toLocaleString()}`);\n\n if (topCategories.length > 0) {\n console.log();\n console.log(\" 本週熱門類別:\");\n for (const { cat, count } of topCategories) {\n const bar = \"█\".repeat(\n Math.round((count / (topCategories[0].count || 1)) * 20),\n );\n console.log(` ${pad(cat, 12)} ${pad(count, 4)} ${bar}`);\n }\n }\n\n return {\n totalUsers: totalUsers ?? 0,\n activeThisWeek,\n weekAchievements: weekCount,\n weekXp,\n topCategories,\n };\n}\n\n// ─── Task 2: Streak Health ────────────────────────────────────────────────────\n\nasync function streakHealth(db: SupabaseClient): Promise<StreakResult> {\n section(\"🔥 Streak 健康檢查\");\n\n const today = new Date().toISOString().split(\"T\")[0];\n const yesterday = new Date();\n yesterday.setDate(yesterday.getDate() - 1);\n const yesterdayStr = yesterday.toISOString().split(\"T\")[0];\n\n const { data: profiles } = await db\n .from(\"profiles\")\n .select(\"current_streak, last_active_date\")\n .gt(\"current_streak\", 0);\n\n if (!profiles || profiles.length === 0) {\n console.log(\" 目前無活躍 streak\");\n return { healthy: 0, atRisk: 0, over30: 0, over7: 0 };\n }\n\n let healthy = 0,\n atRisk = 0,\n over30 = 0,\n over7 = 0;\n\n for (const p of profiles) {\n const isHealthy =\n p.last_active_date === today || p.last_active_date === yesterdayStr;\n if (isHealthy) {\n healthy++;\n if (p.current_streak >= 30) over30++;\n else if (p.current_streak >= 7) over7++;\n } else {\n atRisk++;\n }\n }\n\n console.log(` ✅ 健康 streak:${healthy} 人`);\n console.log(` └── 30 天以上:${over30} 人`);\n console.log(` └── 7-29 天:${over7} 人`);\n if (atRisk > 0) {\n console.log(` ⚠️ streak 已中斷(待 cron 歸零):${atRisk} 人`);\n }\n\n return { healthy, atRisk, over30, over7 };\n}\n\n// ─── Task 3: Season Management ────────────────────────────────────────────────\n\nasync function endSeason(\n db: SupabaseClient,\n season: { id: string; name: string },\n) {\n const { data: participants } = await db\n .from(\"season_participants\")\n .select(\"id, season_xp\")\n .eq(\"season_id\", season.id)\n .order(\"season_xp\", { ascending: false });\n\n for (let i = 0; i < (participants ?? []).length; i++) {\n await db\n .from(\"season_participants\")\n .update({ final_rank: i + 1 })\n .eq(\"id\", participants![i].id);\n }\n\n await db.from(\"seasons\").update({ is_active: false }).eq(\"id\", season.id);\n console.log(\n ` ✅ 賽季「${season.name}」已結算,共 ${participants?.length ?? 0} 位`,\n );\n}\n\nasync function createNextSeason(\n db: SupabaseClient,\n prev: { name: string; ends_at: string } | null,\n) {\n const now = new Date();\n let seasonNum = 1;\n if (prev) {\n const match = prev.name.match(/S(\\d+)/);\n if (match) seasonNum = parseInt(match[1]) + 1;\n }\n\n const startsAt = prev ? new Date(prev.ends_at) : now;\n const endsAt = new Date(startsAt);\n endsAt.setDate(endsAt.getDate() + 90);\n\n const name = `S${String(seasonNum).padStart(2, \"0\")} - ${startsAt.getFullYear()} Q${Math.ceil((startsAt.getMonth() + 1) / 3)}`;\n\n const { data, error } = await db\n .from(\"seasons\")\n .insert({\n name,\n starts_at: startsAt.toISOString(),\n ends_at: endsAt.toISOString(),\n is_active: !prev,\n })\n .select()\n .single();\n\n if (error) {\n console.log(` ❌ 建立賽季失敗:${error.message}`);\n } else {\n console.log(` ✅ 已建立賽季「${data.name}」`);\n console.log(\n ` ${formatDate(data.starts_at)} → ${formatDate(data.ends_at)}`,\n );\n }\n}\n\nasync function seasonManagement(db: SupabaseClient): Promise<SeasonResult> {\n section(\"🏆 賽季管理\");\n\n const now = new Date();\n const { data: seasons } = await db\n .from(\"seasons\")\n .select(\"*\")\n .order(\"starts_at\", { ascending: false });\n\n if (!seasons || seasons.length === 0) {\n console.log(\" ⚠️ 尚無賽季資料,建立第一個賽季...\");\n await createNextSeason(db, null);\n return { status: \"created\", name: \"S01\" };\n }\n\n const active = seasons.find((s) => s.is_active);\n\n if (active) {\n const endsAt = new Date(active.ends_at);\n const daysLeft = Math.ceil(\n (endsAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),\n );\n\n if (daysLeft < 0) {\n console.log(\n ` ⏰ 賽季「${active.name}」已過期 ${-daysLeft} 天,正在結算...`,\n );\n await endSeason(db, active);\n await createNextSeason(db, active);\n return { status: \"expired_fixed\", name: active.name };\n }\n\n if (daysLeft <= 7) {\n console.log(\n ` ⚠️ 賽季「${active.name}」還有 ${daysLeft} 天結束(${formatDate(active.ends_at)})`,\n );\n const nextExists = seasons.some(\n (s) => !s.is_active && new Date(s.starts_at) > new Date(active.ends_at),\n );\n if (!nextExists) {\n console.log(\" ➕ 預先建立下一個賽季...\");\n await createNextSeason(db, active);\n }\n } else {\n console.log(` ✅ 賽季「${active.name}」進行中`);\n console.log(\n ` ${formatDate(active.starts_at)} → ${formatDate(active.ends_at)}(還有 ${daysLeft} 天)`,\n );\n }\n\n const { count: participants } = await db\n .from(\"season_participants\")\n .select(\"*\", { count: \"exact\", head: true })\n .eq(\"season_id\", active.id);\n\n console.log(` 參賽人數:${participants ?? 0} 人`);\n return {\n status: \"active\",\n name: active.name,\n daysLeft,\n participants: participants ?? 0,\n endsAt: active.ends_at,\n };\n }\n\n // 無活躍賽季\n const latest = seasons[0];\n if (latest && new Date(latest.starts_at) <= now) {\n console.log(` ▶️ 啟動賽季「${latest.name}」...`);\n await db.from(\"seasons\").update({ is_active: true }).eq(\"id\", latest.id);\n return { status: \"active\", name: latest.name };\n }\n\n console.log(\" ⚠️ 無活躍賽季\");\n if (latest)\n console.log(\n ` 「${latest.name}」將於 ${formatDatetime(latest.starts_at)} 開始`,\n );\n return { status: \"none\", name: \"\" };\n}\n\n// ─── Task 4: Title Distribution ───────────────────────────────────────────────\n\nasync function titleDistribution(db: SupabaseClient): Promise<TitleEntry[]> {\n section(\"🏅 稱號解鎖分布\");\n\n const { data: titles } = await db\n .from(\"title_definitions\")\n .select(\"id, name, rarity, icon\");\n\n if (!titles || titles.length === 0) {\n console.log(\" 尚無稱號定義\");\n return [];\n }\n\n const { data: unlocks } = await db.from(\"user_titles\").select(\"title_id\");\n\n const titleCounts: Record<string, number> = {};\n for (const u of unlocks ?? []) {\n titleCounts[u.title_id] = (titleCounts[u.title_id] ?? 0) + 1;\n }\n\n const rarityOrder = [\"common\", \"uncommon\", \"rare\", \"epic\", \"legendary\"];\n const sorted = [...titles].sort(\n (a, b) => rarityOrder.indexOf(a.rarity) - rarityOrder.indexOf(b.rarity),\n );\n\n const rarityEmoji: Record<string, string> = {\n common: \"⚪\",\n uncommon: \"🟢\",\n rare: \"🔵\",\n epic: \"🟣\",\n legendary: \"🟡\",\n };\n\n let currentRarity = \"\";\n for (const t of sorted) {\n if (t.rarity !== currentRarity) {\n currentRarity = t.rarity;\n console.log(`\\n ${rarityEmoji[t.rarity]} ${t.rarity.toUpperCase()}`);\n }\n const cnt = titleCounts[t.id] ?? 0;\n console.log(\n ` ${t.icon ?? \" \"} ${pad(t.name, 18)} ${pad(cnt, 4)} 人解鎖`,\n );\n }\n\n return sorted.map((t) => ({\n icon: t.icon,\n name: t.name,\n rarity: t.rarity,\n unlockCount: titleCounts[t.id] ?? 0,\n }));\n}\n\n// ─── Task 5: Yearly Snapshot ──────────────────────────────────────────────────\n\nasync function yearlySnapshotIfNeeded(db: SupabaseClient) {\n const now = new Date();\n if (now.getMonth() !== 0 || now.getDate() !== 1) return;\n\n section(\"📅 年度快照(1/1 觸發)\");\n const lastYear = now.getFullYear() - 1;\n\n const { data: profiles } = await db\n .from(\"profiles\")\n .select(\"id, birth_date, year_xp, longest_streak\");\n\n let ok = 0,\n err = 0;\n\n for (const p of profiles ?? []) {\n const ageLevel = p.birth_date\n ? Math.floor(\n (Date.now() - new Date(p.birth_date).getTime()) /\n (365.25 * 24 * 60 * 60 * 1000),\n )\n : 0;\n\n const { count: achievementsCount } = await db\n .from(\"achievements\")\n .select(\"*\", { count: \"exact\", head: true })\n .eq(\"user_id\", p.id)\n .gte(\"created_at\", `${lastYear}-01-01`)\n .lt(\"created_at\", `${lastYear + 1}-01-01`);\n\n const { data: catData } = await db\n .from(\"achievements\")\n .select(\"category\")\n .eq(\"user_id\", p.id)\n .gte(\"created_at\", `${lastYear}-01-01`)\n .lt(\"created_at\", `${lastYear + 1}-01-01`);\n\n const catCounts: Record<string, number> = {};\n for (const a of catData ?? [])\n catCounts[a.category] = (catCounts[a.category] ?? 0) + 1;\n const topCategories = Object.entries(catCounts)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3)\n .map(([category, count]) => ({ category, count }));\n\n const { count: titlesUnlocked } = await db\n .from(\"user_titles\")\n .select(\"*\", { count: \"exact\", head: true })\n .eq(\"user_id\", p.id);\n\n const { error } = await db.from(\"yearly_snapshots\").upsert(\n {\n user_id: p.id,\n year: lastYear,\n age_level: ageLevel,\n year_xp: p.year_xp,\n achievements_count: achievementsCount ?? 0,\n top_categories: topCategories,\n longest_streak: p.longest_streak,\n titles_unlocked: titlesUnlocked ?? 0,\n },\n { onConflict: \"user_id,year\", ignoreDuplicates: false },\n );\n if (error) err++;\n else ok++;\n }\n\n await db.from(\"profiles\").update({ year_xp: 0, achievements_this_month: 0 });\n console.log(` ✅ 年度快照:${ok} 人完成,${err} 個錯誤`);\n console.log(\" ✅ year_xp 已歸零\");\n}\n\n// ─── Task 6: Save to Obsidian ────────────────────────────────────────────────\n\nasync function saveToObsidian(\n stats: StatsResult,\n streak: StreakResult,\n season: SeasonResult,\n titles: TitleEntry[],\n) {\n const now = new Date();\n const year = now.getFullYear();\n const week = getISOWeek(now);\n const dateStr = now.toLocaleDateString(\"zh-TW\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n });\n\n const week2 = week + 1;\n const periodLabel = `${year}-W${String(week).padStart(2, \"0\")}-W${String(week2).padStart(2, \"0\")}`;\n\n const vaultDir = join(homedir(), \"Documents/tai\");\n const reportDir = join(vaultDir, \"專案/LevelUp.log/雙週報\");\n const fileName = `${periodLabel}.md`;\n const filePath = join(reportDir, fileName);\n\n // 稱號分布 markdown table\n const rarityOrder = [\"common\", \"uncommon\", \"rare\", \"epic\", \"legendary\"];\n const rarityLabel: Record<string, string> = {\n common: \"⚪ Common\",\n uncommon: \"🟢 Uncommon\",\n rare: \"🔵 Rare\",\n epic: \"🟣 Epic\",\n legendary: \"🟡 Legendary\",\n };\n const titlesByRarity = rarityOrder\n .map((r) => {\n const items = titles.filter((t) => t.rarity === r);\n if (items.length === 0) return \"\";\n const rows = items\n .map((t) => `| ${t.icon ?? \"—\"} | ${t.name} | ${t.unlockCount} |`)\n .join(\"\\n\");\n return `**${rarityLabel[r]}**\\n\\n| 圖示 | 稱號 | 解鎖人數 |\\n|------|------|----------|\\n${rows}`;\n })\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n // 熱門類別 markdown\n const catRows = stats.topCategories\n .map(({ cat, count }) => `| ${cat} | ${count} |`)\n .join(\"\\n\");\n const catTable =\n stats.topCategories.length > 0\n ? `| 類別 | 成就數 |\\n|------|--------|\\n${catRows}`\n : \"_本週無記錄_\";\n\n // 賽季狀態\n const seasonLine =\n season.status === \"active\"\n ? `${season.name},還有 ${season.daysLeft} 天(${season.participants ?? 0} 位參賽者)`\n : season.status === \"expired_fixed\"\n ? `${season.name} 已結算,新賽季已建立`\n : season.status === \"created\"\n ? \"已建立第一個賽季\"\n : \"無活躍賽季\";\n\n const md = `---\npublic: false\ndate: ${now.toISOString().split(\"T\")[0]}\ntags: [levelup-log, 雙週報]\n---\n\n# LevelUp.log 雙週報 ${periodLabel}\n\n> 產生時間:${dateStr}(涵蓋過去 14 天)\n\n## 📊 雙週數據\n\n| 指標 | 數值 |\n|------|------|\n| 總用戶數 | ${stats.totalUsers} |\n| 雙週活躍 | ${stats.activeThisWeek} 人 |\n| 雙週成就 | ${stats.weekAchievements} 項 |\n| 雙週 XP | ${stats.weekXp.toLocaleString()} |\n\n### 熱門類別\n\n${catTable}\n\n## 🔥 Streak 健康\n\n| 指標 | 數值 |\n|------|------|\n| 健康 streak | ${streak.healthy} 人 |\n| 30 天以上 | ${streak.over30} 人 |\n| 7-29 天 | ${streak.over7} 人 |\n| 已中斷(待歸零) | ${streak.atRisk} 人 |\n\n## 🏆 賽季\n\n${seasonLine}\n\n## 🏅 稱號解鎖分布\n\n${titlesByRarity || \"_無稱號資料_\"}\n`;\n\n // 建立目錄(若不存在)\n mkdirSync(reportDir, { recursive: true });\n writeFileSync(filePath, md, \"utf-8\");\n\n console.log();\n console.log(\n ` 📝 Obsidian 雙週報已儲存:專案/LevelUp.log/雙週報/${fileName}`,\n );\n}\n\n// ─── Task 7: Google Calendar Event ───────────────────────────────────────────\n\nfunction saveToCalendar(\n stats: StatsResult,\n season: SeasonResult,\n week: number,\n year: number,\n) {\n section(\"📅 Google 日曆\");\n\n // 確認 gog 是否可用\n try {\n execSync(\"which gog\", { stdio: \"ignore\" });\n } catch {\n console.log(\" ⚠️ gog 未安裝,略過日曆整合\");\n console.log(\" 安裝:brew install steipete/tap/gogcli\");\n return;\n }\n\n const now = new Date();\n const todayDate = now.toISOString().split(\"T\")[0];\n\n const w1 = String(week).padStart(2, \"0\");\n const w2 = String(week + 1).padStart(2, \"0\");\n const periodLabel = `${year}-W${w1}-W${w2}`;\n\n // 事件標題:雙週期間 + 關鍵數字\n const title = `LevelUp.log ${periodLabel} — ${stats.activeThisWeek} 活躍 · ${stats.weekAchievements} 成就 · ${stats.weekXp.toLocaleString()} XP`;\n\n // 事件開始 / 結束:今天 09:00–09:30(雙週報閱讀時間)\n const startTime = `${todayDate}T09:00`;\n const endTime = `${todayDate}T09:30`;\n\n // 事件描述(純文字)\n const topCatText = stats.topCategories\n .map(({ cat, count }) => ` ${cat}: ${count}`)\n .join(\"\\n\");\n\n const seasonText =\n season.status === \"active\"\n ? `${season.name},還有 ${season.daysLeft} 天`\n : season.name || \"無活躍賽季\";\n\n const description =\n `LevelUp.log 雙週報 ${periodLabel}\\n\\n` +\n `用戶:${stats.totalUsers} 活躍:${stats.activeThisWeek}\\n` +\n `成就:${stats.weekAchievements} XP:${stats.weekXp.toLocaleString()}\\n\\n` +\n `熱門類別:\\n${topCatText || \" (無)\"}\\n\\n` +\n `賽季:${seasonText}\\n\\n` +\n `Obsidian:專案/LevelUp.log/雙週報/${periodLabel}.md`;\n\n try {\n execSync(\n `gog calendar create \\\n --title ${JSON.stringify(title)} \\\n --start \"${startTime}\" \\\n --end \"${endTime}\" \\\n --description ${JSON.stringify(description)} \\\n --color 9`,\n { stdio: \"pipe\" },\n );\n console.log(` ✅ 日曆事件已建立:${title}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.log(` ❌ 日曆建立失敗:${msg.split(\"\\n\")[0]}`);\n }\n}\n\n// ─── Main ─────────────────────────────────────────────────────────────────────\n\nexport async function main() {\n const serviceRoleKey =\n process.env.LEVELUP_SERVICE_ROLE_KEY ||\n process.env.SUPABASE_SERVICE_ROLE_KEY;\n\n if (!serviceRoleKey) {\n console.error(\"❌ 需要 LEVELUP_SERVICE_ROLE_KEY 環境變數\");\n console.error(\" export LEVELUP_SERVICE_ROLE_KEY=your_service_role_key\");\n process.exit(1);\n }\n\n const db = createClient(CONFIG.SUPABASE_URL, serviceRoleKey, {\n auth: { persistSession: false },\n });\n\n const now = new Date();\n console.log();\n console.log(\"╔══════════════════════════════════════════════════════════╗\");\n console.log(\"║ LevelUp.log Weekly Heartbeat ║\");\n console.log(`║ ${now.toLocaleString(\"zh-TW\").padEnd(50)}║`);\n console.log(\"╚══════════════════════════════════════════════════════════╝\");\n\n const stats = await weeklyStats(db);\n const streak = await streakHealth(db);\n const season = await seasonManagement(db);\n const titles = await titleDistribution(db);\n\n await yearlySnapshotIfNeeded(db);\n await saveToObsidian(stats, streak, season, titles);\n saveToCalendar(stats, season, getISOWeek(now), now.getFullYear());\n\n section(\"✅ Heartbeat 完成\");\n console.log();\n}\n\n// 直接以 tsx 執行時自動跑 main\nconst isDirectRun =\n process.argv[1]?.endsWith(\"heartbeat.ts\") ||\n process.argv[1]?.endsWith(\"heartbeat.js\");\n\nif (isDirectRun) {\n main().catch((err) => {\n console.error(\"❌ Heartbeat 失敗:\", err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,408 @@
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
+ const antigravityConfig = join(home, ".antigravity", "mcp.json");
34
+ const antigravityAlt = os === "darwin" ? join(home, "Library", "Application Support", "Antigravity", "mcp.json") : join(home, ".config", "antigravity", "mcp.json");
35
+ if (existsSync(antigravityConfig)) {
36
+ tools.push({ name: "Antigravity", configPath: antigravityConfig, type: "json-mcpServers" });
37
+ } else if (existsSync(antigravityAlt)) {
38
+ tools.push({ name: "Antigravity", configPath: antigravityAlt, type: "json-mcpServers" });
39
+ }
40
+ const continueConfig = join(home, ".continue", "config.json");
41
+ if (existsSync(continueConfig)) {
42
+ tools.push({ name: "Continue", configPath: continueConfig, type: "json-mcpServers" });
43
+ }
44
+ return tools;
45
+ }
46
+ async function detectRulesTargets() {
47
+ const home = homedir();
48
+ const targets = [];
49
+ if (existsSync(join(home, ".cursor")) || existsSync(join(home, ".cursor", "mcp.json"))) {
50
+ targets.push({
51
+ name: "Cursor",
52
+ globalRulesPath: join(home, ".cursor", "rules"),
53
+ writeMode: "append-section"
54
+ });
55
+ }
56
+ const windsurfDir = join(home, ".codeium", "windsurf");
57
+ if (existsSync(windsurfDir)) {
58
+ targets.push({
59
+ name: "Windsurf",
60
+ globalRulesPath: join(windsurfDir, "rules.md"),
61
+ writeMode: "append-section"
62
+ });
63
+ }
64
+ const claudeDir = join(home, ".claude");
65
+ if (existsSync(claudeDir)) {
66
+ targets.push({
67
+ name: "Claude Code",
68
+ globalRulesPath: join(claudeDir, "CLAUDE.md"),
69
+ writeMode: "append-section"
70
+ });
71
+ }
72
+ const vscodeCopilot = join(home, ".vscode", "copilot-instructions.md");
73
+ if (existsSync(join(home, ".vscode"))) {
74
+ targets.push({
75
+ name: "GitHub Copilot",
76
+ globalRulesPath: vscodeCopilot,
77
+ writeMode: "append-section"
78
+ });
79
+ }
80
+ const antigravityDir = join(home, ".antigravity");
81
+ if (existsSync(antigravityDir)) {
82
+ targets.push({
83
+ name: "Antigravity",
84
+ globalRulesPath: join(antigravityDir, "rules.md"),
85
+ writeMode: "append-section"
86
+ });
87
+ }
88
+ return targets;
89
+ }
90
+
91
+ // src/init/write-config.ts
92
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
93
+ import { dirname } from "path";
94
+ async function writeConfig(tool, mcpConfig) {
95
+ try {
96
+ mkdirSync(dirname(tool.configPath), { recursive: true });
97
+ let config = {};
98
+ try {
99
+ const raw = readFileSync(tool.configPath, "utf-8");
100
+ config = JSON.parse(raw);
101
+ } catch {
102
+ }
103
+ if (!config.mcpServers) {
104
+ config.mcpServers = {};
105
+ }
106
+ if (config.mcpServers["levelup-log"]) {
107
+ return { success: true };
108
+ }
109
+ config.mcpServers["levelup-log"] = mcpConfig;
110
+ writeFileSync(tool.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
111
+ return { success: true };
112
+ } catch (error) {
113
+ return { success: false, error: error.message };
114
+ }
115
+ }
116
+
117
+ // src/init/write-rules.ts
118
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
119
+ import { dirname as dirname2 } from "path";
120
+ var RULES_SECTION = `
121
+ ## LevelUp.log Achievement Tracking
122
+
123
+ After completing any meaningful task, proactively call the \`record_achievement\` MCP tool.
124
+ **Do not wait to be asked.**
125
+
126
+ Trigger on: code written/fixed, deployment, tests, docs, ops, learning, life/health/finance tasks.
127
+ Skip: casual Q&A, simple lookups, short explanations with no tangible output.
128
+
129
+ When \`time_minutes\` is unclear, ask: "\u5927\u6982\u82B1\u4E86\u591A\u4E45\uFF1F"
130
+ Set \`self_reported=true\` when user narrates a past event ("I exercised this morning").
131
+ `;
132
+ var SECTION_MARKER = "## LevelUp.log Achievement Tracking";
133
+ function writeRulesFile(target) {
134
+ const { name, globalRulesPath, writeMode } = target;
135
+ try {
136
+ const dir = dirname2(globalRulesPath);
137
+ if (!existsSync2(dir)) {
138
+ mkdirSync2(dir, { recursive: true });
139
+ }
140
+ if (existsSync2(globalRulesPath)) {
141
+ const existing = readFileSync2(globalRulesPath, "utf-8");
142
+ if (existing.includes(SECTION_MARKER)) {
143
+ return { name, success: true, action: "already-present" };
144
+ }
145
+ if (writeMode === "overwrite-if-absent") {
146
+ return { name, success: true, action: "already-present" };
147
+ }
148
+ writeFileSync2(globalRulesPath, existing.trimEnd() + "\n" + RULES_SECTION, "utf-8");
149
+ return { name, success: true, action: "written" };
150
+ }
151
+ writeFileSync2(globalRulesPath, RULES_SECTION.trimStart(), "utf-8");
152
+ return { name, success: true, action: "created" };
153
+ } catch (err) {
154
+ return { name, success: false, action: "written", error: String(err) };
155
+ }
156
+ }
157
+ function writeAllRulesFiles(targets) {
158
+ return targets.map(writeRulesFile);
159
+ }
160
+
161
+ // src/init/write-skill.ts
162
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
163
+ import { join as join2 } from "path";
164
+ import { homedir as homedir2 } from "os";
165
+ var SKILL_CONTENT = `---
166
+ name: LevelUp Achievement Coach
167
+ description: 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.
168
+ ---
169
+
170
+ ## When activated, you should:
171
+
172
+ 1. **Call \`get_my_stats\`** \u2014 show level, XP, streak, active title
173
+ 2. **Call \`get_recent\`** \u2014 show last 7 days of achievements
174
+ 3. **Call \`check_unlocks\`** \u2014 show progress toward next titles
175
+ 4. Present this as a game dashboard, not a dry report
176
+
177
+ Example output format:
178
+ \`\`\`
179
+ \u2694\uFE0F Lv.29 \xB7 Expert
180
+ \u26A1 Total XP: 12,350 | Year XP: 4,520
181
+ \u{1F525} Streak: 15 days | \u{1F3C6} 8 titles unlocked
182
+
183
+ Recent achievements (7 days):
184
+ \u{1FAB2} Bug Slayer +82 XP (fix)
185
+ \u{1F680} Ship It +130 XP (deploy)
186
+ \u{1F4DD} Doc Wizard +45 XP (docs)
187
+
188
+ Next title: Iron Body (health \xD7 150) \u2014 you're at 87/150 \u{1F4AA}
189
+ \`\`\`
190
+
191
+ ## Achievement tracking (always on)
192
+
193
+ Throughout this session, **proactively call \`record_achievement\`** after any meaningful task.
194
+ Don't wait to be asked. Don't ask for permission.
195
+
196
+ | Trigger | Category | Example |
197
+ |---------|----------|---------|
198
+ | Code written / feature added | code | "Built auth module" |
199
+ | Bug fixed | fix | "Fixed race condition" |
200
+ | Deployed / published | deploy | "Shipped v2.1.0 to prod" |
201
+ | Tests written | test | "Added 23 unit tests" |
202
+ | Docs / README written | docs | "Updated API docs" |
203
+ | Refactored code | refactor | "Extracted shared utilities" |
204
+ | DevOps / CI done | ops | "Set up GitHub Actions" |
205
+ | Learned something | learn | "Studied Supabase RLS" |
206
+ | Life task done | life | "Took kids to school" |
207
+ | Health activity | health | "30min run" |
208
+ | Finance task | finance | "Filed quarterly taxes" |
209
+
210
+ ## Parameter guide
211
+
212
+ \`\`\`
213
+ complexity:
214
+ trivial \u2192 quick fix, single-line change
215
+ normal \u2192 typical task, 30-60 min
216
+ significant \u2192 multi-step, 1-3 hours
217
+ major \u2192 large feature, half day+
218
+ milestone \u2192 exceptional, major release
219
+
220
+ time_minutes: ask "\u5927\u6982\u82B1\u4E86\u591A\u4E45\uFF1F" if unclear
221
+ output_units: files changed / tests added / docs pages / tasks done
222
+ input_units: files read / docs consulted / searches done
223
+ conversation_rounds: count of back-and-forth in this session
224
+ self_reported: true if user narrates past event ("I went for a run today")
225
+ \`\`\`
226
+
227
+ ## Coaching behavior
228
+
229
+ - Use game language: "Quest complete!", "You defeated a bug!", "New skill unlocked!"
230
+ - When streak is active: mention it, encourage keeping it going
231
+ - When user is frustrated/tired: acknowledge WHAT they did, not just "you're doing great"
232
+ \u2192 "You shipped X, fixed Y, and learned Z today \u2014 those all count."
233
+ - Identity reinforcement: "You're becoming someone who ships every day."
234
+ - On birthday (if known): celebrate level-up with last year's stats
235
+ `;
236
+ function installSkill() {
237
+ const skillDir = join2(homedir2(), ".claude", "skills", "levelup");
238
+ const skillPath = join2(skillDir, "SKILL.md");
239
+ const legacyPath = join2(homedir2(), ".claude", "skills", "levelup.md");
240
+ try {
241
+ mkdirSync3(skillDir, { recursive: true });
242
+ if (existsSync3(legacyPath)) {
243
+ import("fs").then(({ unlinkSync }) => {
244
+ try {
245
+ unlinkSync(legacyPath);
246
+ } catch {
247
+ }
248
+ });
249
+ }
250
+ if (existsSync3(skillPath)) {
251
+ const existing = readFileSync3(skillPath, "utf-8");
252
+ if (existing === SKILL_CONTENT) {
253
+ return { success: true, action: "already-installed", path: skillPath };
254
+ }
255
+ writeFileSync3(skillPath, SKILL_CONTENT, "utf-8");
256
+ return { success: true, action: "updated", path: skillPath };
257
+ }
258
+ writeFileSync3(skillPath, SKILL_CONTENT, "utf-8");
259
+ return { success: true, action: "installed", path: skillPath };
260
+ } catch (err) {
261
+ return { success: false, action: "installed", path: skillPath, error: String(err) };
262
+ }
263
+ }
264
+
265
+ // src/init/index.ts
266
+ import { existsSync as existsSync4 } from "fs";
267
+ import { join as join3 } from "path";
268
+ import { homedir as homedir3, platform as platform2 } from "os";
269
+ var MCP_CONFIG = {
270
+ command: "npx",
271
+ args: ["-y", "@levelup-log/mcp-server@latest", "serve"]
272
+ };
273
+ function detectChatGptDesktop() {
274
+ const home = homedir3();
275
+ const os = platform2();
276
+ const candidates = os === "darwin" ? [
277
+ join3(home, "Library", "Application Support", "ChatGPT", "mcp.json"),
278
+ join3(
279
+ home,
280
+ "Library",
281
+ "Application Support",
282
+ "com.openai.ChatGPT",
283
+ "mcp.json"
284
+ )
285
+ ] : os === "win32" ? [join3(home, "AppData", "Roaming", "ChatGPT", "mcp.json")] : [];
286
+ return candidates.find(existsSync4) ?? null;
287
+ }
288
+ async function runInit() {
289
+ console.log("");
290
+ console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
291
+ console.log(" \u2551 LevelUp.log Setup Wizard \u2551");
292
+ console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
293
+ console.log("");
294
+ console.log(" Detecting installed LLM tools...");
295
+ const tools = await detectTools();
296
+ let mcpCount = 0;
297
+ const chatGptPath = detectChatGptDesktop();
298
+ if (chatGptPath) {
299
+ tools.push({
300
+ name: "ChatGPT Desktop",
301
+ configPath: chatGptPath,
302
+ type: "json-mcpServers"
303
+ });
304
+ }
305
+ if (tools.length === 0) {
306
+ console.log("");
307
+ console.log(" No supported LLM tools detected. Manual config:");
308
+ console.log("");
309
+ console.log(
310
+ JSON.stringify({ mcpServers: { "levelup-log": MCP_CONFIG } }, null, 2)
311
+ );
312
+ console.log("");
313
+ printManualInstructions();
314
+ return;
315
+ }
316
+ console.log(` Found ${tools.length} tool(s):`);
317
+ tools.forEach((t) => console.log(` \u2713 ${t.name}`));
318
+ console.log("");
319
+ for (const tool of tools) {
320
+ const result = await writeConfig(tool, MCP_CONFIG);
321
+ if (result.success) {
322
+ console.log(` \u2713 MCP configured: ${tool.name}`);
323
+ mcpCount++;
324
+ } else {
325
+ console.log(` \u2717 ${tool.name}: ${result.error}`);
326
+ }
327
+ }
328
+ console.log("");
329
+ console.log(" Writing global rules (auto-trigger instructions)...");
330
+ console.log(
331
+ " These make the AI proactively record achievements in every session."
332
+ );
333
+ console.log("");
334
+ const rulesTargets = await detectRulesTargets();
335
+ if (rulesTargets.length > 0) {
336
+ const results = writeAllRulesFiles(rulesTargets);
337
+ for (const r of results) {
338
+ if (!r.success) {
339
+ console.log(` \u2717 ${r.name} rules: ${r.error}`);
340
+ } else if (r.action === "already-present") {
341
+ console.log(` \u2713 ${r.name}: rules already installed`);
342
+ } else {
343
+ console.log(
344
+ ` \u2713 ${r.name}: rules ${r.action} \u2192 ${rulesTargets.find((t) => t.name === r.name)?.globalRulesPath}`
345
+ );
346
+ }
347
+ }
348
+ } else {
349
+ console.log(" (no supported rules targets detected)");
350
+ }
351
+ if (chatGptPath) {
352
+ console.log("");
353
+ console.log(" \u2139 ChatGPT Desktop detected.");
354
+ console.log(" MCP config written. For auto-trigger, also paste into");
355
+ console.log(" Settings \u2192 Personalization \u2192 Custom Instructions:");
356
+ console.log("");
357
+ console.log(
358
+ ' "When I complete any meaningful task, use the record_achievement'
359
+ );
360
+ console.log(
361
+ ` MCP tool to log it as an achievement. Don't wait to be asked."`
362
+ );
363
+ }
364
+ console.log("");
365
+ console.log(" Installing /levelup Agent Skill...");
366
+ const skillResult = installSkill();
367
+ if (skillResult.success) {
368
+ const label = skillResult.action === "already-installed" ? "already installed" : skillResult.action;
369
+ console.log(` \u2713 Skill ${label}: ${skillResult.path}`);
370
+ console.log(
371
+ " Use /levelup in Claude Code, Cursor, Gemini CLI, Codex, Antigravity"
372
+ );
373
+ console.log(" to show stats + activate coach mode.");
374
+ } else {
375
+ console.log(` \u2717 Skill install failed: ${skillResult.error}`);
376
+ }
377
+ console.log("");
378
+ console.log(" \u2713 MCP server instructions active (Claude Desktop, Cursor,");
379
+ console.log(" Windsurf, Antigravity auto-inject on every session).");
380
+ console.log("");
381
+ if (mcpCount > 0) {
382
+ console.log(
383
+ ` Done! Installed in ${mcpCount} tool(s). Restart to activate.`
384
+ );
385
+ console.log(" On first use, you'll be prompted to sign in with Google.");
386
+ } else {
387
+ console.log(" Rules written. Please add MCP config manually (see above).");
388
+ }
389
+ console.log("");
390
+ }
391
+ function printManualInstructions() {
392
+ console.log(" \u2500\u2500\u2500 Manual Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
393
+ console.log(" Add to your MCP tool's config (mcpServers section):");
394
+ console.log("");
395
+ console.log(' "levelup-log": {');
396
+ console.log(' "command": "npx",');
397
+ console.log(' "args": ["-y", "@levelup-log/mcp-server@latest", "serve"]');
398
+ console.log(" }");
399
+ console.log("");
400
+ console.log(" For ChatGPT Desktop Custom Instructions, paste:");
401
+ console.log(' "When I complete any meaningful task, use record_achievement');
402
+ console.log(` to log it. Don't wait to be asked."`);
403
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
404
+ }
405
+ export {
406
+ runInit
407
+ };
408
+ //# sourceMappingURL=init-GKLMB6BS.js.map
@@ -0,0 +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"]}