level-up-mcp-server-cn 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/projects/c--Users-klexi-OneDrive-Desktop-Levelup-level-up-mcp-server/memory/project_testing_service.md +11 -0
- package/.claude/settings.local.json +10 -0
- package/.env.example +19 -0
- package/CLAUDE.md +222 -0
- package/CODE_REVIEW.md +282 -0
- package/LICENSE +64 -0
- package/README.md +198 -0
- package/dist/constants.d.ts +33 -0
- package/dist/constants.js +78 -0
- package/dist/constants.js.map +1 -0
- package/dist/data/quest-seeds.d.ts +18 -0
- package/dist/data/quest-seeds.js +380 -0
- package/dist/data/quest-seeds.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/common.d.ts +33 -0
- package/dist/schemas/common.js +96 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/services/dispatcher.d.ts +27 -0
- package/dist/services/dispatcher.js +47 -0
- package/dist/services/dispatcher.js.map +1 -0
- package/dist/services/errors.d.ts +56 -0
- package/dist/services/errors.js +99 -0
- package/dist/services/errors.js.map +1 -0
- package/dist/services/format.d.ts +74 -0
- package/dist/services/format.js +144 -0
- package/dist/services/format.js.map +1 -0
- package/dist/services/ownership.d.ts +19 -0
- package/dist/services/ownership.js +79 -0
- package/dist/services/ownership.js.map +1 -0
- package/dist/services/quality-gate.d.ts +45 -0
- package/dist/services/quality-gate.js +131 -0
- package/dist/services/quality-gate.js.map +1 -0
- package/dist/services/rate-limit.d.ts +12 -0
- package/dist/services/rate-limit.js +49 -0
- package/dist/services/rate-limit.js.map +1 -0
- package/dist/services/register.d.ts +49 -0
- package/dist/services/register.js +63 -0
- package/dist/services/register.js.map +1 -0
- package/dist/services/supabase.d.ts +10 -0
- package/dist/services/supabase.js +79 -0
- package/dist/services/supabase.js.map +1 -0
- package/dist/tools/achievements.d.ts +6 -0
- package/dist/tools/achievements.js +242 -0
- package/dist/tools/achievements.js.map +1 -0
- package/dist/tools/admin.d.ts +16 -0
- package/dist/tools/admin.js +328 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/agents.d.ts +3 -0
- package/dist/tools/agents.js +400 -0
- package/dist/tools/agents.js.map +1 -0
- package/dist/tools/bootstrap.d.ts +17 -0
- package/dist/tools/bootstrap.js +565 -0
- package/dist/tools/bootstrap.js.map +1 -0
- package/dist/tools/dispatchers/admin.d.ts +3 -0
- package/dist/tools/dispatchers/admin.js +50 -0
- package/dist/tools/dispatchers/admin.js.map +1 -0
- package/dist/tools/dispatchers/eval.d.ts +3 -0
- package/dist/tools/dispatchers/eval.js +40 -0
- package/dist/tools/dispatchers/eval.js.map +1 -0
- package/dist/tools/dispatchers/quests.d.ts +3 -0
- package/dist/tools/dispatchers/quests.js +60 -0
- package/dist/tools/dispatchers/quests.js.map +1 -0
- package/dist/tools/dispatchers/session.d.ts +3 -0
- package/dist/tools/dispatchers/session.js +38 -0
- package/dist/tools/dispatchers/session.js.map +1 -0
- package/dist/tools/dispatchers/skills.d.ts +3 -0
- package/dist/tools/dispatchers/skills.js +49 -0
- package/dist/tools/dispatchers/skills.js.map +1 -0
- package/dist/tools/dispatchers/tasks.d.ts +3 -0
- package/dist/tools/dispatchers/tasks.js +53 -0
- package/dist/tools/dispatchers/tasks.js.map +1 -0
- package/dist/tools/dispatchers/users.d.ts +3 -0
- package/dist/tools/dispatchers/users.js +65 -0
- package/dist/tools/dispatchers/users.js.map +1 -0
- package/dist/tools/dispatchers/xp.d.ts +3 -0
- package/dist/tools/dispatchers/xp.js +51 -0
- package/dist/tools/dispatchers/xp.js.map +1 -0
- package/dist/tools/growth-plan.d.ts +5 -0
- package/dist/tools/growth-plan.js +791 -0
- package/dist/tools/growth-plan.js.map +1 -0
- package/dist/tools/leaderboards.d.ts +10 -0
- package/dist/tools/leaderboards.js +279 -0
- package/dist/tools/leaderboards.js.map +1 -0
- package/dist/tools/leveling.d.ts +24 -0
- package/dist/tools/leveling.js +356 -0
- package/dist/tools/leveling.js.map +1 -0
- package/dist/tools/metrics.d.ts +3 -0
- package/dist/tools/metrics.js +247 -0
- package/dist/tools/metrics.js.map +1 -0
- package/dist/tools/quests.d.ts +5 -0
- package/dist/tools/quests.js +586 -0
- package/dist/tools/quests.js.map +1 -0
- package/dist/tools/ratings.d.ts +11 -0
- package/dist/tools/ratings.js +564 -0
- package/dist/tools/ratings.js.map +1 -0
- package/dist/tools/skills.d.ts +66 -0
- package/dist/tools/skills.js +1112 -0
- package/dist/tools/skills.js.map +1 -0
- package/dist/tools/system.d.ts +31 -0
- package/dist/tools/system.js +605 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/tasks.d.ts +73 -0
- package/dist/tools/tasks.js +1572 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/users.d.ts +97 -0
- package/dist/tools/users.js +1306 -0
- package/dist/tools/users.js.map +1 -0
- package/dist/tools/xp.d.ts +38 -0
- package/dist/tools/xp.js +670 -0
- package/dist/tools/xp.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/docs/recommended-skillsets.md +622 -0
- package/docs/skills-and-abilities-review.md +672 -0
- package/docs/v0.3-roadmap.md +191 -0
- package/package.json +35 -0
- package/sql/agent_pending_installs.sql +28 -0
- package/sql/award_class_xp.sql +81 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/migrations/20260314000000_anon_rls_policies.sql +311 -0
- package/supabase/migrations/20260314000001_ownership_rpcs.sql +382 -0
- package/supabase/migrations/20260314000002_evidence_and_growth_plan.sql +97 -0
- package/supabase/migrations/20260317000000_seed_quests.sql +62 -0
- package/supabase/migrations/20260317000001_star_cooldown_and_fixes.sql +16 -0
- package/supabase/migrations/20260318000000_restore_rank_names.sql +25 -0
- package/supabase/migrations/20260320000000_chinese_rank_names.sql +24 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// services/ownership.ts — Ownership verification for data access
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Prevents users from viewing other users' private data (tasks,
|
|
5
|
+
// XP history, evidence) by verifying the caller owns or is
|
|
6
|
+
// associated with the requested entity.
|
|
7
|
+
// ============================================================
|
|
8
|
+
import { supabase } from "./supabase.js";
|
|
9
|
+
import { fail } from "./errors.js";
|
|
10
|
+
import { toMcpResponse } from "./register.js";
|
|
11
|
+
/**
|
|
12
|
+
* Verify that a caller has access to the requested entity's data.
|
|
13
|
+
*
|
|
14
|
+
* Rules:
|
|
15
|
+
* - If target user_id matches caller_user_id → allowed
|
|
16
|
+
* - If target agent_id is owned by caller_user_id → allowed
|
|
17
|
+
* - If target is a task, verify caller owns the task's user_id or agent
|
|
18
|
+
* - Otherwise → denied
|
|
19
|
+
*
|
|
20
|
+
* @returns null if access is allowed, or an MCP error response if denied
|
|
21
|
+
*/
|
|
22
|
+
export async function verifyOwnership(params) {
|
|
23
|
+
const { caller_user_id, target_user_id, target_agent_id, target_task_id } = params;
|
|
24
|
+
// Direct user match
|
|
25
|
+
if (target_user_id && target_user_id === caller_user_id) {
|
|
26
|
+
return null; // allowed
|
|
27
|
+
}
|
|
28
|
+
// Check agent ownership
|
|
29
|
+
if (target_agent_id) {
|
|
30
|
+
const { data: agent } = await supabase
|
|
31
|
+
.from("agents")
|
|
32
|
+
.select("owner_user_id")
|
|
33
|
+
.eq("id", target_agent_id)
|
|
34
|
+
.maybeSingle();
|
|
35
|
+
if (agent?.owner_user_id === caller_user_id) {
|
|
36
|
+
return null; // allowed — caller owns this agent
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Check task ownership (task belongs to caller's user or agent)
|
|
40
|
+
if (target_task_id) {
|
|
41
|
+
const { data: task } = await supabase
|
|
42
|
+
.from("tasks")
|
|
43
|
+
.select("user_id, agent_id")
|
|
44
|
+
.eq("id", target_task_id)
|
|
45
|
+
.maybeSingle();
|
|
46
|
+
if (!task) {
|
|
47
|
+
return toMcpResponse(fail("未找到任务"));
|
|
48
|
+
}
|
|
49
|
+
// Task belongs to caller directly
|
|
50
|
+
if (task.user_id === caller_user_id) {
|
|
51
|
+
return null; // allowed
|
|
52
|
+
}
|
|
53
|
+
// Task belongs to caller's agent
|
|
54
|
+
if (task.agent_id) {
|
|
55
|
+
const { data: agent } = await supabase
|
|
56
|
+
.from("agents")
|
|
57
|
+
.select("owner_user_id")
|
|
58
|
+
.eq("id", task.agent_id)
|
|
59
|
+
.maybeSingle();
|
|
60
|
+
if (agent?.owner_user_id === caller_user_id) {
|
|
61
|
+
return null; // allowed
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// If only user_id was provided and it didn't match, deny
|
|
66
|
+
if (target_user_id && target_user_id !== caller_user_id) {
|
|
67
|
+
return toMcpResponse(fail("拒绝访问:您只能查看自己的数据", "请传入您自己的 user_id 作为 caller_user_id 以证明所有权。"));
|
|
68
|
+
}
|
|
69
|
+
// If only agent_id was provided and ownership check failed
|
|
70
|
+
if (target_agent_id) {
|
|
71
|
+
return toMcpResponse(fail("拒绝访问:您不是此智能体的所有者", "请传入您自己的 user_id 作为 caller_user_id。只有智能体的所有者才能查看其数据。"));
|
|
72
|
+
}
|
|
73
|
+
// Task not owned by caller
|
|
74
|
+
if (target_task_id) {
|
|
75
|
+
return toMcpResponse(fail("拒绝访问:此任务不属于您", "您只能查看属于您或您的智能体的任务。"));
|
|
76
|
+
}
|
|
77
|
+
return toMcpResponse(fail("拒绝访问"));
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownership.js","sourceRoot":"","sources":["../../src/services/ownership.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,iEAAiE;AACjE,+DAA+D;AAC/D,gEAAgE;AAChE,2DAA2D;AAC3D,wCAAwC;AACxC,+DAA+D;AAE/D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAKrC;IACC,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IAEnF,oBAAoB;IACpB,IAAI,cAAc,IAAI,cAAc,KAAK,cAAc,EAAE,CAAC;QACxD,OAAO,IAAI,CAAC,CAAC,UAAU;IACzB,CAAC;IAED,wBAAwB;IACxB,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;aACnC,IAAI,CAAC,QAAQ,CAAC;aACd,MAAM,CAAC,eAAe,CAAC;aACvB,EAAE,CAAC,IAAI,EAAE,eAAe,CAAC;aACzB,WAAW,EAAE,CAAC;QAEjB,IAAI,KAAK,EAAE,aAAa,KAAK,cAAc,EAAE,CAAC;YAC5C,OAAO,IAAI,CAAC,CAAC,mCAAmC;QAClD,CAAC;IACH,CAAC;IAED,gEAAgE;IAChE,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ;aAClC,IAAI,CAAC,OAAO,CAAC;aACb,MAAM,CAAC,mBAAmB,CAAC;aAC3B,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC;aACxB,WAAW,EAAE,CAAC;QAEjB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACtC,CAAC;QAED,kCAAkC;QAClC,IAAI,IAAI,CAAC,OAAO,KAAK,cAAc,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC,CAAC,UAAU;QACzB,CAAC;QAED,iCAAiC;QACjC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;iBACnC,IAAI,CAAC,QAAQ,CAAC;iBACd,MAAM,CAAC,eAAe,CAAC;iBACvB,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC;iBACvB,WAAW,EAAE,CAAC;YAEjB,IAAI,KAAK,EAAE,aAAa,KAAK,cAAc,EAAE,CAAC;gBAC5C,OAAO,IAAI,CAAC,CAAC,UAAU;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,IAAI,cAAc,IAAI,cAAc,KAAK,cAAc,EAAE,CAAC;QACxD,OAAO,aAAa,CAAC,IAAI,CACvB,iBAAiB,EACjB,2CAA2C,CAC5C,CAAC,CAAC;IACL,CAAC;IAED,2DAA2D;IAC3D,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,aAAa,CAAC,IAAI,CACvB,kBAAkB,EAClB,qDAAqD,CACtD,CAAC,CAAC;IACL,CAAC;IAED,2BAA2B;IAC3B,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,aAAa,CAAC,IAAI,CACvB,cAAc,EACd,oBAAoB,CACrB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Award XP via SECURITY DEFINER RPC (shared helper).
|
|
3
|
+
* Used by skills, quests, and any code that needs to award non-task XP.
|
|
4
|
+
*/
|
|
5
|
+
export declare function awardXpViaRpc(entityType: string, entityId: string, xpAmount: number, sourceType: string, description: string, sourceId?: string): Promise<{
|
|
6
|
+
success: boolean;
|
|
7
|
+
running_total?: number;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Award class XP via SECURITY DEFINER RPC (shared helper).
|
|
11
|
+
* Updates user_class_progress and records in xp_ledger with class xp_type.
|
|
12
|
+
*/
|
|
13
|
+
export declare function awardClassXpViaRpc(userId: string, className: string, xpAmount: number, sourceType: string, description: string, sourceId?: string): Promise<{
|
|
14
|
+
success: boolean;
|
|
15
|
+
new_class_xp?: number;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Compute a system template quality score for a task (1-5 scale).
|
|
19
|
+
* Same logic as levelup_request_system_evaluation but runs inline.
|
|
20
|
+
*
|
|
21
|
+
* Timeliness is now tier-aware: tasks completed much faster than the
|
|
22
|
+
* expected pace for their tier score lower on timeliness (2-3/5),
|
|
23
|
+
* while tasks at or above the expected pace score 4-5/5.
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeSystemScore(task: {
|
|
26
|
+
completion_pct: number;
|
|
27
|
+
output_type: string | null;
|
|
28
|
+
difficulty: number;
|
|
29
|
+
duration_seconds: number | null;
|
|
30
|
+
xp_tier?: string | null;
|
|
31
|
+
}): {
|
|
32
|
+
score: number;
|
|
33
|
+
breakdown: Record<string, number>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Get the integrity multiplier for an agent (0.1 to 1.0).
|
|
37
|
+
* Users always get multiplier 1.0.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getIntegrityMultiplier(entityType: string, entityId: string): Promise<number>;
|
|
40
|
+
/**
|
|
41
|
+
* Apply quality and integrity multipliers to an XP pool.
|
|
42
|
+
* Returns the adjusted XP amount (never less than 1 if original > 0).
|
|
43
|
+
*/
|
|
44
|
+
export declare function applyQualityMultiplier(xpPool: number, qualityScore: number, integrityMultiplier: number): number;
|
|
45
|
+
//# sourceMappingURL=quality-gate.d.ts.map
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// services/quality-gate.ts — Quality-gated XP & shared XP helpers
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Auto-scores a completed task using template-based evaluation,
|
|
5
|
+
// then applies quality and integrity multipliers to XP.
|
|
6
|
+
// Also exports awardXpViaRpc for shared use across tool files.
|
|
7
|
+
// ============================================================
|
|
8
|
+
import { supabase } from "./supabase.js";
|
|
9
|
+
/**
|
|
10
|
+
* Award XP via SECURITY DEFINER RPC (shared helper).
|
|
11
|
+
* Used by skills, quests, and any code that needs to award non-task XP.
|
|
12
|
+
*/
|
|
13
|
+
export async function awardXpViaRpc(entityType, entityId, xpAmount, sourceType, description, sourceId) {
|
|
14
|
+
const { data, error } = await supabase.rpc('award_xp_direct', {
|
|
15
|
+
p_entity_type: entityType,
|
|
16
|
+
p_entity_id: entityId,
|
|
17
|
+
p_amount: xpAmount,
|
|
18
|
+
p_source_type: sourceType,
|
|
19
|
+
p_description: description,
|
|
20
|
+
p_source_id: sourceId || null,
|
|
21
|
+
});
|
|
22
|
+
if (error) {
|
|
23
|
+
console.error("award_xp_direct RPC error:", error);
|
|
24
|
+
return { success: false };
|
|
25
|
+
}
|
|
26
|
+
return { success: data?.success ?? false, running_total: data?.running_total };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Award class XP via SECURITY DEFINER RPC (shared helper).
|
|
30
|
+
* Updates user_class_progress and records in xp_ledger with class xp_type.
|
|
31
|
+
*/
|
|
32
|
+
export async function awardClassXpViaRpc(userId, className, xpAmount, sourceType, description, sourceId) {
|
|
33
|
+
const { data, error } = await supabase.rpc('award_class_xp', {
|
|
34
|
+
p_user_id: userId,
|
|
35
|
+
p_class_name: className,
|
|
36
|
+
p_amount: xpAmount,
|
|
37
|
+
p_source_type: sourceType,
|
|
38
|
+
p_description: description,
|
|
39
|
+
p_source_id: sourceId || null,
|
|
40
|
+
});
|
|
41
|
+
if (error) {
|
|
42
|
+
console.error("award_class_xp RPC error:", error);
|
|
43
|
+
return { success: false };
|
|
44
|
+
}
|
|
45
|
+
return { success: data?.success ?? false, new_class_xp: data?.new_class_xp };
|
|
46
|
+
}
|
|
47
|
+
// "Good pace" thresholds per tier (seconds). Tasks completed faster than
|
|
48
|
+
// the hard floor pass, but completing well under the expected pace lowers
|
|
49
|
+
// the timeliness score in the quality gate — rewarding thoroughness without
|
|
50
|
+
// hard-blocking fast agents.
|
|
51
|
+
const EXPECTED_PACE_SECONDS = {
|
|
52
|
+
micro: 30, // 30s expected
|
|
53
|
+
light: 120, // 2 min expected
|
|
54
|
+
standard: 300, // 5 min expected
|
|
55
|
+
complex: 600, // 10 min expected
|
|
56
|
+
major: 1800, // 30 min expected
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Compute a system template quality score for a task (1-5 scale).
|
|
60
|
+
* Same logic as levelup_request_system_evaluation but runs inline.
|
|
61
|
+
*
|
|
62
|
+
* Timeliness is now tier-aware: tasks completed much faster than the
|
|
63
|
+
* expected pace for their tier score lower on timeliness (2-3/5),
|
|
64
|
+
* while tasks at or above the expected pace score 4-5/5.
|
|
65
|
+
*/
|
|
66
|
+
export function computeSystemScore(task) {
|
|
67
|
+
const completionScore = task.completion_pct >= 100 ? 5
|
|
68
|
+
: task.completion_pct >= 75 ? 4
|
|
69
|
+
: task.completion_pct >= 50 ? 3 : 2;
|
|
70
|
+
const hasOutput = task.output_type === "deliverable" || task.output_type === "deployed";
|
|
71
|
+
const outputScore = hasOutput ? 4 : 3;
|
|
72
|
+
const difficultyScore = task.difficulty >= 3 ? 4 : 3;
|
|
73
|
+
// Tier-aware timeliness: compare actual duration against expected pace
|
|
74
|
+
const tier = task.xp_tier || "standard";
|
|
75
|
+
const expectedPace = EXPECTED_PACE_SECONDS[tier] || 300;
|
|
76
|
+
const durationSeconds = task.duration_seconds || 0;
|
|
77
|
+
const paceRatio = durationSeconds / Math.max(1, expectedPace); // 1.0 = on pace
|
|
78
|
+
let timelinessScore;
|
|
79
|
+
if (paceRatio >= 1.0) {
|
|
80
|
+
// At or above expected pace — good
|
|
81
|
+
timelinessScore = paceRatio < 3.0 ? 5 : 4; // very slow = still OK but 4
|
|
82
|
+
}
|
|
83
|
+
else if (paceRatio >= 0.5) {
|
|
84
|
+
// 50-99% of expected pace — slightly fast, decent score
|
|
85
|
+
timelinessScore = 4;
|
|
86
|
+
}
|
|
87
|
+
else if (paceRatio >= 0.3) {
|
|
88
|
+
// 30-49% of expected pace — suspiciously fast
|
|
89
|
+
timelinessScore = 3;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Under 30% of expected pace — rushing
|
|
93
|
+
timelinessScore = 2;
|
|
94
|
+
}
|
|
95
|
+
const breakdown = {
|
|
96
|
+
completeness: completionScore,
|
|
97
|
+
output_quality: outputScore,
|
|
98
|
+
difficulty_appropriate: difficultyScore,
|
|
99
|
+
timeliness: timelinessScore,
|
|
100
|
+
};
|
|
101
|
+
const scores = Object.values(breakdown);
|
|
102
|
+
const score = scores.reduce((s, n) => s + n, 0) / scores.length;
|
|
103
|
+
return { score, breakdown };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the integrity multiplier for an agent (0.1 to 1.0).
|
|
107
|
+
* Users always get multiplier 1.0.
|
|
108
|
+
*/
|
|
109
|
+
export async function getIntegrityMultiplier(entityType, entityId) {
|
|
110
|
+
if (entityType === "user")
|
|
111
|
+
return 1.0;
|
|
112
|
+
const { data: agent } = await supabase
|
|
113
|
+
.from("agents")
|
|
114
|
+
.select("integrity_score")
|
|
115
|
+
.eq("id", entityId)
|
|
116
|
+
.maybeSingle();
|
|
117
|
+
const integrity = agent?.integrity_score ?? 100;
|
|
118
|
+
return Math.max(0.1, Math.min(1.0, integrity / 100));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Apply quality and integrity multipliers to an XP pool.
|
|
122
|
+
* Returns the adjusted XP amount (never less than 1 if original > 0).
|
|
123
|
+
*/
|
|
124
|
+
export function applyQualityMultiplier(xpPool, qualityScore, integrityMultiplier) {
|
|
125
|
+
if (xpPool <= 0)
|
|
126
|
+
return 0;
|
|
127
|
+
const qualityMultiplier = qualityScore / 5.0;
|
|
128
|
+
const adjusted = Math.round(xpPool * qualityMultiplier * integrityMultiplier);
|
|
129
|
+
return Math.max(1, adjusted); // at least 1 XP if pool was > 0
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=quality-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality-gate.js","sourceRoot":"","sources":["../../src/services/quality-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { toMcpResponse } from "./register.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check if an action is within the rate limit.
|
|
4
|
+
*
|
|
5
|
+
* @param entityId - The user or agent ID performing the action
|
|
6
|
+
* @param action - Action name (e.g. "start_task", "complete_task")
|
|
7
|
+
* @param maxCalls - Max calls allowed in the window
|
|
8
|
+
* @param windowMs - Time window in milliseconds
|
|
9
|
+
* @returns null if allowed, or an MCP error response if rate-limited
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkRateLimit(entityId: string, action: string, maxCalls: number, windowMs: number): ReturnType<typeof toMcpResponse> | null;
|
|
12
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// services/rate-limit.ts — In-memory rate limiting
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Simple sliding-window rate limiter to prevent abuse of
|
|
5
|
+
// XP-generating tools (start_task, complete_task, etc.).
|
|
6
|
+
//
|
|
7
|
+
// This is an in-memory solution — limits reset on server restart.
|
|
8
|
+
// For production at scale, replace with Redis-based limiter.
|
|
9
|
+
// ============================================================
|
|
10
|
+
import { fail } from "./errors.js";
|
|
11
|
+
import { toMcpResponse } from "./register.js";
|
|
12
|
+
const buckets = new Map();
|
|
13
|
+
// Clean up stale entries every 10 minutes
|
|
14
|
+
setInterval(() => {
|
|
15
|
+
const cutoff = Date.now() - 3600_000; // 1 hour
|
|
16
|
+
for (const [key, entry] of buckets) {
|
|
17
|
+
entry.timestamps = entry.timestamps.filter((t) => t > cutoff);
|
|
18
|
+
if (entry.timestamps.length === 0)
|
|
19
|
+
buckets.delete(key);
|
|
20
|
+
}
|
|
21
|
+
}, 600_000);
|
|
22
|
+
/**
|
|
23
|
+
* Check if an action is within the rate limit.
|
|
24
|
+
*
|
|
25
|
+
* @param entityId - The user or agent ID performing the action
|
|
26
|
+
* @param action - Action name (e.g. "start_task", "complete_task")
|
|
27
|
+
* @param maxCalls - Max calls allowed in the window
|
|
28
|
+
* @param windowMs - Time window in milliseconds
|
|
29
|
+
* @returns null if allowed, or an MCP error response if rate-limited
|
|
30
|
+
*/
|
|
31
|
+
export function checkRateLimit(entityId, action, maxCalls, windowMs) {
|
|
32
|
+
const key = `${action}:${entityId}`;
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const cutoff = now - windowMs;
|
|
35
|
+
let entry = buckets.get(key);
|
|
36
|
+
if (!entry) {
|
|
37
|
+
entry = { timestamps: [] };
|
|
38
|
+
buckets.set(key, entry);
|
|
39
|
+
}
|
|
40
|
+
// Remove timestamps outside the window
|
|
41
|
+
entry.timestamps = entry.timestamps.filter((t) => t > cutoff);
|
|
42
|
+
if (entry.timestamps.length >= maxCalls) {
|
|
43
|
+
const windowMinutes = Math.round(windowMs / 60_000);
|
|
44
|
+
return toMcpResponse(fail(`超出速率限制:每${windowMinutes}分钟最多${maxCalls}次${action}调用`, "请等待后重试。此限制用于防止滥用。"));
|
|
45
|
+
}
|
|
46
|
+
entry.timestamps.push(now);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/services/rate-limit.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,mDAAmD;AACnD,+DAA+D;AAC/D,yDAAyD;AACzD,yDAAyD;AACzD,EAAE;AACF,kEAAkE;AAClE,6DAA6D;AAC7D,+DAA+D;AAE/D,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAM9C,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;AAElD,0CAA0C;AAC1C,WAAW,CAAC,GAAG,EAAE;IACf,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,SAAS;IAC/C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QAC9D,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;AACH,CAAC,EAAE,OAAO,CAAC,CAAC;AAEZ;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,MAAc,EACd,QAAgB,EAChB,QAAgB;IAEhB,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC;IAE9B,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,uCAAuC;IACvC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IAE9D,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QACxC,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC;QACpD,OAAO,aAAa,CAClB,IAAI,CACF,WAAW,aAAa,OAAO,QAAQ,IAAI,MAAM,IAAI,EACrD,mBAAmB,CACpB,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ToolResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* MCP tool annotations — metadata about what a tool does.
|
|
4
|
+
* These help LLMs decide which tools to call (and which to avoid).
|
|
5
|
+
*
|
|
6
|
+
* readOnlyHint: true = this tool only reads data, never changes anything
|
|
7
|
+
* destructiveHint: true = this tool deletes or irreversibly modifies data
|
|
8
|
+
* idempotentHint: true = calling it twice with same params gives same result
|
|
9
|
+
* openWorldHint: true = the tool talks to external systems (not just our DB)
|
|
10
|
+
*/
|
|
11
|
+
export interface ToolAnnotations {
|
|
12
|
+
readOnlyHint: boolean;
|
|
13
|
+
destructiveHint: boolean;
|
|
14
|
+
idempotentHint: boolean;
|
|
15
|
+
openWorldHint: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Standard annotations for read-only tools (get_*, list_*).
|
|
19
|
+
* Most of our tools are reads, so this is the most common set.
|
|
20
|
+
*/
|
|
21
|
+
export declare const READ_ONLY: ToolAnnotations;
|
|
22
|
+
/** For tools that create new records (register_*, start_*, create_*) */
|
|
23
|
+
export declare const CREATES: ToolAnnotations;
|
|
24
|
+
/** For tools that update existing records (update_*, complete_*) */
|
|
25
|
+
export declare const UPDATES: ToolAnnotations;
|
|
26
|
+
/** For tools that delete or irreversibly change data (merge_*, fail with cancel) */
|
|
27
|
+
export declare const DESTRUCTIVE: ToolAnnotations;
|
|
28
|
+
/**
|
|
29
|
+
* Convert a ToolResult into the MCP response format.
|
|
30
|
+
*
|
|
31
|
+
* The MCP protocol expects responses in a specific shape:
|
|
32
|
+
* { content: [{ type: "text", text: "..." }] }
|
|
33
|
+
*
|
|
34
|
+
* If our tool returned an error (success: false), we also set
|
|
35
|
+
* isError: true so the MCP client knows something went wrong.
|
|
36
|
+
*/
|
|
37
|
+
export declare function toMcpResponse(result: ToolResult): {
|
|
38
|
+
content: Array<{
|
|
39
|
+
type: "text";
|
|
40
|
+
text: string;
|
|
41
|
+
}>;
|
|
42
|
+
isError?: boolean;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Helper to log tool registration at startup.
|
|
46
|
+
* This helps with debugging — you can see exactly which tools loaded.
|
|
47
|
+
*/
|
|
48
|
+
export declare function logRegistered(toolName: string): void;
|
|
49
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// services/register.ts — Tool registration helper
|
|
3
|
+
// ============================================================
|
|
4
|
+
// This file provides a convenience wrapper around the MCP SDK's
|
|
5
|
+
// registerTool method. It standardizes how we register tools
|
|
6
|
+
// and ensures every tool follows the same patterns.
|
|
7
|
+
// ============================================================
|
|
8
|
+
import { formatToolResponse } from "./format.js";
|
|
9
|
+
/**
|
|
10
|
+
* Standard annotations for read-only tools (get_*, list_*).
|
|
11
|
+
* Most of our tools are reads, so this is the most common set.
|
|
12
|
+
*/
|
|
13
|
+
export const READ_ONLY = {
|
|
14
|
+
readOnlyHint: true,
|
|
15
|
+
destructiveHint: false,
|
|
16
|
+
idempotentHint: true,
|
|
17
|
+
openWorldHint: false,
|
|
18
|
+
};
|
|
19
|
+
/** For tools that create new records (register_*, start_*, create_*) */
|
|
20
|
+
export const CREATES = {
|
|
21
|
+
readOnlyHint: false,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
idempotentHint: false,
|
|
24
|
+
openWorldHint: false,
|
|
25
|
+
};
|
|
26
|
+
/** For tools that update existing records (update_*, complete_*) */
|
|
27
|
+
export const UPDATES = {
|
|
28
|
+
readOnlyHint: false,
|
|
29
|
+
destructiveHint: false,
|
|
30
|
+
idempotentHint: true,
|
|
31
|
+
openWorldHint: false,
|
|
32
|
+
};
|
|
33
|
+
/** For tools that delete or irreversibly change data (merge_*, fail with cancel) */
|
|
34
|
+
export const DESTRUCTIVE = {
|
|
35
|
+
readOnlyHint: false,
|
|
36
|
+
destructiveHint: true,
|
|
37
|
+
idempotentHint: false,
|
|
38
|
+
openWorldHint: false,
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Convert a ToolResult into the MCP response format.
|
|
42
|
+
*
|
|
43
|
+
* The MCP protocol expects responses in a specific shape:
|
|
44
|
+
* { content: [{ type: "text", text: "..." }] }
|
|
45
|
+
*
|
|
46
|
+
* If our tool returned an error (success: false), we also set
|
|
47
|
+
* isError: true so the MCP client knows something went wrong.
|
|
48
|
+
*/
|
|
49
|
+
export function toMcpResponse(result) {
|
|
50
|
+
const text = formatToolResponse(result);
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text }],
|
|
53
|
+
...(result.success === false ? { isError: true } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Helper to log tool registration at startup.
|
|
58
|
+
* This helps with debugging — you can see exactly which tools loaded.
|
|
59
|
+
*/
|
|
60
|
+
export function logRegistered(toolName) {
|
|
61
|
+
console.error(` ✓ Registered: ${toolName}`);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/services/register.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,kDAAkD;AAClD,+DAA+D;AAC/D,gEAAgE;AAChE,6DAA6D;AAC7D,oDAAoD;AACpD,+DAA+D;AAG/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAmBjD;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAoB;IACxC,YAAY,EAAE,IAAI;IAClB,eAAe,EAAE,KAAK;IACtB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,KAAK;CACrB,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,OAAO,GAAoB;IACtC,YAAY,EAAE,KAAK;IACnB,eAAe,EAAE,KAAK;IACtB,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,KAAK;CACrB,CAAC;AAEF,oEAAoE;AACpE,MAAM,CAAC,MAAM,OAAO,GAAoB;IACtC,YAAY,EAAE,KAAK;IACnB,eAAe,EAAE,KAAK;IACtB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,KAAK;CACrB,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,WAAW,GAAoB;IAC1C,YAAY,EAAE,KAAK;IACnB,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,KAAK;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,MAAkB;IAI9C,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC;QAC1C,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
/**
|
|
3
|
+
* Validate that required environment variables are set and
|
|
4
|
+
* initialize the Supabase client. Call this at server startup —
|
|
5
|
+
* if either variable is missing, we crash immediately with a
|
|
6
|
+
* helpful error message rather than failing mysteriously later.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateEnv(): void;
|
|
9
|
+
export declare const supabase: SupabaseClient<any, "public", "public", any, any>;
|
|
10
|
+
//# sourceMappingURL=supabase.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// services/supabase.ts — Database client setup
|
|
3
|
+
// ============================================================
|
|
4
|
+
// This file creates the Supabase client that ALL tools use to
|
|
5
|
+
// talk to your Postgres database. Think of it as the "phone
|
|
6
|
+
// line" between your MCP server and Supabase.
|
|
7
|
+
//
|
|
8
|
+
// HOW IT WORKS:
|
|
9
|
+
// - Zero-config: Falls back to the public anon key (RLS-protected)
|
|
10
|
+
// - Optional: Set env vars to use your own project or service_role key
|
|
11
|
+
//
|
|
12
|
+
// KEY TYPES:
|
|
13
|
+
// anon key (default): Safe to expose. Respects Row Level Security.
|
|
14
|
+
// All operations go through RLS policies on the database.
|
|
15
|
+
// service_role key (optional): Bypasses RLS. Only set this if you
|
|
16
|
+
// need admin access or are running a private instance.
|
|
17
|
+
// ============================================================
|
|
18
|
+
import { createClient } from "@supabase/supabase-js";
|
|
19
|
+
// Default public project credentials (anon key — safe to expose, RLS-protected)
|
|
20
|
+
const DEFAULT_SUPABASE_URL = "https://pxanrmpwptinbtwigqdt.supabase.co";
|
|
21
|
+
const DEFAULT_SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB4YW5ybXB3cHRpbmJ0d2lncWR0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzMzMzE0MTUsImV4cCI6MjA4ODkwNzQxNX0.UES9REdFSED5Sl7VT_2tN_96rEgiFFZiXWR5-jflJL0";
|
|
22
|
+
const supabaseUrl = process.env.SUPABASE_URL || DEFAULT_SUPABASE_URL;
|
|
23
|
+
const supabaseKey = process.env.SUPABASE_SERVICE_KEY || DEFAULT_SUPABASE_KEY;
|
|
24
|
+
/**
|
|
25
|
+
* Validate that required environment variables are set and
|
|
26
|
+
* initialize the Supabase client. Call this at server startup —
|
|
27
|
+
* if either variable is missing, we crash immediately with a
|
|
28
|
+
* helpful error message rather than failing mysteriously later.
|
|
29
|
+
*/
|
|
30
|
+
export function validateEnv() {
|
|
31
|
+
const usingDefaults = !process.env.SUPABASE_URL && !process.env.SUPABASE_SERVICE_KEY;
|
|
32
|
+
if (usingDefaults) {
|
|
33
|
+
console.error(" ℹ️ Using default public project (anon key, RLS-protected)");
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// If user set one but not both, warn
|
|
37
|
+
if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_KEY) {
|
|
38
|
+
console.error(`\n⚠️ Partial config: set BOTH SUPABASE_URL and SUPABASE_SERVICE_KEY, or neither (for defaults).\n` +
|
|
39
|
+
` Falling back to defaults for missing values.\n`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
_supabase = createClient(supabaseUrl, supabaseKey, {
|
|
43
|
+
auth: {
|
|
44
|
+
autoRefreshToken: false,
|
|
45
|
+
persistSession: false,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The Supabase client instance.
|
|
51
|
+
*
|
|
52
|
+
* Usage in tool files:
|
|
53
|
+
* import { supabase } from "../services/supabase.js";
|
|
54
|
+
*
|
|
55
|
+
* const { data, error } = await supabase
|
|
56
|
+
* .from("users")
|
|
57
|
+
* .select("*")
|
|
58
|
+
* .eq("id", userId);
|
|
59
|
+
*
|
|
60
|
+
* Common patterns:
|
|
61
|
+
* .select("*") → get all columns
|
|
62
|
+
* .select("id, name") → get specific columns
|
|
63
|
+
* .eq("id", value) → WHERE id = value
|
|
64
|
+
* .ilike("name", "%pat") → case-insensitive LIKE
|
|
65
|
+
* .order("created_at", { ascending: false }) → ORDER BY
|
|
66
|
+
* .range(0, 19) → LIMIT 20, OFFSET 0
|
|
67
|
+
* .single() → expect exactly one row
|
|
68
|
+
* .maybeSingle() → expect 0 or 1 rows
|
|
69
|
+
*/
|
|
70
|
+
let _supabase;
|
|
71
|
+
export const supabase = new Proxy({}, {
|
|
72
|
+
get(_target, prop, receiver) {
|
|
73
|
+
if (!_supabase) {
|
|
74
|
+
throw new Error("Supabase client not initialized. Call validateEnv() first.");
|
|
75
|
+
}
|
|
76
|
+
return Reflect.get(_supabase, prop, receiver);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=supabase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase.js","sourceRoot":"","sources":["../../src/services/supabase.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+CAA+C;AAC/C,+DAA+D;AAC/D,8DAA8D;AAC9D,4DAA4D;AAC5D,8CAA8C;AAC9C,EAAE;AACF,gBAAgB;AAChB,mEAAmE;AACnE,uEAAuE;AACvE,EAAE;AACF,aAAa;AACb,qEAAqE;AACrE,8DAA8D;AAC9D,oEAAoE;AACpE,2DAA2D;AAC3D,+DAA+D;AAE/D,OAAO,EAAE,YAAY,EAAkB,MAAM,uBAAuB,CAAC;AAErE,gFAAgF;AAChF,MAAM,oBAAoB,GAAG,0CAA0C,CAAC;AACxE,MAAM,oBAAoB,GAAG,kNAAkN,CAAC;AAEhP,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,oBAAoB,CAAC;AACrE,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,oBAAoB,CAAC;AAE7E;;;;;GAKG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,aAAa,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAErF,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAChF,CAAC;SAAM,CAAC;QACN,qCAAqC;QACrC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC;YACnE,OAAO,CAAC,KAAK,CACX,oGAAoG;gBACpG,kDAAkD,CACnD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,SAAS,GAAG,YAAY,CAAC,WAAW,EAAE,WAAW,EAAE;QACjD,IAAI,EAAE;YACJ,gBAAgB,EAAE,KAAK;YACvB,cAAc,EAAE,KAAK;SACtB;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,IAAI,SAAyB,CAAC;AAE9B,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,EAAoB,EAAE;IACtD,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ;QACzB,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { ActionHandler } from "../services/dispatcher.js";
|
|
3
|
+
export declare const getAchievementsHandler: ActionHandler;
|
|
4
|
+
export declare const scanAchievementsHandler: ActionHandler;
|
|
5
|
+
export declare function registerAchievementTools(server: McpServer): void;
|
|
6
|
+
//# sourceMappingURL=achievements.d.ts.map
|