@cocaxcode/logbook-mcp 0.2.0 → 0.3.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/dist/index.js
CHANGED
|
@@ -6,13 +6,13 @@ async function main() {
|
|
|
6
6
|
const hasMcpFlag = argv.includes("--mcp");
|
|
7
7
|
if (hasMcpFlag) {
|
|
8
8
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
9
|
-
const { createServer } = await import("./server-
|
|
9
|
+
const { createServer } = await import("./server-YXLXUSPK.js");
|
|
10
10
|
const server = createServer();
|
|
11
11
|
const transport = new StdioServerTransport();
|
|
12
12
|
await server.connect(transport);
|
|
13
13
|
console.error("logbook-mcp server running on stdio");
|
|
14
14
|
} else {
|
|
15
|
-
const { runCli } = await import("./cli-
|
|
15
|
+
const { runCli } = await import("./cli-VGOVRY45.js");
|
|
16
16
|
await runCli(argv);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -45,6 +45,7 @@ CREATE TABLE IF NOT EXISTS todos (
|
|
|
45
45
|
content TEXT NOT NULL,
|
|
46
46
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
47
47
|
priority TEXT NOT NULL DEFAULT 'normal',
|
|
48
|
+
remind_at TEXT,
|
|
48
49
|
completed_at TEXT,
|
|
49
50
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
50
51
|
);
|
|
@@ -56,6 +57,7 @@ CREATE INDEX IF NOT EXISTS idx_todos_repo ON todos(repo_id);
|
|
|
56
57
|
CREATE INDEX IF NOT EXISTS idx_todos_topic ON todos(topic_id);
|
|
57
58
|
CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status);
|
|
58
59
|
CREATE INDEX IF NOT EXISTS idx_todos_date ON todos(created_at);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_todos_remind ON todos(remind_at);
|
|
59
61
|
|
|
60
62
|
CREATE TABLE IF NOT EXISTS code_todo_snapshots (
|
|
61
63
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -114,7 +116,8 @@ INSERT OR IGNORE INTO topics (name, description, commit_prefix, is_custom) VALUE
|
|
|
114
116
|
('chore', 'Mantenimiento general', 'refactor,docs,ci,build,chore,test,perf', 0),
|
|
115
117
|
('idea', 'Ideas y propuestas futuras', NULL, 0),
|
|
116
118
|
('decision', 'Decisiones tomadas', NULL, 0),
|
|
117
|
-
('blocker', 'Bloqueos activos', NULL, 0)
|
|
119
|
+
('blocker', 'Bloqueos activos', NULL, 0),
|
|
120
|
+
('reminder', 'Recordatorios con fecha', NULL, 0);
|
|
118
121
|
`;
|
|
119
122
|
|
|
120
123
|
// src/db/connection.ts
|
|
@@ -207,11 +210,11 @@ var TODO_WITH_META_SQL = `
|
|
|
207
210
|
LEFT JOIN repos r ON t.repo_id = r.id
|
|
208
211
|
LEFT JOIN topics tp ON t.topic_id = tp.id
|
|
209
212
|
`;
|
|
210
|
-
function insertTodo(db2, repoId, topicId, content, priority = "normal") {
|
|
213
|
+
function insertTodo(db2, repoId, topicId, content, priority = "normal", remindAt) {
|
|
211
214
|
const stmt = db2.prepare(
|
|
212
|
-
"INSERT INTO todos (repo_id, topic_id, content, priority) VALUES (?, ?, ?, ?)"
|
|
215
|
+
"INSERT INTO todos (repo_id, topic_id, content, priority, remind_at) VALUES (?, ?, ?, ?, ?)"
|
|
213
216
|
);
|
|
214
|
-
const result = stmt.run(repoId, topicId, content, priority);
|
|
217
|
+
const result = stmt.run(repoId, topicId, content, priority, remindAt ?? null);
|
|
215
218
|
return db2.prepare(`${TODO_WITH_META_SQL} WHERE t.id = ?`).get(result.lastInsertRowid);
|
|
216
219
|
}
|
|
217
220
|
function getTodos(db2, filters = {}) {
|
|
@@ -365,6 +368,43 @@ function getCompletedTodos(db2, filters = {}) {
|
|
|
365
368
|
`${TODO_WITH_META_SQL} WHERE ${where} ORDER BY t.completed_at DESC`
|
|
366
369
|
).all(...params);
|
|
367
370
|
}
|
|
371
|
+
function getDueReminders(db2) {
|
|
372
|
+
const now = /* @__PURE__ */ new Date();
|
|
373
|
+
const today = now.toISOString().split("T")[0];
|
|
374
|
+
const tomorrow = new Date(now.getTime() + 864e5).toISOString().split("T")[0];
|
|
375
|
+
const todayItems = db2.prepare(
|
|
376
|
+
`${TODO_WITH_META_SQL} WHERE t.status = 'pending' AND t.remind_at >= ? AND t.remind_at < ? ORDER BY t.remind_at`
|
|
377
|
+
).all(today, tomorrow);
|
|
378
|
+
const overdueItems = db2.prepare(
|
|
379
|
+
`${TODO_WITH_META_SQL} WHERE t.status = 'pending' AND t.remind_at < ? ORDER BY t.remind_at`
|
|
380
|
+
).all(today);
|
|
381
|
+
return {
|
|
382
|
+
today: groupByRepo(todayItems),
|
|
383
|
+
overdue: groupByRepo(overdueItems)
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function groupByRepo(items) {
|
|
387
|
+
const map = /* @__PURE__ */ new Map();
|
|
388
|
+
for (const item of items) {
|
|
389
|
+
const key = item.repo_name ?? "global";
|
|
390
|
+
if (!map.has(key)) map.set(key, []);
|
|
391
|
+
map.get(key).push(item);
|
|
392
|
+
}
|
|
393
|
+
return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([repo_name, reminders]) => ({
|
|
394
|
+
repo_name: repo_name === "global" ? null : repo_name,
|
|
395
|
+
reminders
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
function resolveTopicId(db2, name) {
|
|
399
|
+
const existing = getTopicByName(db2, name);
|
|
400
|
+
if (existing) return existing.id;
|
|
401
|
+
const normalized = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
402
|
+
if (!normalized) return getTopicByName(db2, "chore").id;
|
|
403
|
+
const existingNorm = getTopicByName(db2, normalized);
|
|
404
|
+
if (existingNorm) return existingNorm.id;
|
|
405
|
+
const created = insertTopic(db2, normalized);
|
|
406
|
+
return created.id;
|
|
407
|
+
}
|
|
368
408
|
function syncCodeTodos(db2, repoId, currentTodos) {
|
|
369
409
|
const sync = db2.transaction(() => {
|
|
370
410
|
const existing = db2.prepare("SELECT * FROM code_todo_snapshots WHERE repo_id = ? AND resolved_at IS NULL").all(repoId);
|
|
@@ -507,21 +547,7 @@ function registerNoteTool(server) {
|
|
|
507
547
|
try {
|
|
508
548
|
const db2 = getDb();
|
|
509
549
|
const repo = autoRegisterRepo(db2);
|
|
510
|
-
|
|
511
|
-
if (topic) {
|
|
512
|
-
const topicRow = getTopicByName(db2, topic);
|
|
513
|
-
if (!topicRow) {
|
|
514
|
-
const available = getAllTopics(db2);
|
|
515
|
-
return {
|
|
516
|
-
isError: true,
|
|
517
|
-
content: [{
|
|
518
|
-
type: "text",
|
|
519
|
-
text: `Topic "${topic}" no existe. Disponibles: ${available.map((t) => t.name).join(", ")}`
|
|
520
|
-
}]
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
topicId = topicRow.id;
|
|
524
|
-
}
|
|
550
|
+
const topicId = topic ? resolveTopicId(db2, topic) : null;
|
|
525
551
|
const note = insertNote(db2, repo?.id ?? null, topicId, content);
|
|
526
552
|
return {
|
|
527
553
|
content: [{ type: "text", text: JSON.stringify(note) }]
|
|
@@ -547,15 +573,17 @@ function registerTodoAddTool(server) {
|
|
|
547
573
|
content: z3.string().min(1).max(2e3).optional().describe("Contenido del TODO (para crear uno solo)"),
|
|
548
574
|
topic: z3.string().optional().describe("Topic para el TODO individual"),
|
|
549
575
|
priority: priorityEnum.optional().default("normal").describe("Prioridad del TODO individual"),
|
|
576
|
+
remind_at: z3.string().optional().describe('Fecha recordatorio (YYYY-MM-DD). Se asigna topic "reminder" automaticamente.'),
|
|
550
577
|
items: z3.array(
|
|
551
578
|
z3.object({
|
|
552
579
|
content: z3.string().min(1).max(2e3).describe("Contenido del TODO"),
|
|
553
580
|
topic: z3.string().optional().describe("Topic"),
|
|
554
|
-
priority: priorityEnum.optional().default("normal").describe("Prioridad")
|
|
581
|
+
priority: priorityEnum.optional().default("normal").describe("Prioridad"),
|
|
582
|
+
remind_at: z3.string().optional().describe("Fecha recordatorio (YYYY-MM-DD)")
|
|
555
583
|
})
|
|
556
584
|
).max(50).optional().describe("Array de TODOs para crear varios a la vez (max 50)")
|
|
557
585
|
},
|
|
558
|
-
async ({ content, topic, priority, items }) => {
|
|
586
|
+
async ({ content, topic, priority, remind_at, items }) => {
|
|
559
587
|
try {
|
|
560
588
|
if (!content && (!items || items.length === 0)) {
|
|
561
589
|
return {
|
|
@@ -569,30 +597,18 @@ function registerTodoAddTool(server) {
|
|
|
569
597
|
const db2 = getDb();
|
|
570
598
|
const repo = autoRegisterRepo(db2);
|
|
571
599
|
const repoId = repo?.id ?? null;
|
|
572
|
-
const todoItems = items ? items : [{ content, topic, priority: priority ?? "normal" }];
|
|
600
|
+
const todoItems = items ? items : [{ content, topic, priority: priority ?? "normal", remind_at }];
|
|
573
601
|
const results = [];
|
|
574
602
|
for (const item of todoItems) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const topicRow = getTopicByName(db2, item.topic);
|
|
578
|
-
if (!topicRow) {
|
|
579
|
-
const available = getAllTopics(db2);
|
|
580
|
-
return {
|
|
581
|
-
isError: true,
|
|
582
|
-
content: [{
|
|
583
|
-
type: "text",
|
|
584
|
-
text: `Topic "${item.topic}" no existe. Disponibles: ${available.map((t) => t.name).join(", ")}`
|
|
585
|
-
}]
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
topicId = topicRow.id;
|
|
589
|
-
}
|
|
603
|
+
const effectiveTopic = item.remind_at && !item.topic ? "reminder" : item.topic;
|
|
604
|
+
const topicId = effectiveTopic ? resolveTopicId(db2, effectiveTopic) : null;
|
|
590
605
|
const todo = insertTodo(
|
|
591
606
|
db2,
|
|
592
607
|
repoId,
|
|
593
608
|
topicId,
|
|
594
609
|
item.content,
|
|
595
|
-
item.priority ?? "normal"
|
|
610
|
+
item.priority ?? "normal",
|
|
611
|
+
item.remind_at
|
|
596
612
|
);
|
|
597
613
|
results.push(todo);
|
|
598
614
|
}
|
|
@@ -787,21 +803,7 @@ function registerTodoEditTool(server) {
|
|
|
787
803
|
async ({ id, content, topic, priority }) => {
|
|
788
804
|
try {
|
|
789
805
|
const db2 = getDb();
|
|
790
|
-
|
|
791
|
-
if (topic) {
|
|
792
|
-
const topicRow = getTopicByName(db2, topic);
|
|
793
|
-
if (!topicRow) {
|
|
794
|
-
const available = getAllTopics(db2);
|
|
795
|
-
return {
|
|
796
|
-
isError: true,
|
|
797
|
-
content: [{
|
|
798
|
-
type: "text",
|
|
799
|
-
text: `Topic "${topic}" no existe. Disponibles: ${available.map((t) => t.name).join(", ")}`
|
|
800
|
-
}]
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
topicId = topicRow.id;
|
|
804
|
-
}
|
|
806
|
+
const topicId = topic ? resolveTopicId(db2, topic) : void 0;
|
|
805
807
|
const updated = updateTodo(db2, id, { content, topicId, priority });
|
|
806
808
|
if (!updated) {
|
|
807
809
|
return {
|
|
@@ -1026,8 +1028,44 @@ function registerSearchTool(server) {
|
|
|
1026
1028
|
);
|
|
1027
1029
|
}
|
|
1028
1030
|
|
|
1031
|
+
// src/resources/reminders.ts
|
|
1032
|
+
function registerRemindersResource(server) {
|
|
1033
|
+
server.resource(
|
|
1034
|
+
"reminders",
|
|
1035
|
+
"logbook://reminders",
|
|
1036
|
+
{
|
|
1037
|
+
description: "Recordatorios pendientes para hoy y atrasados, agrupados por proyecto",
|
|
1038
|
+
mimeType: "application/json"
|
|
1039
|
+
},
|
|
1040
|
+
async (uri) => {
|
|
1041
|
+
try {
|
|
1042
|
+
const db2 = getDb();
|
|
1043
|
+
const { today, overdue } = getDueReminders(db2);
|
|
1044
|
+
const hasReminders = today.length > 0 || overdue.length > 0;
|
|
1045
|
+
return {
|
|
1046
|
+
contents: [{
|
|
1047
|
+
uri: uri.href,
|
|
1048
|
+
text: JSON.stringify({
|
|
1049
|
+
hasReminders,
|
|
1050
|
+
today,
|
|
1051
|
+
overdue
|
|
1052
|
+
})
|
|
1053
|
+
}]
|
|
1054
|
+
};
|
|
1055
|
+
} catch {
|
|
1056
|
+
return {
|
|
1057
|
+
contents: [{
|
|
1058
|
+
uri: uri.href,
|
|
1059
|
+
text: JSON.stringify({ hasReminders: false, today: [], overdue: [] })
|
|
1060
|
+
}]
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1029
1067
|
// src/server.ts
|
|
1030
|
-
var VERSION = true ? "0.
|
|
1068
|
+
var VERSION = true ? "0.3.0" : "0.0.0";
|
|
1031
1069
|
function createServer() {
|
|
1032
1070
|
const server = new McpServer({
|
|
1033
1071
|
name: "logbook-mcp",
|
|
@@ -1042,6 +1080,7 @@ function createServer() {
|
|
|
1042
1080
|
registerTodoRmTool(server);
|
|
1043
1081
|
registerLogTool(server);
|
|
1044
1082
|
registerSearchTool(server);
|
|
1083
|
+
registerRemindersResource(server);
|
|
1045
1084
|
return server;
|
|
1046
1085
|
}
|
|
1047
1086
|
export {
|