libero-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,60 @@
1
+ export function createTimeBlockTools(repo) {
2
+ return {
3
+ async create(input) {
4
+ if (!input.title || input.title.trim() === "") {
5
+ throw new Error("事件名不能为空");
6
+ }
7
+ if (input.rating !== undefined && (input.rating < 1 || input.rating > 5)) {
8
+ throw new Error("评分必须在 1-5 之间");
9
+ }
10
+ if (input.status !== undefined && input.status !== "planned" && input.status !== "completed") {
11
+ throw new Error("status 只能为 planned 或 completed");
12
+ }
13
+ if (input.start_time > input.end_time) {
14
+ throw new Error("开始时间不能晚于结束时间");
15
+ }
16
+ const userExists = await repo.userExists(input.user_id);
17
+ if (!userExists) {
18
+ throw new Error("用户不存在");
19
+ }
20
+ return repo.create(input);
21
+ },
22
+ async getById(id) {
23
+ return repo.getById(id);
24
+ },
25
+ async list(filters) {
26
+ if (!filters.user_id) {
27
+ throw new Error("user_id 为必填参数");
28
+ }
29
+ return repo.list(filters);
30
+ },
31
+ async update(id, input) {
32
+ const existing = await repo.getById(id);
33
+ if (!existing) {
34
+ throw new Error("时间块不存在");
35
+ }
36
+ if (input.rating !== undefined && (input.rating < 1 || input.rating > 5)) {
37
+ throw new Error("评分必须在 1-5 之间");
38
+ }
39
+ if (input.status !== undefined && input.status !== "planned" && input.status !== "completed") {
40
+ throw new Error("status 只能为 planned 或 completed");
41
+ }
42
+ const newStart = input.start_time ?? existing.start_time;
43
+ const newEnd = input.end_time ?? existing.end_time;
44
+ if (newStart > newEnd) {
45
+ throw new Error("开始时间不能晚于结束时间");
46
+ }
47
+ return repo.update(id, input);
48
+ },
49
+ async remove(id) {
50
+ const exists = await repo.exists(id);
51
+ if (!exists) {
52
+ throw new Error("时间块不存在");
53
+ }
54
+ return repo.remove(id);
55
+ },
56
+ async restore(id) {
57
+ return repo.restore(id);
58
+ },
59
+ };
60
+ }
@@ -0,0 +1,116 @@
1
+ // ── pure helpers (algorithm = verbatim copy of original memory logic) ──
2
+ function nowISO() {
3
+ return new Date().toISOString();
4
+ }
5
+ const IDLE_VIEW = {
6
+ status: "idle",
7
+ event: null,
8
+ startedAt: null,
9
+ pausedAt: null,
10
+ accumulatedMs: 0,
11
+ };
12
+ function toView(session) {
13
+ if (session.status === "stopped")
14
+ return { ...IDLE_VIEW };
15
+ return {
16
+ status: session.status,
17
+ event: session.event,
18
+ categoryId: session.category_id ?? undefined,
19
+ startedAt: session.started_at,
20
+ pausedAt: session.status === "paused" ? session.updated_at : null,
21
+ accumulatedMs: session.accumulated_ms,
22
+ };
23
+ }
24
+ function computeElapsed(session) {
25
+ if (session.status === "stopped")
26
+ return 0;
27
+ if (session.status === "paused")
28
+ return session.accumulated_ms;
29
+ // running
30
+ return (session.accumulated_ms +
31
+ (Date.now() - new Date(session.started_at).getTime()));
32
+ }
33
+ // ── factory ──
34
+ export function createTimerTools(repo) {
35
+ const start = async (userId, event, categoryId) => {
36
+ if (!event || event.trim() === "") {
37
+ throw new Error("事件名不能为空");
38
+ }
39
+ const userExists = await repo.userExists(userId);
40
+ if (!userExists) {
41
+ throw new Error("用户不存在");
42
+ }
43
+ const active = await repo.getActive(userId);
44
+ if (active) {
45
+ throw new Error(`当前正在计时「${active.event}」,请先停止后再开始新的计时`);
46
+ }
47
+ const session = await repo.create({
48
+ user_id: userId,
49
+ event: event.trim(),
50
+ category_id: categoryId,
51
+ started_at: nowISO(),
52
+ });
53
+ return toView(session);
54
+ };
55
+ const pause = async (userId) => {
56
+ const active = await repo.getActive(userId);
57
+ if (!active || active.status !== "running") {
58
+ throw new Error("当前没有运行中的计时");
59
+ }
60
+ const segmentMs = Date.now() - new Date(active.started_at).getTime();
61
+ const accumulated = active.accumulated_ms + segmentMs;
62
+ const session = await repo.update(active.id, {
63
+ status: "paused",
64
+ accumulated_ms: accumulated,
65
+ });
66
+ return toView(session);
67
+ };
68
+ const resume = async (userId) => {
69
+ const active = await repo.getActive(userId);
70
+ if (active && active.status === "running") {
71
+ throw new Error("计时已在运行中");
72
+ }
73
+ if (!active || active.status !== "paused") {
74
+ throw new Error("没有可恢复的暂停计时");
75
+ }
76
+ const session = await repo.update(active.id, {
77
+ status: "running",
78
+ started_at: nowISO(),
79
+ });
80
+ return toView(session);
81
+ };
82
+ const stop = async (userId) => {
83
+ const active = await repo.getActive(userId);
84
+ if (!active ||
85
+ (active.status !== "running" && active.status !== "paused")) {
86
+ throw new Error("当前没有运行中的计时");
87
+ }
88
+ const endedAt = nowISO();
89
+ const totalMs = computeElapsed(active);
90
+ await repo.update(active.id, {
91
+ status: "stopped",
92
+ ended_at: endedAt,
93
+ });
94
+ const record = {
95
+ event: active.event,
96
+ categoryId: active.category_id ?? undefined,
97
+ startedAt: active.started_at,
98
+ endedAt,
99
+ elapsedMs: totalMs,
100
+ };
101
+ return record;
102
+ };
103
+ const status = async (userId) => {
104
+ const active = await repo.getActive(userId);
105
+ if (!active)
106
+ return { ...IDLE_VIEW };
107
+ return toView(active);
108
+ };
109
+ const elapsed = async (userId) => {
110
+ const active = await repo.getActive(userId);
111
+ if (!active)
112
+ return 0;
113
+ return computeElapsed(active);
114
+ };
115
+ return { start, pause, resume, stop, status, elapsed };
116
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "libero-mcp",
3
+ "version": "0.1.0",
4
+ "description": "AI 时间管理教练 MCP 工具集——时间块记录、统计、矩阵诊断、计时、分类、profile",
5
+ "license": "MIT",
6
+ "author": "amosyuan",
7
+ "homepage": "https://github.com/beimingju-dafu/libero-2",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/beimingju-dafu/libero-2.git"
11
+ },
12
+ "keywords": ["mcp", "model-context-protocol", "time-tracking", "productivity", "coach", "ai"],
13
+ "type": "module",
14
+ "bin": {
15
+ "libero-mcp": "./dist/mcp/server.js"
16
+ },
17
+ "scripts": {
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "db:init": "tsx data/db.ts",
21
+ "build": "tsc -p tsconfig.build.json && cp -r data/migrations dist/data/ && cp data/schema.sql dist/data/",
22
+ "prepublishOnly": "npm test && npm run build",
23
+ "knowledge:validate": "tsx knowledge/validate.ts"
24
+ },
25
+ "files": ["dist", "data/migrations", "data/schema.sql"],
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.29.0",
28
+ "better-sqlite3": "^12.10.0",
29
+ "js-yaml": "^4.2.0",
30
+ "zod": "^4.4.3"
31
+ },
32
+ "devDependencies": {
33
+ "@types/better-sqlite3": "^7.6.0",
34
+ "@types/js-yaml": "^4.0.9",
35
+ "@types/node": "^20.0.0",
36
+ "tsx": "^4.0.0",
37
+ "typescript": "^5.4.0",
38
+ "vitest": "^1.6.0"
39
+ }
40
+ }