@levelup-log/mcp-server 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/dist/{heartbeat-A4ZMVGSV.js → heartbeat-7CQ4JW6Y.js} +61 -5
- package/dist/heartbeat-7CQ4JW6Y.js.map +1 -0
- package/dist/{init-GKLMB6BS.js → init-S6MGBOW7.js} +6 -2
- package/dist/{init-GKLMB6BS.js.map → init-S6MGBOW7.js.map} +1 -1
- package/package.json +1 -1
- package/dist/heartbeat-A4ZMVGSV.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -15,11 +15,11 @@ async function serve() {
|
|
|
15
15
|
console.error("LevelUp.log MCP server running on stdio");
|
|
16
16
|
}
|
|
17
17
|
async function init() {
|
|
18
|
-
const { runInit } = await import("./init-
|
|
18
|
+
const { runInit } = await import("./init-S6MGBOW7.js");
|
|
19
19
|
await runInit();
|
|
20
20
|
}
|
|
21
21
|
async function heartbeat() {
|
|
22
|
-
const { main: main2 } = await import("./heartbeat-
|
|
22
|
+
const { main: main2 } = await import("./heartbeat-7CQ4JW6Y.js");
|
|
23
23
|
await main2();
|
|
24
24
|
}
|
|
25
25
|
async function main() {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
// src/scripts/heartbeat.ts
|
|
7
7
|
import { createClient } from "@supabase/supabase-js";
|
|
8
8
|
import { execSync } from "child_process";
|
|
9
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
9
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
10
|
import { homedir } from "os";
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
function hr(char = "\u2500", width = 60) {
|
|
@@ -296,7 +296,7 @@ async function yearlySnapshotIfNeeded(db) {
|
|
|
296
296
|
console.log(` \u2705 \u5E74\u5EA6\u5FEB\u7167\uFF1A${ok} \u4EBA\u5B8C\u6210\uFF0C${err} \u500B\u932F\u8AA4`);
|
|
297
297
|
console.log(" \u2705 year_xp \u5DF2\u6B78\u96F6");
|
|
298
298
|
}
|
|
299
|
-
async function saveToObsidian(stats, streak, season, titles) {
|
|
299
|
+
async function saveToObsidian(stats, streak, season, titles, version) {
|
|
300
300
|
const now = /* @__PURE__ */ new Date();
|
|
301
301
|
const year = now.getFullYear();
|
|
302
302
|
const week = getISOWeek(now);
|
|
@@ -373,7 +373,14 @@ ${seasonLine}
|
|
|
373
373
|
## \u{1F3C5} \u7A31\u865F\u89E3\u9396\u5206\u5E03
|
|
374
374
|
|
|
375
375
|
${titlesByRarity || "_\u7121\u7A31\u865F\u8CC7\u6599_"}
|
|
376
|
-
|
|
376
|
+
${version?.checked ? `
|
|
377
|
+
## \u{1F4E6} \u7248\u672C
|
|
378
|
+
|
|
379
|
+
${version.upToDate ? `\u2705 \u6700\u65B0\u7248 ${version.current}` : `\u26A0\uFE0F \u6709\u66F4\u65B0\uFF1A${version.current} \u2192 ${version.latest}
|
|
380
|
+
\`\`\`
|
|
381
|
+
npx @levelup-log/mcp-server@latest init
|
|
382
|
+
\`\`\``}
|
|
383
|
+
` : ""}`;
|
|
377
384
|
mkdirSync(reportDir, { recursive: true });
|
|
378
385
|
writeFileSync(filePath, md, "utf-8");
|
|
379
386
|
console.log();
|
|
@@ -381,6 +388,48 @@ ${titlesByRarity || "_\u7121\u7A31\u865F\u8CC7\u6599_"}
|
|
|
381
388
|
` \u{1F4DD} Obsidian \u96D9\u9031\u5831\u5DF2\u5132\u5B58\uFF1A\u5C08\u6848/LevelUp.log/\u96D9\u9031\u5831/${fileName}`
|
|
382
389
|
);
|
|
383
390
|
}
|
|
391
|
+
var VERSION_CHECK_STATE = join(homedir(), ".levelup", "version-check.json");
|
|
392
|
+
var VERSION_CHECK_INTERVAL_DAYS = 30;
|
|
393
|
+
function checkVersionIfNeeded() {
|
|
394
|
+
let current = "unknown";
|
|
395
|
+
try {
|
|
396
|
+
const pkgPath = join(
|
|
397
|
+
new URL(".", import.meta.url).pathname,
|
|
398
|
+
"../../../package.json"
|
|
399
|
+
);
|
|
400
|
+
current = JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
401
|
+
} catch {
|
|
402
|
+
}
|
|
403
|
+
let lastChecked = null;
|
|
404
|
+
try {
|
|
405
|
+
lastChecked = JSON.parse(readFileSync(VERSION_CHECK_STATE, "utf8")).lastChecked;
|
|
406
|
+
} catch {
|
|
407
|
+
}
|
|
408
|
+
if (lastChecked) {
|
|
409
|
+
const daysSince = (Date.now() - new Date(lastChecked).getTime()) / 864e5;
|
|
410
|
+
if (daysSince < VERSION_CHECK_INTERVAL_DAYS) {
|
|
411
|
+
return { current, latest: current, upToDate: true, checked: false };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
let latest = "unknown";
|
|
415
|
+
try {
|
|
416
|
+
latest = execSync("npm view @levelup-log/mcp-server version", {
|
|
417
|
+
encoding: "utf8",
|
|
418
|
+
timeout: 1e4
|
|
419
|
+
}).trim();
|
|
420
|
+
writeFileSync(
|
|
421
|
+
VERSION_CHECK_STATE,
|
|
422
|
+
JSON.stringify({
|
|
423
|
+
lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
|
|
424
|
+
latestSeen: latest
|
|
425
|
+
}),
|
|
426
|
+
"utf8"
|
|
427
|
+
);
|
|
428
|
+
} catch {
|
|
429
|
+
return { current, latest: "unknown", upToDate: true, checked: false };
|
|
430
|
+
}
|
|
431
|
+
return { current, latest, upToDate: latest === current, checked: true };
|
|
432
|
+
}
|
|
384
433
|
function saveToCalendar(stats, season, week, year) {
|
|
385
434
|
section("\u{1F4C5} Google \u65E5\u66C6");
|
|
386
435
|
try {
|
|
@@ -442,8 +491,15 @@ async function main() {
|
|
|
442
491
|
const streak = await streakHealth(db);
|
|
443
492
|
const season = await seasonManagement(db);
|
|
444
493
|
const titles = await titleDistribution(db);
|
|
494
|
+
const version = checkVersionIfNeeded();
|
|
495
|
+
if (version.checked && !version.upToDate) {
|
|
496
|
+
section("\u26A0\uFE0F \u7248\u672C\u66F4\u65B0\u53EF\u7528");
|
|
497
|
+
console.log(` \u76EE\u524D\u7248\u672C\uFF1A${version.current}`);
|
|
498
|
+
console.log(` \u6700\u65B0\u7248\u672C\uFF1A${version.latest}`);
|
|
499
|
+
console.log(` \u57F7\u884C npx @levelup-log/mcp-server@latest init \u66F4\u65B0`);
|
|
500
|
+
}
|
|
445
501
|
await yearlySnapshotIfNeeded(db);
|
|
446
|
-
await saveToObsidian(stats, streak, season, titles);
|
|
502
|
+
await saveToObsidian(stats, streak, season, titles, version);
|
|
447
503
|
saveToCalendar(stats, season, getISOWeek(now), now.getFullYear());
|
|
448
504
|
section("\u2705 Heartbeat \u5B8C\u6210");
|
|
449
505
|
console.log();
|
|
@@ -458,4 +514,4 @@ if (isDirectRun) {
|
|
|
458
514
|
export {
|
|
459
515
|
main
|
|
460
516
|
};
|
|
461
|
-
//# sourceMappingURL=heartbeat-
|
|
517
|
+
//# sourceMappingURL=heartbeat-7CQ4JW6Y.js.map
|
|
@@ -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 { existsSync, mkdirSync, readFileSync, 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 version?: VersionCheckResult,\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 version?.checked\n ? `\\n## 📦 版本\\n\\n${version.upToDate ? `✅ 最新版 ${version.current}` : `⚠️ 有更新:${version.current} → ${version.latest}\\n\\`\\`\\`\\nnpx @levelup-log/mcp-server@latest init\\n\\`\\`\\``}\\n`\n : \"\"\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: Version Check (every 30 days) ────────────────────────────────────\n\ntype VersionCheckResult = {\n current: string;\n latest: string;\n upToDate: boolean;\n checked: boolean; // false = skipped (< 30 days since last check)\n};\n\nconst VERSION_CHECK_STATE = join(homedir(), \".levelup\", \"version-check.json\");\nconst VERSION_CHECK_INTERVAL_DAYS = 30;\n\nfunction checkVersionIfNeeded(): VersionCheckResult {\n // Read current version from package.json (bundled alongside this script)\n let current = \"unknown\";\n try {\n const pkgPath = join(\n new URL(\".\", import.meta.url).pathname,\n \"../../../package.json\",\n );\n current = JSON.parse(readFileSync(pkgPath, \"utf8\")).version as string;\n } catch {\n /* ignore */\n }\n\n // Load last check state\n let lastChecked: string | null = null;\n try {\n lastChecked = JSON.parse(readFileSync(VERSION_CHECK_STATE, \"utf8\"))\n .lastChecked as string;\n } catch {\n /* first run */\n }\n\n // Skip if checked recently\n if (lastChecked) {\n const daysSince =\n (Date.now() - new Date(lastChecked).getTime()) / 86_400_000;\n if (daysSince < VERSION_CHECK_INTERVAL_DAYS) {\n return { current, latest: current, upToDate: true, checked: false };\n }\n }\n\n // Query npm\n let latest = \"unknown\";\n try {\n latest = execSync(\"npm view @levelup-log/mcp-server version\", {\n encoding: \"utf8\",\n timeout: 10_000,\n }).trim();\n writeFileSync(\n VERSION_CHECK_STATE,\n JSON.stringify({\n lastChecked: new Date().toISOString(),\n latestSeen: latest,\n }),\n \"utf8\",\n );\n } catch {\n return { current, latest: \"unknown\", upToDate: true, checked: false };\n }\n\n return { current, latest, upToDate: latest === current, checked: true };\n}\n\n// ─── Task 8: 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 const version = checkVersionIfNeeded();\n\n if (version.checked && !version.upToDate) {\n section(\"⚠️ 版本更新可用\");\n console.log(` 目前版本:${version.current}`);\n console.log(` 最新版本:${version.latest}`);\n console.log(` 執行 npx @levelup-log/mcp-server@latest init 更新`);\n }\n\n await yearlySnapshotIfNeeded(db);\n await saveToObsidian(stats, streak, season, titles, version);\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":[]}
|
|
@@ -266,9 +266,13 @@ function installSkill() {
|
|
|
266
266
|
import { existsSync as existsSync4 } from "fs";
|
|
267
267
|
import { join as join3 } from "path";
|
|
268
268
|
import { homedir as homedir3, platform as platform2 } from "os";
|
|
269
|
+
import { createRequire } from "module";
|
|
270
|
+
var _require = createRequire(import.meta.url);
|
|
271
|
+
var _pkg = _require("../../package.json");
|
|
272
|
+
var PINNED_VERSION = _pkg.version;
|
|
269
273
|
var MCP_CONFIG = {
|
|
270
274
|
command: "npx",
|
|
271
|
-
args: ["-y",
|
|
275
|
+
args: ["-y", `@levelup-log/mcp-server@${PINNED_VERSION}`]
|
|
272
276
|
};
|
|
273
277
|
function detectChatGptDesktop() {
|
|
274
278
|
const home = homedir3();
|
|
@@ -405,4 +409,4 @@ function printManualInstructions() {
|
|
|
405
409
|
export {
|
|
406
410
|
runInit
|
|
407
411
|
};
|
|
408
|
-
//# sourceMappingURL=init-
|
|
412
|
+
//# sourceMappingURL=init-S6MGBOW7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/init/detect.ts","../src/init/write-config.ts","../src/init/write-rules.ts","../src/init/write-skill.ts","../src/init/index.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir, platform } from 'os';\n\nexport interface DetectedTool {\n name: string;\n configPath: string;\n type: 'json-mcpServers';\n}\n\nexport interface DetectedRulesTarget {\n name: string;\n /** Global rules file path — write once, applies to all projects */\n globalRulesPath: string;\n /** How to write: 'append-section' adds a ## section, 'overwrite-if-absent' only creates if missing */\n writeMode: 'append-section' | 'overwrite-if-absent';\n}\n\nexport async function detectTools(): Promise<DetectedTool[]> {\n const home = homedir();\n const os = platform();\n const tools: DetectedTool[] = [];\n\n // ── Claude Desktop ────────────────────────────────────────────────────────\n const claudeDesktopConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json')\n : join(home, '.config', 'claude', 'claude_desktop_config.json');\n\n if (existsSync(claudeDesktopConfig)) {\n tools.push({ name: 'Claude Desktop', configPath: claudeDesktopConfig, type: 'json-mcpServers' });\n }\n\n // ── Claude Code ───────────────────────────────────────────────────────────\n const claudeCodeConfig = join(home, '.claude', 'settings.json');\n const claudeCodeAlt = join(home, '.claude.json');\n if (existsSync(claudeCodeConfig)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeConfig, type: 'json-mcpServers' });\n } else if (existsSync(claudeCodeAlt)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeAlt, type: 'json-mcpServers' });\n }\n\n // ── Cursor ────────────────────────────────────────────────────────────────\n const cursorConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : join(home, '.config', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json');\n const cursorSimple = join(home, '.cursor', 'mcp.json');\n\n if (existsSync(cursorConfig)) {\n tools.push({ name: 'Cursor', configPath: cursorConfig, type: 'json-mcpServers' });\n } else if (existsSync(cursorSimple)) {\n tools.push({ name: 'Cursor', configPath: cursorSimple, type: 'json-mcpServers' });\n }\n\n // ── Windsurf / Codeium ────────────────────────────────────────────────────\n const windsurfConfig = join(home, '.codeium', 'windsurf', 'mcp_config.json');\n if (existsSync(windsurfConfig)) {\n tools.push({ name: 'Windsurf', configPath: windsurfConfig, type: 'json-mcpServers' });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityConfig = join(home, '.antigravity', 'mcp.json');\n const antigravityAlt = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Antigravity', 'mcp.json')\n : join(home, '.config', 'antigravity', 'mcp.json');\n\n if (existsSync(antigravityConfig)) {\n tools.push({ name: 'Antigravity', configPath: antigravityConfig, type: 'json-mcpServers' });\n } else if (existsSync(antigravityAlt)) {\n tools.push({ name: 'Antigravity', configPath: antigravityAlt, type: 'json-mcpServers' });\n }\n\n // ── Continue (VS Code extension) ──────────────────────────────────────────\n const continueConfig = join(home, '.continue', 'config.json');\n if (existsSync(continueConfig)) {\n // Continue uses a different config format — detected but handled separately in writeConfig\n tools.push({ name: 'Continue', configPath: continueConfig, type: 'json-mcpServers' });\n }\n\n return tools;\n}\n\nexport async function detectRulesTargets(): Promise<DetectedRulesTarget[]> {\n const home = homedir();\n const targets: DetectedRulesTarget[] = [];\n\n // ── Cursor: global rules (~/.cursor/rules) ────────────────────────────────\n // Applies to ALL Cursor projects automatically\n if (existsSync(join(home, '.cursor')) || existsSync(join(home, '.cursor', 'mcp.json'))) {\n targets.push({\n name: 'Cursor',\n globalRulesPath: join(home, '.cursor', 'rules'),\n writeMode: 'append-section',\n });\n }\n\n // ── Windsurf: global rules ────────────────────────────────────────────────\n const windsurfDir = join(home, '.codeium', 'windsurf');\n if (existsSync(windsurfDir)) {\n targets.push({\n name: 'Windsurf',\n globalRulesPath: join(windsurfDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── Claude Code: global CLAUDE.md ─────────────────────────────────────────\n const claudeDir = join(home, '.claude');\n if (existsSync(claudeDir)) {\n targets.push({\n name: 'Claude Code',\n globalRulesPath: join(claudeDir, 'CLAUDE.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── GitHub Copilot (VS Code): global instructions ─────────────────────────\n // VS Code stores Copilot instructions in a user-level file\n const vscodeCopilot = join(home, '.vscode', 'copilot-instructions.md');\n if (existsSync(join(home, '.vscode'))) {\n targets.push({\n name: 'GitHub Copilot',\n globalRulesPath: vscodeCopilot,\n writeMode: 'append-section',\n });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityDir = join(home, '.antigravity');\n if (existsSync(antigravityDir)) {\n targets.push({\n name: 'Antigravity',\n globalRulesPath: join(antigravityDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n return targets;\n}\n","import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedTool } from './detect.js';\n\ninterface WriteResult {\n success: boolean;\n error?: string;\n}\n\nexport async function writeConfig(\n tool: DetectedTool,\n mcpConfig: { command: string; args: string[] }\n): Promise<WriteResult> {\n try {\n // Ensure directory exists\n mkdirSync(dirname(tool.configPath), { recursive: true });\n\n // Read existing config or start fresh\n let config: Record<string, any> = {};\n try {\n const raw = readFileSync(tool.configPath, 'utf-8');\n config = JSON.parse(raw);\n } catch {\n // File doesn't exist or invalid JSON — start fresh\n }\n\n // Ensure mcpServers key exists\n if (!config.mcpServers) {\n config.mcpServers = {};\n }\n\n // Don't overwrite existing levelup-log config\n if (config.mcpServers['levelup-log']) {\n return { success: true }; // Already configured\n }\n\n // Add levelup-log\n config.mcpServers['levelup-log'] = mcpConfig;\n\n // Write back\n writeFileSync(tool.configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedRulesTarget } from './detect.js';\n\n// ─── Rules Content ────────────────────────────────────────────────────────────\n// Injected into each platform's global rules file.\n// Keep short — this is appended to existing content.\n\nconst RULES_SECTION = `\n## LevelUp.log Achievement Tracking\n\nAfter completing any meaningful task, proactively call the \\`record_achievement\\` MCP tool.\n**Do not wait to be asked.**\n\nTrigger on: code written/fixed, deployment, tests, docs, ops, learning, life/health/finance tasks.\nSkip: casual Q&A, simple lookups, short explanations with no tangible output.\n\nWhen \\`time_minutes\\` is unclear, ask: \"大概花了多久?\"\nSet \\`self_reported=true\\` when user narrates a past event (\"I exercised this morning\").\n`;\n\nconst SECTION_MARKER = '## LevelUp.log Achievement Tracking';\n\n// ─── Writer ───────────────────────────────────────────────────────────────────\n\nexport interface RulesWriteResult {\n name: string;\n success: boolean;\n action: 'written' | 'already-present' | 'created';\n error?: string;\n}\n\nexport function writeRulesFile(target: DetectedRulesTarget): RulesWriteResult {\n const { name, globalRulesPath, writeMode } = target;\n\n try {\n // Ensure parent directory exists\n const dir = dirname(globalRulesPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Check if already written\n if (existsSync(globalRulesPath)) {\n const existing = readFileSync(globalRulesPath, 'utf-8');\n if (existing.includes(SECTION_MARKER)) {\n return { name, success: true, action: 'already-present' };\n }\n\n if (writeMode === 'overwrite-if-absent') {\n // File exists but no section — skip (don't want to stomp unrelated content)\n return { name, success: true, action: 'already-present' };\n }\n\n // append-section: add to end\n writeFileSync(globalRulesPath, existing.trimEnd() + '\\n' + RULES_SECTION, 'utf-8');\n return { name, success: true, action: 'written' };\n }\n\n // File doesn't exist — create it\n writeFileSync(globalRulesPath, RULES_SECTION.trimStart(), 'utf-8');\n return { name, success: true, action: 'created' };\n\n } catch (err) {\n return { name, success: false, action: 'written', error: String(err) };\n }\n}\n\nexport function writeAllRulesFiles(targets: DetectedRulesTarget[]): RulesWriteResult[] {\n return targets.map(writeRulesFile);\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n// ─── Skill Content ────────────────────────────────────────────────────────────\n// Agent Skills open standard: directory + SKILL.md with YAML frontmatter.\n// Compatible with Claude Code, Cursor, Gemini CLI, Codex CLI, Antigravity.\n//\n// Installed to ~/.claude/skills/levelup/SKILL.md\n// Invoked with: /levelup\n\nconst SKILL_CONTENT = `---\nname: LevelUp Achievement Coach\ndescription: Show your LevelUp.log stats dashboard (level, XP, streak, recent achievements, title progress) and activate full achievement coaching mode. Also proactively records achievements after any meaningful task during this session. Use at the start of a session or anytime you want a progress check.\n---\n\n## When activated, you should:\n\n1. **Call \\`get_my_stats\\`** — show level, XP, streak, active title\n2. **Call \\`get_recent\\`** — show last 7 days of achievements\n3. **Call \\`check_unlocks\\`** — show progress toward next titles\n4. Present this as a game dashboard, not a dry report\n\nExample output format:\n\\`\\`\\`\n⚔️ Lv.29 · Expert\n⚡ Total XP: 12,350 | Year XP: 4,520\n🔥 Streak: 15 days | 🏆 8 titles unlocked\n\nRecent achievements (7 days):\n 🪲 Bug Slayer +82 XP (fix)\n 🚀 Ship It +130 XP (deploy)\n 📝 Doc Wizard +45 XP (docs)\n\nNext title: Iron Body (health × 150) — you're at 87/150 💪\n\\`\\`\\`\n\n## Achievement tracking (always on)\n\nThroughout this session, **proactively call \\`record_achievement\\`** after any meaningful task.\nDon't wait to be asked. Don't ask for permission.\n\n| Trigger | Category | Example |\n|---------|----------|---------|\n| Code written / feature added | code | \"Built auth module\" |\n| Bug fixed | fix | \"Fixed race condition\" |\n| Deployed / published | deploy | \"Shipped v2.1.0 to prod\" |\n| Tests written | test | \"Added 23 unit tests\" |\n| Docs / README written | docs | \"Updated API docs\" |\n| Refactored code | refactor | \"Extracted shared utilities\" |\n| DevOps / CI done | ops | \"Set up GitHub Actions\" |\n| Learned something | learn | \"Studied Supabase RLS\" |\n| Life task done | life | \"Took kids to school\" |\n| Health activity | health | \"30min run\" |\n| Finance task | finance | \"Filed quarterly taxes\" |\n\n## Parameter guide\n\n\\`\\`\\`\ncomplexity:\n trivial → quick fix, single-line change\n normal → typical task, 30-60 min\n significant → multi-step, 1-3 hours\n major → large feature, half day+\n milestone → exceptional, major release\n\ntime_minutes: ask \"大概花了多久?\" if unclear\noutput_units: files changed / tests added / docs pages / tasks done\ninput_units: files read / docs consulted / searches done\nconversation_rounds: count of back-and-forth in this session\nself_reported: true if user narrates past event (\"I went for a run today\")\n\\`\\`\\`\n\n## Coaching behavior\n\n- Use game language: \"Quest complete!\", \"You defeated a bug!\", \"New skill unlocked!\"\n- When streak is active: mention it, encourage keeping it going\n- When user is frustrated/tired: acknowledge WHAT they did, not just \"you're doing great\"\n → \"You shipped X, fixed Y, and learned Z today — those all count.\"\n- Identity reinforcement: \"You're becoming someone who ships every day.\"\n- On birthday (if known): celebrate level-up with last year's stats\n`;\n\n// ─── Installer ────────────────────────────────────────────────────────────────\n\nexport interface SkillInstallResult {\n success: boolean;\n action: 'installed' | 'already-installed' | 'updated';\n path: string;\n error?: string;\n}\n\nexport function installSkill(): SkillInstallResult {\n // New format: directory-based skill (~/.claude/skills/levelup/SKILL.md)\n const skillDir = join(homedir(), '.claude', 'skills', 'levelup');\n const skillPath = join(skillDir, 'SKILL.md');\n\n // Migrate old flat file if it exists\n const legacyPath = join(homedir(), '.claude', 'skills', 'levelup.md');\n\n try {\n mkdirSync(skillDir, { recursive: true });\n\n // Remove legacy flat file if present\n if (existsSync(legacyPath)) {\n import('fs').then(({ unlinkSync }) => {\n try { unlinkSync(legacyPath); } catch { /* ignore */ }\n });\n }\n\n if (existsSync(skillPath)) {\n const existing = readFileSync(skillPath, 'utf-8');\n if (existing === SKILL_CONTENT) {\n return { success: true, action: 'already-installed', path: skillPath };\n }\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'updated', path: skillPath };\n }\n\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'installed', path: skillPath };\n\n } catch (err) {\n return { success: false, action: 'installed', path: skillPath, error: String(err) };\n }\n}\n","import { detectTools, detectRulesTargets } from \"./detect.js\";\nimport { writeConfig } from \"./write-config.js\";\nimport { writeAllRulesFiles } from \"./write-rules.js\";\nimport { installSkill } from \"./write-skill.js\";\nimport { existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { homedir, platform } from \"os\";\n\nconst MCP_CONFIG = {\n command: \"npx\",\n args: [\"-y\", \"@levelup-log/mcp-server@latest\", \"serve\"],\n};\n\n/** ChatGPT Desktop supports MCP on macOS (v1.2024.x+). Detect its config path. */\nfunction detectChatGptDesktop(): string | null {\n const home = homedir();\n const os = platform();\n\n const candidates =\n os === \"darwin\"\n ? [\n join(home, \"Library\", \"Application Support\", \"ChatGPT\", \"mcp.json\"),\n join(\n home,\n \"Library\",\n \"Application Support\",\n \"com.openai.ChatGPT\",\n \"mcp.json\",\n ),\n ]\n : os === \"win32\"\n ? [join(home, \"AppData\", \"Roaming\", \"ChatGPT\", \"mcp.json\")]\n : [];\n\n return candidates.find(existsSync) ?? null;\n}\n\nexport async function runInit() {\n console.log(\"\");\n console.log(\" ╔══════════════════════════════════════╗\");\n console.log(\" ║ LevelUp.log Setup Wizard ║\");\n console.log(\" ╚══════════════════════════════════════╝\");\n console.log(\"\");\n console.log(\" Detecting installed LLM tools...\");\n\n // ── Step 1: MCP config (all platforms) ──────────────────────────────────\n const tools = await detectTools();\n let mcpCount = 0;\n\n // Check ChatGPT Desktop separately (different config format handling)\n const chatGptPath = detectChatGptDesktop();\n if (chatGptPath) {\n tools.push({\n name: \"ChatGPT Desktop\",\n configPath: chatGptPath,\n type: \"json-mcpServers\",\n });\n }\n\n if (tools.length === 0) {\n console.log(\"\");\n console.log(\" No supported LLM tools detected. Manual config:\");\n console.log(\"\");\n console.log(\n JSON.stringify({ mcpServers: { \"levelup-log\": MCP_CONFIG } }, null, 2),\n );\n console.log(\"\");\n printManualInstructions();\n return;\n }\n\n console.log(` Found ${tools.length} tool(s):`);\n tools.forEach((t) => console.log(` ✓ ${t.name}`));\n console.log(\"\");\n\n for (const tool of tools) {\n const result = await writeConfig(tool, MCP_CONFIG);\n if (result.success) {\n console.log(` ✓ MCP configured: ${tool.name}`);\n mcpCount++;\n } else {\n console.log(` ✗ ${tool.name}: ${result.error}`);\n }\n }\n\n // ── Step 2: Global rules files (auto-trigger instructions) ──────────────\n console.log(\"\");\n console.log(\" Writing global rules (auto-trigger instructions)...\");\n console.log(\n \" These make the AI proactively record achievements in every session.\",\n );\n console.log(\"\");\n\n const rulesTargets = await detectRulesTargets();\n if (rulesTargets.length > 0) {\n const results = writeAllRulesFiles(rulesTargets);\n for (const r of results) {\n if (!r.success) {\n console.log(` ✗ ${r.name} rules: ${r.error}`);\n } else if (r.action === \"already-present\") {\n console.log(` ✓ ${r.name}: rules already installed`);\n } else {\n console.log(\n ` ✓ ${r.name}: rules ${r.action} → ${rulesTargets.find((t) => t.name === r.name)?.globalRulesPath}`,\n );\n }\n }\n } else {\n console.log(\" (no supported rules targets detected)\");\n }\n\n // ── Step 3: ChatGPT Desktop manual note ─────────────────────────────────\n if (chatGptPath) {\n console.log(\"\");\n console.log(\" ℹ ChatGPT Desktop detected.\");\n console.log(\" MCP config written. For auto-trigger, also paste into\");\n console.log(\" Settings → Personalization → Custom Instructions:\");\n console.log(\"\");\n console.log(\n ' \"When I complete any meaningful task, use the record_achievement',\n );\n console.log(\n \" MCP tool to log it as an achievement. Don't wait to be asked.\\\"\",\n );\n }\n\n // ── Step 4: Agent Skill (/levelup, cross-platform open standard) ────────\n console.log(\"\");\n console.log(\" Installing /levelup Agent Skill...\");\n const skillResult = installSkill();\n if (skillResult.success) {\n const label =\n skillResult.action === \"already-installed\"\n ? \"already installed\"\n : skillResult.action;\n console.log(` ✓ Skill ${label}: ${skillResult.path}`);\n console.log(\n \" Use /levelup in Claude Code, Cursor, Gemini CLI, Codex, Antigravity\",\n );\n console.log(\" to show stats + activate coach mode.\");\n } else {\n console.log(` ✗ Skill install failed: ${skillResult.error}`);\n }\n\n // ── Step 5: MCP server instructions note ─────────────────────────────────\n console.log(\"\");\n console.log(\" ✓ MCP server instructions active (Claude Desktop, Cursor,\");\n console.log(\" Windsurf, Antigravity auto-inject on every session).\");\n\n // ── Done ─────────────────────────────────────────────────────────────────\n console.log(\"\");\n if (mcpCount > 0) {\n console.log(\n ` Done! Installed in ${mcpCount} tool(s). Restart to activate.`,\n );\n console.log(\" On first use, you'll be prompted to sign in with Google.\");\n } else {\n console.log(\" Rules written. Please add MCP config manually (see above).\");\n }\n console.log(\"\");\n}\n\nfunction printManualInstructions() {\n console.log(\" ─── Manual Setup ───────────────────────────────────────────\");\n console.log(\" Add to your MCP tool's config (mcpServers section):\");\n console.log(\"\");\n console.log(' \"levelup-log\": {');\n console.log(' \"command\": \"npx\",');\n console.log(' \"args\": [\"-y\", \"@levelup-log/mcp-server@latest\", \"serve\"]');\n console.log(\" }\");\n console.log(\"\");\n console.log(\" For ChatGPT Desktop Custom Instructions, paste:\");\n console.log(' \"When I complete any meaningful task, use record_achievement');\n console.log(\" to log it. Don't wait to be asked.\\\"\");\n console.log(\" ────────────────────────────────────────────────────────────\");\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","mkdirSync","dirname","existsSync","readFileSync","writeFileSync","mkdirSync","join","homedir","existsSync","join","homedir","platform"]}
|
|
1
|
+
{"version":3,"sources":["../src/init/detect.ts","../src/init/write-config.ts","../src/init/write-rules.ts","../src/init/write-skill.ts","../src/init/index.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir, platform } from 'os';\n\nexport interface DetectedTool {\n name: string;\n configPath: string;\n type: 'json-mcpServers';\n}\n\nexport interface DetectedRulesTarget {\n name: string;\n /** Global rules file path — write once, applies to all projects */\n globalRulesPath: string;\n /** How to write: 'append-section' adds a ## section, 'overwrite-if-absent' only creates if missing */\n writeMode: 'append-section' | 'overwrite-if-absent';\n}\n\nexport async function detectTools(): Promise<DetectedTool[]> {\n const home = homedir();\n const os = platform();\n const tools: DetectedTool[] = [];\n\n // ── Claude Desktop ────────────────────────────────────────────────────────\n const claudeDesktopConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json')\n : join(home, '.config', 'claude', 'claude_desktop_config.json');\n\n if (existsSync(claudeDesktopConfig)) {\n tools.push({ name: 'Claude Desktop', configPath: claudeDesktopConfig, type: 'json-mcpServers' });\n }\n\n // ── Claude Code ───────────────────────────────────────────────────────────\n const claudeCodeConfig = join(home, '.claude', 'settings.json');\n const claudeCodeAlt = join(home, '.claude.json');\n if (existsSync(claudeCodeConfig)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeConfig, type: 'json-mcpServers' });\n } else if (existsSync(claudeCodeAlt)) {\n tools.push({ name: 'Claude Code', configPath: claudeCodeAlt, type: 'json-mcpServers' });\n }\n\n // ── Cursor ────────────────────────────────────────────────────────────────\n const cursorConfig = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : os === 'win32'\n ? join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json')\n : join(home, '.config', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json');\n const cursorSimple = join(home, '.cursor', 'mcp.json');\n\n if (existsSync(cursorConfig)) {\n tools.push({ name: 'Cursor', configPath: cursorConfig, type: 'json-mcpServers' });\n } else if (existsSync(cursorSimple)) {\n tools.push({ name: 'Cursor', configPath: cursorSimple, type: 'json-mcpServers' });\n }\n\n // ── Windsurf / Codeium ────────────────────────────────────────────────────\n const windsurfConfig = join(home, '.codeium', 'windsurf', 'mcp_config.json');\n if (existsSync(windsurfConfig)) {\n tools.push({ name: 'Windsurf', configPath: windsurfConfig, type: 'json-mcpServers' });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityConfig = join(home, '.antigravity', 'mcp.json');\n const antigravityAlt = os === 'darwin'\n ? join(home, 'Library', 'Application Support', 'Antigravity', 'mcp.json')\n : join(home, '.config', 'antigravity', 'mcp.json');\n\n if (existsSync(antigravityConfig)) {\n tools.push({ name: 'Antigravity', configPath: antigravityConfig, type: 'json-mcpServers' });\n } else if (existsSync(antigravityAlt)) {\n tools.push({ name: 'Antigravity', configPath: antigravityAlt, type: 'json-mcpServers' });\n }\n\n // ── Continue (VS Code extension) ──────────────────────────────────────────\n const continueConfig = join(home, '.continue', 'config.json');\n if (existsSync(continueConfig)) {\n // Continue uses a different config format — detected but handled separately in writeConfig\n tools.push({ name: 'Continue', configPath: continueConfig, type: 'json-mcpServers' });\n }\n\n return tools;\n}\n\nexport async function detectRulesTargets(): Promise<DetectedRulesTarget[]> {\n const home = homedir();\n const targets: DetectedRulesTarget[] = [];\n\n // ── Cursor: global rules (~/.cursor/rules) ────────────────────────────────\n // Applies to ALL Cursor projects automatically\n if (existsSync(join(home, '.cursor')) || existsSync(join(home, '.cursor', 'mcp.json'))) {\n targets.push({\n name: 'Cursor',\n globalRulesPath: join(home, '.cursor', 'rules'),\n writeMode: 'append-section',\n });\n }\n\n // ── Windsurf: global rules ────────────────────────────────────────────────\n const windsurfDir = join(home, '.codeium', 'windsurf');\n if (existsSync(windsurfDir)) {\n targets.push({\n name: 'Windsurf',\n globalRulesPath: join(windsurfDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── Claude Code: global CLAUDE.md ─────────────────────────────────────────\n const claudeDir = join(home, '.claude');\n if (existsSync(claudeDir)) {\n targets.push({\n name: 'Claude Code',\n globalRulesPath: join(claudeDir, 'CLAUDE.md'),\n writeMode: 'append-section',\n });\n }\n\n // ── GitHub Copilot (VS Code): global instructions ─────────────────────────\n // VS Code stores Copilot instructions in a user-level file\n const vscodeCopilot = join(home, '.vscode', 'copilot-instructions.md');\n if (existsSync(join(home, '.vscode'))) {\n targets.push({\n name: 'GitHub Copilot',\n globalRulesPath: vscodeCopilot,\n writeMode: 'append-section',\n });\n }\n\n // ── Antigravity ───────────────────────────────────────────────────────────\n const antigravityDir = join(home, '.antigravity');\n if (existsSync(antigravityDir)) {\n targets.push({\n name: 'Antigravity',\n globalRulesPath: join(antigravityDir, 'rules.md'),\n writeMode: 'append-section',\n });\n }\n\n return targets;\n}\n","import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedTool } from './detect.js';\n\ninterface WriteResult {\n success: boolean;\n error?: string;\n}\n\nexport async function writeConfig(\n tool: DetectedTool,\n mcpConfig: { command: string; args: string[] }\n): Promise<WriteResult> {\n try {\n // Ensure directory exists\n mkdirSync(dirname(tool.configPath), { recursive: true });\n\n // Read existing config or start fresh\n let config: Record<string, any> = {};\n try {\n const raw = readFileSync(tool.configPath, 'utf-8');\n config = JSON.parse(raw);\n } catch {\n // File doesn't exist or invalid JSON — start fresh\n }\n\n // Ensure mcpServers key exists\n if (!config.mcpServers) {\n config.mcpServers = {};\n }\n\n // Don't overwrite existing levelup-log config\n if (config.mcpServers['levelup-log']) {\n return { success: true }; // Already configured\n }\n\n // Add levelup-log\n config.mcpServers['levelup-log'] = mcpConfig;\n\n // Write back\n writeFileSync(tool.configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport type { DetectedRulesTarget } from './detect.js';\n\n// ─── Rules Content ────────────────────────────────────────────────────────────\n// Injected into each platform's global rules file.\n// Keep short — this is appended to existing content.\n\nconst RULES_SECTION = `\n## LevelUp.log Achievement Tracking\n\nAfter completing any meaningful task, proactively call the \\`record_achievement\\` MCP tool.\n**Do not wait to be asked.**\n\nTrigger on: code written/fixed, deployment, tests, docs, ops, learning, life/health/finance tasks.\nSkip: casual Q&A, simple lookups, short explanations with no tangible output.\n\nWhen \\`time_minutes\\` is unclear, ask: \"大概花了多久?\"\nSet \\`self_reported=true\\` when user narrates a past event (\"I exercised this morning\").\n`;\n\nconst SECTION_MARKER = '## LevelUp.log Achievement Tracking';\n\n// ─── Writer ───────────────────────────────────────────────────────────────────\n\nexport interface RulesWriteResult {\n name: string;\n success: boolean;\n action: 'written' | 'already-present' | 'created';\n error?: string;\n}\n\nexport function writeRulesFile(target: DetectedRulesTarget): RulesWriteResult {\n const { name, globalRulesPath, writeMode } = target;\n\n try {\n // Ensure parent directory exists\n const dir = dirname(globalRulesPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Check if already written\n if (existsSync(globalRulesPath)) {\n const existing = readFileSync(globalRulesPath, 'utf-8');\n if (existing.includes(SECTION_MARKER)) {\n return { name, success: true, action: 'already-present' };\n }\n\n if (writeMode === 'overwrite-if-absent') {\n // File exists but no section — skip (don't want to stomp unrelated content)\n return { name, success: true, action: 'already-present' };\n }\n\n // append-section: add to end\n writeFileSync(globalRulesPath, existing.trimEnd() + '\\n' + RULES_SECTION, 'utf-8');\n return { name, success: true, action: 'written' };\n }\n\n // File doesn't exist — create it\n writeFileSync(globalRulesPath, RULES_SECTION.trimStart(), 'utf-8');\n return { name, success: true, action: 'created' };\n\n } catch (err) {\n return { name, success: false, action: 'written', error: String(err) };\n }\n}\n\nexport function writeAllRulesFiles(targets: DetectedRulesTarget[]): RulesWriteResult[] {\n return targets.map(writeRulesFile);\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n// ─── Skill Content ────────────────────────────────────────────────────────────\n// Agent Skills open standard: directory + SKILL.md with YAML frontmatter.\n// Compatible with Claude Code, Cursor, Gemini CLI, Codex CLI, Antigravity.\n//\n// Installed to ~/.claude/skills/levelup/SKILL.md\n// Invoked with: /levelup\n\nconst SKILL_CONTENT = `---\nname: LevelUp Achievement Coach\ndescription: Show your LevelUp.log stats dashboard (level, XP, streak, recent achievements, title progress) and activate full achievement coaching mode. Also proactively records achievements after any meaningful task during this session. Use at the start of a session or anytime you want a progress check.\n---\n\n## When activated, you should:\n\n1. **Call \\`get_my_stats\\`** — show level, XP, streak, active title\n2. **Call \\`get_recent\\`** — show last 7 days of achievements\n3. **Call \\`check_unlocks\\`** — show progress toward next titles\n4. Present this as a game dashboard, not a dry report\n\nExample output format:\n\\`\\`\\`\n⚔️ Lv.29 · Expert\n⚡ Total XP: 12,350 | Year XP: 4,520\n🔥 Streak: 15 days | 🏆 8 titles unlocked\n\nRecent achievements (7 days):\n 🪲 Bug Slayer +82 XP (fix)\n 🚀 Ship It +130 XP (deploy)\n 📝 Doc Wizard +45 XP (docs)\n\nNext title: Iron Body (health × 150) — you're at 87/150 💪\n\\`\\`\\`\n\n## Achievement tracking (always on)\n\nThroughout this session, **proactively call \\`record_achievement\\`** after any meaningful task.\nDon't wait to be asked. Don't ask for permission.\n\n| Trigger | Category | Example |\n|---------|----------|---------|\n| Code written / feature added | code | \"Built auth module\" |\n| Bug fixed | fix | \"Fixed race condition\" |\n| Deployed / published | deploy | \"Shipped v2.1.0 to prod\" |\n| Tests written | test | \"Added 23 unit tests\" |\n| Docs / README written | docs | \"Updated API docs\" |\n| Refactored code | refactor | \"Extracted shared utilities\" |\n| DevOps / CI done | ops | \"Set up GitHub Actions\" |\n| Learned something | learn | \"Studied Supabase RLS\" |\n| Life task done | life | \"Took kids to school\" |\n| Health activity | health | \"30min run\" |\n| Finance task | finance | \"Filed quarterly taxes\" |\n\n## Parameter guide\n\n\\`\\`\\`\ncomplexity:\n trivial → quick fix, single-line change\n normal → typical task, 30-60 min\n significant → multi-step, 1-3 hours\n major → large feature, half day+\n milestone → exceptional, major release\n\ntime_minutes: ask \"大概花了多久?\" if unclear\noutput_units: files changed / tests added / docs pages / tasks done\ninput_units: files read / docs consulted / searches done\nconversation_rounds: count of back-and-forth in this session\nself_reported: true if user narrates past event (\"I went for a run today\")\n\\`\\`\\`\n\n## Coaching behavior\n\n- Use game language: \"Quest complete!\", \"You defeated a bug!\", \"New skill unlocked!\"\n- When streak is active: mention it, encourage keeping it going\n- When user is frustrated/tired: acknowledge WHAT they did, not just \"you're doing great\"\n → \"You shipped X, fixed Y, and learned Z today — those all count.\"\n- Identity reinforcement: \"You're becoming someone who ships every day.\"\n- On birthday (if known): celebrate level-up with last year's stats\n`;\n\n// ─── Installer ────────────────────────────────────────────────────────────────\n\nexport interface SkillInstallResult {\n success: boolean;\n action: 'installed' | 'already-installed' | 'updated';\n path: string;\n error?: string;\n}\n\nexport function installSkill(): SkillInstallResult {\n // New format: directory-based skill (~/.claude/skills/levelup/SKILL.md)\n const skillDir = join(homedir(), '.claude', 'skills', 'levelup');\n const skillPath = join(skillDir, 'SKILL.md');\n\n // Migrate old flat file if it exists\n const legacyPath = join(homedir(), '.claude', 'skills', 'levelup.md');\n\n try {\n mkdirSync(skillDir, { recursive: true });\n\n // Remove legacy flat file if present\n if (existsSync(legacyPath)) {\n import('fs').then(({ unlinkSync }) => {\n try { unlinkSync(legacyPath); } catch { /* ignore */ }\n });\n }\n\n if (existsSync(skillPath)) {\n const existing = readFileSync(skillPath, 'utf-8');\n if (existing === SKILL_CONTENT) {\n return { success: true, action: 'already-installed', path: skillPath };\n }\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'updated', path: skillPath };\n }\n\n writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');\n return { success: true, action: 'installed', path: skillPath };\n\n } catch (err) {\n return { success: false, action: 'installed', path: skillPath, error: String(err) };\n }\n}\n","import { detectTools, detectRulesTargets } from \"./detect.js\";\nimport { writeConfig } from \"./write-config.js\";\nimport { writeAllRulesFiles } from \"./write-rules.js\";\nimport { installSkill } from \"./write-skill.js\";\nimport { existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { homedir, platform } from \"os\";\n\nimport { createRequire } from \"module\";\nconst _require = createRequire(import.meta.url);\nconst _pkg = _require(\"../../package.json\") as { version: string };\nconst PINNED_VERSION = _pkg.version; // e.g. \"0.4.0\" — pinned at install time\n\nconst MCP_CONFIG = {\n command: \"npx\",\n args: [\"-y\", `@levelup-log/mcp-server@${PINNED_VERSION}`],\n};\n\n/** ChatGPT Desktop supports MCP on macOS (v1.2024.x+). Detect its config path. */\nfunction detectChatGptDesktop(): string | null {\n const home = homedir();\n const os = platform();\n\n const candidates =\n os === \"darwin\"\n ? [\n join(home, \"Library\", \"Application Support\", \"ChatGPT\", \"mcp.json\"),\n join(\n home,\n \"Library\",\n \"Application Support\",\n \"com.openai.ChatGPT\",\n \"mcp.json\",\n ),\n ]\n : os === \"win32\"\n ? [join(home, \"AppData\", \"Roaming\", \"ChatGPT\", \"mcp.json\")]\n : [];\n\n return candidates.find(existsSync) ?? null;\n}\n\nexport async function runInit() {\n console.log(\"\");\n console.log(\" ╔══════════════════════════════════════╗\");\n console.log(\" ║ LevelUp.log Setup Wizard ║\");\n console.log(\" ╚══════════════════════════════════════╝\");\n console.log(\"\");\n console.log(\" Detecting installed LLM tools...\");\n\n // ── Step 1: MCP config (all platforms) ──────────────────────────────────\n const tools = await detectTools();\n let mcpCount = 0;\n\n // Check ChatGPT Desktop separately (different config format handling)\n const chatGptPath = detectChatGptDesktop();\n if (chatGptPath) {\n tools.push({\n name: \"ChatGPT Desktop\",\n configPath: chatGptPath,\n type: \"json-mcpServers\",\n });\n }\n\n if (tools.length === 0) {\n console.log(\"\");\n console.log(\" No supported LLM tools detected. Manual config:\");\n console.log(\"\");\n console.log(\n JSON.stringify({ mcpServers: { \"levelup-log\": MCP_CONFIG } }, null, 2),\n );\n console.log(\"\");\n printManualInstructions();\n return;\n }\n\n console.log(` Found ${tools.length} tool(s):`);\n tools.forEach((t) => console.log(` ✓ ${t.name}`));\n console.log(\"\");\n\n for (const tool of tools) {\n const result = await writeConfig(tool, MCP_CONFIG);\n if (result.success) {\n console.log(` ✓ MCP configured: ${tool.name}`);\n mcpCount++;\n } else {\n console.log(` ✗ ${tool.name}: ${result.error}`);\n }\n }\n\n // ── Step 2: Global rules files (auto-trigger instructions) ──────────────\n console.log(\"\");\n console.log(\" Writing global rules (auto-trigger instructions)...\");\n console.log(\n \" These make the AI proactively record achievements in every session.\",\n );\n console.log(\"\");\n\n const rulesTargets = await detectRulesTargets();\n if (rulesTargets.length > 0) {\n const results = writeAllRulesFiles(rulesTargets);\n for (const r of results) {\n if (!r.success) {\n console.log(` ✗ ${r.name} rules: ${r.error}`);\n } else if (r.action === \"already-present\") {\n console.log(` ✓ ${r.name}: rules already installed`);\n } else {\n console.log(\n ` ✓ ${r.name}: rules ${r.action} → ${rulesTargets.find((t) => t.name === r.name)?.globalRulesPath}`,\n );\n }\n }\n } else {\n console.log(\" (no supported rules targets detected)\");\n }\n\n // ── Step 3: ChatGPT Desktop manual note ─────────────────────────────────\n if (chatGptPath) {\n console.log(\"\");\n console.log(\" ℹ ChatGPT Desktop detected.\");\n console.log(\" MCP config written. For auto-trigger, also paste into\");\n console.log(\" Settings → Personalization → Custom Instructions:\");\n console.log(\"\");\n console.log(\n ' \"When I complete any meaningful task, use the record_achievement',\n );\n console.log(\n \" MCP tool to log it as an achievement. Don't wait to be asked.\\\"\",\n );\n }\n\n // ── Step 4: Agent Skill (/levelup, cross-platform open standard) ────────\n console.log(\"\");\n console.log(\" Installing /levelup Agent Skill...\");\n const skillResult = installSkill();\n if (skillResult.success) {\n const label =\n skillResult.action === \"already-installed\"\n ? \"already installed\"\n : skillResult.action;\n console.log(` ✓ Skill ${label}: ${skillResult.path}`);\n console.log(\n \" Use /levelup in Claude Code, Cursor, Gemini CLI, Codex, Antigravity\",\n );\n console.log(\" to show stats + activate coach mode.\");\n } else {\n console.log(` ✗ Skill install failed: ${skillResult.error}`);\n }\n\n // ── Step 5: MCP server instructions note ─────────────────────────────────\n console.log(\"\");\n console.log(\" ✓ MCP server instructions active (Claude Desktop, Cursor,\");\n console.log(\" Windsurf, Antigravity auto-inject on every session).\");\n\n // ── Done ─────────────────────────────────────────────────────────────────\n console.log(\"\");\n if (mcpCount > 0) {\n console.log(\n ` Done! Installed in ${mcpCount} tool(s). Restart to activate.`,\n );\n console.log(\" On first use, you'll be prompted to sign in with Google.\");\n } else {\n console.log(\" Rules written. Please add MCP config manually (see above).\");\n }\n console.log(\"\");\n}\n\nfunction printManualInstructions() {\n console.log(\" ─── Manual Setup ───────────────────────────────────────────\");\n console.log(\" Add to your MCP tool's config (mcpServers section):\");\n console.log(\"\");\n console.log(' \"levelup-log\": {');\n console.log(' \"command\": \"npx\",');\n console.log(' \"args\": [\"-y\", \"@levelup-log/mcp-server@latest\", \"serve\"]');\n console.log(\" }\");\n console.log(\"\");\n console.log(\" For ChatGPT Desktop Custom Instructions, paste:\");\n console.log(' \"When I complete any meaningful task, use record_achievement');\n console.log(\" to log it. Don't wait to be asked.\\\"\");\n console.log(\" ────────────────────────────────────────────────────────────\");\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","mkdirSync","dirname","existsSync","readFileSync","writeFileSync","mkdirSync","join","homedir","existsSync","join","homedir","platform"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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":";;;;;;AAkBA,SAAS,oBAAyC;AAClD,SAAS,gBAAgB;AACzB,SAAS,WAAW,qBAAqB;AACzC,SAAS,eAAe;AACxB,SAAS,YAAY;AAqCrB,SAAS,GAAG,OAAO,UAAK,QAAQ,IAAI;AAClC,UAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAChC;AAEA,SAAS,QAAQ,OAAe;AAC9B,UAAQ,IAAI;AACZ,KAAG;AACH,UAAQ,IAAI,KAAK,KAAK,EAAE;AACxB,KAAG;AACL;AAEA,SAAS,IAAI,GAAoB,GAAW;AAC1C,SAAO,OAAO,CAAC,EAAE,OAAO,CAAC;AAC3B;AAEA,SAAS,SAAS,GAAmB;AACnC,QAAM,IAAI,oBAAI,KAAK;AACnB,IAAE,QAAQ,EAAE,QAAQ,IAAI,CAAC;AACzB,SAAO,EAAE,YAAY;AACvB;AAEA,SAAS,WAAW,KAAa;AAC/B,SAAO,IAAI,KAAK,GAAG,EAAE,mBAAmB,SAAS;AAAA,IAC/C,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC;AACH;AAEA,SAAS,eAAe,KAAa;AACnC,SAAO,IAAI,KAAK,GAAG,EAAE,eAAe,SAAS;AAAA,IAC3C,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,SAAS,WAAW,MAAoB;AACtC,QAAM,IAAI,IAAI;AAAA,IACZ,KAAK,IAAI,KAAK,YAAY,GAAG,KAAK,SAAS,GAAG,KAAK,QAAQ,CAAC;AAAA,EAC9D;AACA,QAAM,MAAM,EAAE,UAAU,KAAK;AAC7B,IAAE,WAAW,EAAE,WAAW,IAAI,IAAI,GAAG;AACrC,QAAM,YAAY,IAAI,KAAK,KAAK,IAAI,EAAE,eAAe,GAAG,GAAG,CAAC,CAAC;AAC7D,SAAO,KAAK,OAAO,EAAE,QAAQ,IAAI,UAAU,QAAQ,KAAK,QAAW,KAAK,CAAC;AAC3E;AAIA,eAAe,YAAY,IAA0C;AACnE,UAAQ,gEAAiB;AAEzB,QAAM,QAAQ,SAAS,EAAE;AAEzB,QAAM,EAAE,OAAO,WAAW,IAAI,MAAM,GACjC,KAAK,UAAU,EACf,OAAO,KAAK,EAAE,OAAO,SAAS,MAAM,KAAK,CAAC;AAE7C,QAAM,EAAE,MAAM,gBAAgB,IAAI,MAAM,GACrC,KAAK,cAAc,EACnB,OAAO,SAAS,EAChB,IAAI,cAAc,KAAK;AAE1B,QAAM,iBAAiB,IAAI,IAAI,iBAAiB,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;AAEvE,QAAM,EAAE,MAAM,iBAAiB,IAAI,MAAM,GACtC,KAAK,cAAc,EACnB,OAAO,cAAc,EACrB,IAAI,cAAc,KAAK;AAE1B,QAAM,YAAY,kBAAkB,UAAU;AAC9C,QAAM,SAAS,kBAAkB,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,IAAI,CAAC,KAAK;AAEtE,QAAM,YAAoC,CAAC;AAC3C,aAAW,KAAK,oBAAoB,CAAC,GAAG;AACtC,cAAU,EAAE,QAAQ,KAAK,UAAU,EAAE,QAAQ,KAAK,KAAK;AAAA,EACzD;AACA,QAAM,gBAAgB,OAAO,QAAQ,SAAS,EAC3C,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAC1B,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE;AAEzC,UAAQ,IAAI,6CAAa,cAAc,CAAC,EAAE;AAC1C,UAAQ,IAAI,6CAAa,cAAc,SAAI;AAC3C,UAAQ,IAAI,6CAAa,SAAS,SAAI;AACtC,UAAQ,IAAI,iCAAa,OAAO,eAAe,CAAC,EAAE;AAElD,MAAI,cAAc,SAAS,GAAG;AAC5B,YAAQ,IAAI;AACZ,YAAQ,IAAI,8CAAW;AACvB,eAAW,EAAE,KAAK,MAAM,KAAK,eAAe;AAC1C,YAAM,MAAM,SAAI;AAAA,QACd,KAAK,MAAO,SAAS,cAAc,CAAC,EAAE,SAAS,KAAM,EAAE;AAAA,MACzD;AACA,cAAQ,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC,IAAI,GAAG,EAAE;AAAA,IAC3D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,YAAY,cAAc;AAAA,IAC1B;AAAA,IACA,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AACF;AAIA,eAAe,aAAa,IAA2C;AACrE,UAAQ,2CAAgB;AAExB,QAAM,SAAQ,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACnD,QAAM,YAAY,oBAAI,KAAK;AAC3B,YAAU,QAAQ,UAAU,QAAQ,IAAI,CAAC;AACzC,QAAM,eAAe,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAEzD,QAAM,EAAE,MAAM,SAAS,IAAI,MAAM,GAC9B,KAAK,UAAU,EACf,OAAO,kCAAkC,EACzC,GAAG,kBAAkB,CAAC;AAEzB,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,YAAQ,IAAI,yCAAgB;AAC5B,WAAO,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,EAAE;AAAA,EACtD;AAEA,MAAI,UAAU,GACZ,SAAS,GACT,SAAS,GACT,QAAQ;AAEV,aAAW,KAAK,UAAU;AACxB,UAAM,YACJ,EAAE,qBAAqB,SAAS,EAAE,qBAAqB;AACzD,QAAI,WAAW;AACb;AACA,UAAI,EAAE,kBAAkB,GAAI;AAAA,eACnB,EAAE,kBAAkB,EAAG;AAAA,IAClC,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,IAAI,qCAAiB,OAAO,SAAI;AACxC,UAAQ,IAAI,sDAAmB,MAAM,SAAI;AACzC,UAAQ,IAAI,4CAAmB,KAAK,SAAI;AACxC,MAAI,SAAS,GAAG;AACd,YAAQ,IAAI,sFAA+B,MAAM,SAAI;AAAA,EACvD;AAEA,SAAO,EAAE,SAAS,QAAQ,QAAQ,MAAM;AAC1C;AAIA,eAAe,UACb,IACA,QACA;AACA,QAAM,EAAE,MAAM,aAAa,IAAI,MAAM,GAClC,KAAK,qBAAqB,EAC1B,OAAO,eAAe,EACtB,GAAG,aAAa,OAAO,EAAE,EACzB,MAAM,aAAa,EAAE,WAAW,MAAM,CAAC;AAE1C,WAAS,IAAI,GAAG,KAAK,gBAAgB,CAAC,GAAG,QAAQ,KAAK;AACpD,UAAM,GACH,KAAK,qBAAqB,EAC1B,OAAO,EAAE,YAAY,IAAI,EAAE,CAAC,EAC5B,GAAG,MAAM,aAAc,CAAC,EAAE,EAAE;AAAA,EACjC;AAEA,QAAM,GAAG,KAAK,SAAS,EAAE,OAAO,EAAE,WAAW,MAAM,CAAC,EAAE,GAAG,MAAM,OAAO,EAAE;AACxE,UAAQ;AAAA,IACN,8BAAU,OAAO,IAAI,wCAAU,cAAc,UAAU,CAAC;AAAA,EAC1D;AACF;AAEA,eAAe,iBACb,IACA,MACA;AACA,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,YAAY;AAChB,MAAI,MAAM;AACR,UAAM,QAAQ,KAAK,KAAK,MAAM,QAAQ;AACtC,QAAI,MAAO,aAAY,SAAS,MAAM,CAAC,CAAC,IAAI;AAAA,EAC9C;AAEA,QAAM,WAAW,OAAO,IAAI,KAAK,KAAK,OAAO,IAAI;AACjD,QAAM,SAAS,IAAI,KAAK,QAAQ;AAChC,SAAO,QAAQ,OAAO,QAAQ,IAAI,EAAE;AAEpC,QAAM,OAAO,IAAI,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,SAAS,YAAY,CAAC,KAAK,KAAK,MAAM,SAAS,SAAS,IAAI,KAAK,CAAC,CAAC;AAE5H,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,GAC3B,KAAK,SAAS,EACd,OAAO;AAAA,IACN;AAAA,IACA,WAAW,SAAS,YAAY;AAAA,IAChC,SAAS,OAAO,YAAY;AAAA,IAC5B,WAAW,CAAC;AAAA,EACd,CAAC,EACA,OAAO,EACP,OAAO;AAEV,MAAI,OAAO;AACT,YAAQ,IAAI,sDAAc,MAAM,OAAO,EAAE;AAAA,EAC3C,OAAO;AACL,YAAQ,IAAI,gDAAa,KAAK,IAAI,QAAG;AACrC,YAAQ;AAAA,MACN,QAAQ,WAAW,KAAK,SAAS,CAAC,WAAM,WAAW,KAAK,OAAO,CAAC;AAAA,IAClE;AAAA,EACF;AACF;AAEA,eAAe,iBAAiB,IAA2C;AACzE,UAAQ,oCAAS;AAEjB,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,GAC7B,KAAK,SAAS,EACd,OAAO,GAAG,EACV,MAAM,aAAa,EAAE,WAAW,MAAM,CAAC;AAE1C,MAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,YAAQ,IAAI,yGAAyB;AACrC,UAAM,iBAAiB,IAAI,IAAI;AAC/B,WAAO,EAAE,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC1C;AAEA,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS;AAE9C,MAAI,QAAQ;AACV,UAAM,SAAS,IAAI,KAAK,OAAO,OAAO;AACtC,UAAM,WAAW,KAAK;AAAA,OACnB,OAAO,QAAQ,IAAI,IAAI,QAAQ,MAAM,MAAO,KAAK,KAAK;AAAA,IACzD;AAEA,QAAI,WAAW,GAAG;AAChB,cAAQ;AAAA,QACN,8BAAU,OAAO,IAAI,4BAAQ,CAAC,QAAQ;AAAA,MACxC;AACA,YAAM,UAAU,IAAI,MAAM;AAC1B,YAAM,iBAAiB,IAAI,MAAM;AACjC,aAAO,EAAE,QAAQ,iBAAiB,MAAM,OAAO,KAAK;AAAA,IACtD;AAEA,QAAI,YAAY,GAAG;AACjB,cAAQ;AAAA,QACN,qCAAY,OAAO,IAAI,sBAAO,QAAQ,4BAAQ,WAAW,OAAO,OAAO,CAAC;AAAA,MAC1E;AACA,YAAM,aAAa,QAAQ;AAAA,QACzB,CAAC,MAAM,CAAC,EAAE,aAAa,IAAI,KAAK,EAAE,SAAS,IAAI,IAAI,KAAK,OAAO,OAAO;AAAA,MACxE;AACA,UAAI,CAAC,YAAY;AACf,gBAAQ,IAAI,oEAAkB;AAC9B,cAAM,iBAAiB,IAAI,MAAM;AAAA,MACnC;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,8BAAU,OAAO,IAAI,0BAAM;AACvC,cAAQ;AAAA,QACN,QAAQ,WAAW,OAAO,SAAS,CAAC,WAAM,WAAW,OAAO,OAAO,CAAC,sBAAO,QAAQ;AAAA,MACrF;AAAA,IACF;AAEA,UAAM,EAAE,OAAO,aAAa,IAAI,MAAM,GACnC,KAAK,qBAAqB,EAC1B,OAAO,KAAK,EAAE,OAAO,SAAS,MAAM,KAAK,CAAC,EAC1C,GAAG,aAAa,OAAO,EAAE;AAE5B,YAAQ,IAAI,sCAAa,gBAAgB,CAAC,SAAI;AAC9C,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM,OAAO;AAAA,MACb;AAAA,MACA,cAAc,gBAAgB;AAAA,MAC9B,QAAQ,OAAO;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,SAAS,QAAQ,CAAC;AACxB,MAAI,UAAU,IAAI,KAAK,OAAO,SAAS,KAAK,KAAK;AAC/C,YAAQ,IAAI,iDAAc,OAAO,IAAI,WAAM;AAC3C,UAAM,GAAG,KAAK,SAAS,EAAE,OAAO,EAAE,WAAW,KAAK,CAAC,EAAE,GAAG,MAAM,OAAO,EAAE;AACvE,WAAO,EAAE,QAAQ,UAAU,MAAM,OAAO,KAAK;AAAA,EAC/C;AAEA,UAAQ,IAAI,gDAAa;AACzB,MAAI;AACF,YAAQ;AAAA,MACN,cAAS,OAAO,IAAI,sBAAO,eAAe,OAAO,SAAS,CAAC;AAAA,IAC7D;AACF,SAAO,EAAE,QAAQ,QAAQ,MAAM,GAAG;AACpC;AAIA,eAAe,kBAAkB,IAA2C;AAC1E,UAAQ,gDAAW;AAEnB,QAAM,EAAE,MAAM,OAAO,IAAI,MAAM,GAC5B,KAAK,mBAAmB,EACxB,OAAO,wBAAwB;AAElC,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,YAAQ,IAAI,wCAAU;AACtB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,GAAG,KAAK,aAAa,EAAE,OAAO,UAAU;AAExE,QAAM,cAAsC,CAAC;AAC7C,aAAW,KAAK,WAAW,CAAC,GAAG;AAC7B,gBAAY,EAAE,QAAQ,KAAK,YAAY,EAAE,QAAQ,KAAK,KAAK;AAAA,EAC7D;AAEA,QAAM,cAAc,CAAC,UAAU,YAAY,QAAQ,QAAQ,WAAW;AACtE,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE;AAAA,IACzB,CAAC,GAAG,MAAM,YAAY,QAAQ,EAAE,MAAM,IAAI,YAAY,QAAQ,EAAE,MAAM;AAAA,EACxE;AAEA,QAAM,cAAsC;AAAA,IAC1C,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,EACb;AAEA,MAAI,gBAAgB;AACpB,aAAW,KAAK,QAAQ;AACtB,QAAI,EAAE,WAAW,eAAe;AAC9B,sBAAgB,EAAE;AAClB,cAAQ,IAAI;AAAA,IAAO,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,YAAY,CAAC,EAAE;AAAA,IACtE;AACA,UAAM,MAAM,YAAY,EAAE,EAAE,KAAK;AACjC,YAAQ;AAAA,MACN,OAAO,EAAE,QAAQ,IAAI,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,SAAO,OAAO,IAAI,CAAC,OAAO;AAAA,IACxB,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE;AAAA,IACV,aAAa,YAAY,EAAE,EAAE,KAAK;AAAA,EACpC,EAAE;AACJ;AAIA,eAAe,uBAAuB,IAAoB;AACxD,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,IAAI,SAAS,MAAM,KAAK,IAAI,QAAQ,MAAM,EAAG;AAEjD,UAAQ,gEAAiB;AACzB,QAAM,WAAW,IAAI,YAAY,IAAI;AAErC,QAAM,EAAE,MAAM,SAAS,IAAI,MAAM,GAC9B,KAAK,UAAU,EACf,OAAO,yCAAyC;AAEnD,MAAI,KAAK,GACP,MAAM;AAER,aAAW,KAAK,YAAY,CAAC,GAAG;AAC9B,UAAM,WAAW,EAAE,aACf,KAAK;AAAA,OACF,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,MAC1C,SAAS,KAAK,KAAK,KAAK;AAAA,IAC7B,IACA;AAEJ,UAAM,EAAE,OAAO,kBAAkB,IAAI,MAAM,GACxC,KAAK,cAAc,EACnB,OAAO,KAAK,EAAE,OAAO,SAAS,MAAM,KAAK,CAAC,EAC1C,GAAG,WAAW,EAAE,EAAE,EAClB,IAAI,cAAc,GAAG,QAAQ,QAAQ,EACrC,GAAG,cAAc,GAAG,WAAW,CAAC,QAAQ;AAE3C,UAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,GAC7B,KAAK,cAAc,EACnB,OAAO,UAAU,EACjB,GAAG,WAAW,EAAE,EAAE,EAClB,IAAI,cAAc,GAAG,QAAQ,QAAQ,EACrC,GAAG,cAAc,GAAG,WAAW,CAAC,QAAQ;AAE3C,UAAM,YAAoC,CAAC;AAC3C,eAAW,KAAK,WAAW,CAAC;AAC1B,gBAAU,EAAE,QAAQ,KAAK,UAAU,EAAE,QAAQ,KAAK,KAAK;AACzD,UAAM,gBAAgB,OAAO,QAAQ,SAAS,EAC3C,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAC1B,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,CAAC,UAAU,KAAK,OAAO,EAAE,UAAU,MAAM,EAAE;AAEnD,UAAM,EAAE,OAAO,eAAe,IAAI,MAAM,GACrC,KAAK,aAAa,EAClB,OAAO,KAAK,EAAE,OAAO,SAAS,MAAM,KAAK,CAAC,EAC1C,GAAG,WAAW,EAAE,EAAE;AAErB,UAAM,EAAE,MAAM,IAAI,MAAM,GAAG,KAAK,kBAAkB,EAAE;AAAA,MAClD;AAAA,QACE,SAAS,EAAE;AAAA,QACX,MAAM;AAAA,QACN,WAAW;AAAA,QACX,SAAS,EAAE;AAAA,QACX,oBAAoB,qBAAqB;AAAA,QACzC,gBAAgB;AAAA,QAChB,gBAAgB,EAAE;AAAA,QAClB,iBAAiB,kBAAkB;AAAA,MACrC;AAAA,MACA,EAAE,YAAY,gBAAgB,kBAAkB,MAAM;AAAA,IACxD;AACA,QAAI,MAAO;AAAA,QACN;AAAA,EACP;AAEA,QAAM,GAAG,KAAK,UAAU,EAAE,OAAO,EAAE,SAAS,GAAG,yBAAyB,EAAE,CAAC;AAC3E,UAAQ,IAAI,0CAAY,EAAE,4BAAQ,GAAG,qBAAM;AAC3C,UAAQ,IAAI,qCAAiB;AAC/B;AAIA,eAAe,eACb,OACA,QACA,QACA,QACA;AACA,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,OAAO,IAAI,YAAY;AAC7B,QAAM,OAAO,WAAW,GAAG;AAC3B,QAAM,UAAU,IAAI,mBAAmB,SAAS;AAAA,IAC9C,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC;AAED,QAAM,QAAQ,OAAO;AACrB,QAAM,cAAc,GAAG,IAAI,KAAK,OAAO,IAAI,EAAE,SAAS,GAAG,GAAG,CAAC,KAAK,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC;AAEhG,QAAM,WAAW,KAAK,QAAQ,GAAG,eAAe;AAChD,QAAM,YAAY,KAAK,UAAU,6CAAoB;AACrD,QAAM,WAAW,GAAG,WAAW;AAC/B,QAAM,WAAW,KAAK,WAAW,QAAQ;AAGzC,QAAM,cAAc,CAAC,UAAU,YAAY,QAAQ,QAAQ,WAAW;AACtE,QAAM,cAAsC;AAAA,IAC1C,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,EACb;AACA,QAAM,iBAAiB,YACpB,IAAI,CAAC,MAAM;AACV,UAAM,QAAQ,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC;AACjD,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,OAAO,MACV,IAAI,CAAC,MAAM,KAAK,EAAE,QAAQ,QAAG,MAAM,EAAE,IAAI,MAAM,EAAE,WAAW,IAAI,EAChE,KAAK,IAAI;AACZ,WAAO,KAAK,YAAY,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA,EAAyD,IAAI;AAAA,EACzF,CAAC,EACA,OAAO,OAAO,EACd,KAAK,MAAM;AAGd,QAAM,UAAU,MAAM,cACnB,IAAI,CAAC,EAAE,KAAK,MAAM,MAAM,KAAK,GAAG,MAAM,KAAK,IAAI,EAC/C,KAAK,IAAI;AACZ,QAAM,WACJ,MAAM,cAAc,SAAS,IACzB;AAAA;AAAA,EAAoC,OAAO,KAC3C;AAGN,QAAM,aACJ,OAAO,WAAW,WACd,GAAG,OAAO,IAAI,sBAAO,OAAO,QAAQ,gBAAM,OAAO,gBAAgB,CAAC,oCAClE,OAAO,WAAW,kBAChB,GAAG,OAAO,IAAI,kEACd,OAAO,WAAW,YAChB,qDACA;AAEV,QAAM,KAAK;AAAA;AAAA,QAEL,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA,mCAInB,WAAW;AAAA;AAAA,kCAEtB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAML,MAAM,UAAU;AAAA,+BAChB,MAAM,cAAc;AAAA,+BACpB,MAAM,gBAAgB;AAAA,sBACrB,MAAM,OAAO,eAAe,CAAC;AAAA;AAAA;AAAA;AAAA,EAIvC,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAMM,OAAO,OAAO;AAAA,4BACjB,OAAO,MAAM;AAAA,kBACb,OAAO,KAAK;AAAA,uDACV,OAAO,MAAM;AAAA;AAAA;AAAA;AAAA,EAI1B,UAAU;AAAA;AAAA;AAAA;AAAA,EAIV,kBAAkB,kCAAS;AAAA;AAI3B,YAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,gBAAc,UAAU,IAAI,OAAO;AAEnC,UAAQ,IAAI;AACZ,UAAQ;AAAA,IACN,8GAA2C,QAAQ;AAAA,EACrD;AACF;AAIA,SAAS,eACP,OACA,QACA,MACA,MACA;AACA,UAAQ,+BAAc;AAGtB,MAAI;AACF,aAAS,aAAa,EAAE,OAAO,SAAS,CAAC;AAAA,EAC3C,QAAQ;AACN,YAAQ,IAAI,kFAAsB;AAClC,YAAQ,IAAI,yDAA0C;AACtD;AAAA,EACF;AAEA,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,YAAY,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAEhD,QAAM,KAAK,OAAO,IAAI,EAAE,SAAS,GAAG,GAAG;AACvC,QAAM,KAAK,OAAO,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAC3C,QAAM,cAAc,GAAG,IAAI,KAAK,EAAE,KAAK,EAAE;AAGzC,QAAM,QAAQ,eAAe,WAAW,WAAM,MAAM,cAAc,sBAAS,MAAM,gBAAgB,sBAAS,MAAM,OAAO,eAAe,CAAC;AAGvI,QAAM,YAAY,GAAG,SAAS;AAC9B,QAAM,UAAU,GAAG,SAAS;AAG5B,QAAM,aAAa,MAAM,cACtB,IAAI,CAAC,EAAE,KAAK,MAAM,MAAM,KAAK,GAAG,KAAK,KAAK,EAAE,EAC5C,KAAK,IAAI;AAEZ,QAAM,aACJ,OAAO,WAAW,WACd,GAAG,OAAO,IAAI,sBAAO,OAAO,QAAQ,YACpC,OAAO,QAAQ;AAErB,QAAM,cACJ,kCAAmB,WAAW;AAAA;AAAA,oBACxB,MAAM,UAAU,uBAAQ,MAAM,cAAc;AAAA,oBAC5C,MAAM,gBAAgB,aAAQ,MAAM,OAAO,eAAe,CAAC;AAAA;AAAA;AAAA,EACvD,cAAc,sBAAO;AAAA;AAAA,oBACzB,UAAU;AAAA;AAAA,4DACe,WAAW;AAE5C,MAAI;AACF;AAAA,MACE,uCACY,KAAK,UAAU,KAAK,CAAC,qBACpB,SAAS,oBACX,OAAO,2BACA,KAAK,UAAU,WAAW,CAAC;AAAA,MAE7C,EAAE,OAAO,OAAO;AAAA,IAClB;AACA,YAAQ,IAAI,4DAAe,KAAK,EAAE;AAAA,EACpC,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAQ,IAAI,sDAAc,IAAI,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE;AAAA,EAChD;AACF;AAIA,eAAsB,OAAO;AAC3B,QAAM,iBACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI;AAEd,MAAI,CAAC,gBAAgB;AACnB,YAAQ,MAAM,uEAAoC;AAClD,YAAQ,MAAM,0DAA0D;AACxE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,KAAK,aAAa,OAAO,cAAc,gBAAgB;AAAA,IAC3D,MAAM,EAAE,gBAAgB,MAAM;AAAA,EAChC,CAAC;AAED,QAAM,MAAM,oBAAI,KAAK;AACrB,UAAQ,IAAI;AACZ,UAAQ,IAAI,0WAA8D;AAC1E,UAAQ,IAAI,uEAA6D;AACzE,UAAQ,IAAI,kBAAa,IAAI,eAAe,OAAO,EAAE,OAAO,EAAE,CAAC,QAAG;AAClE,UAAQ,IAAI,0WAA8D;AAE1E,QAAM,QAAQ,MAAM,YAAY,EAAE;AAClC,QAAM,SAAS,MAAM,aAAa,EAAE;AACpC,QAAM,SAAS,MAAM,iBAAiB,EAAE;AACxC,QAAM,SAAS,MAAM,kBAAkB,EAAE;AAEzC,QAAM,uBAAuB,EAAE;AAC/B,QAAM,eAAe,OAAO,QAAQ,QAAQ,MAAM;AAClD,iBAAe,OAAO,QAAQ,WAAW,GAAG,GAAG,IAAI,YAAY,CAAC;AAEhE,UAAQ,+BAAgB;AACxB,UAAQ,IAAI;AACd;AAGA,IAAM,cACJ,QAAQ,KAAK,CAAC,GAAG,SAAS,cAAc,KACxC,QAAQ,KAAK,CAAC,GAAG,SAAS,cAAc;AAE1C,IAAI,aAAa;AACf,OAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,YAAQ,MAAM,uCAAmB,GAAG;AACpC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":[]}
|