@cocaxcode/logbook-mcp 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ 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-2NMQDQSU.js");
|
|
10
10
|
const server = createServer();
|
|
11
11
|
const transport = new StdioServerTransport();
|
|
12
12
|
await server.connect(transport);
|
|
@@ -46,6 +46,8 @@ CREATE TABLE IF NOT EXISTS todos (
|
|
|
46
46
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
47
47
|
priority TEXT NOT NULL DEFAULT 'normal',
|
|
48
48
|
remind_at TEXT,
|
|
49
|
+
remind_pattern TEXT,
|
|
50
|
+
remind_last_done TEXT,
|
|
49
51
|
completed_at TEXT,
|
|
50
52
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
51
53
|
);
|
|
@@ -210,11 +212,11 @@ var TODO_WITH_META_SQL = `
|
|
|
210
212
|
LEFT JOIN repos r ON t.repo_id = r.id
|
|
211
213
|
LEFT JOIN topics tp ON t.topic_id = tp.id
|
|
212
214
|
`;
|
|
213
|
-
function insertTodo(db2, repoId, topicId, content, priority = "normal", remindAt) {
|
|
215
|
+
function insertTodo(db2, repoId, topicId, content, priority = "normal", remindAt, remindPattern) {
|
|
214
216
|
const stmt = db2.prepare(
|
|
215
|
-
"INSERT INTO todos (repo_id, topic_id, content, priority, remind_at) VALUES (?, ?, ?, ?, ?)"
|
|
217
|
+
"INSERT INTO todos (repo_id, topic_id, content, priority, remind_at, remind_pattern) VALUES (?, ?, ?, ?, ?, ?)"
|
|
216
218
|
);
|
|
217
|
-
const result = stmt.run(repoId, topicId, content, priority, remindAt ?? null);
|
|
219
|
+
const result = stmt.run(repoId, topicId, content, priority, remindAt ?? null, remindPattern ?? null);
|
|
218
220
|
return db2.prepare(`${TODO_WITH_META_SQL} WHERE t.id = ?`).get(result.lastInsertRowid);
|
|
219
221
|
}
|
|
220
222
|
function getTodos(db2, filters = {}) {
|
|
@@ -376,13 +378,39 @@ function getDueReminders(db2) {
|
|
|
376
378
|
`${TODO_WITH_META_SQL} WHERE t.status = 'pending' AND t.remind_at >= ? AND t.remind_at < ? ORDER BY t.remind_at`
|
|
377
379
|
).all(today, tomorrow);
|
|
378
380
|
const overdueItems = db2.prepare(
|
|
379
|
-
`${TODO_WITH_META_SQL} WHERE t.status = 'pending' AND t.remind_at < ? ORDER BY t.remind_at`
|
|
381
|
+
`${TODO_WITH_META_SQL} WHERE t.status = 'pending' AND t.remind_at IS NOT NULL AND t.remind_at < ? AND t.remind_pattern IS NULL ORDER BY t.remind_at`
|
|
380
382
|
).all(today);
|
|
383
|
+
const recurringAll = db2.prepare(
|
|
384
|
+
`${TODO_WITH_META_SQL} WHERE t.remind_pattern IS NOT NULL AND (t.remind_last_done IS NULL OR t.remind_last_done < ?)`
|
|
385
|
+
).all(today);
|
|
386
|
+
const recurringToday = recurringAll.filter((r) => matchesPattern(r.remind_pattern, now));
|
|
387
|
+
const hasAny = todayItems.length > 0 || overdueItems.length > 0 || recurringToday.length > 0;
|
|
388
|
+
if (!hasAny) return null;
|
|
381
389
|
return {
|
|
382
390
|
today: groupByRepo(todayItems),
|
|
383
|
-
overdue: groupByRepo(overdueItems)
|
|
391
|
+
overdue: groupByRepo(overdueItems),
|
|
392
|
+
recurring: groupByRepo(recurringToday)
|
|
384
393
|
};
|
|
385
394
|
}
|
|
395
|
+
function ackRecurringReminder(db2, id) {
|
|
396
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
397
|
+
db2.prepare("UPDATE todos SET remind_last_done = ? WHERE id = ?").run(today, id);
|
|
398
|
+
}
|
|
399
|
+
function matchesPattern(pattern, date) {
|
|
400
|
+
const dayOfWeek = date.getDay();
|
|
401
|
+
const dayOfMonth = date.getDate();
|
|
402
|
+
if (pattern === "daily") return true;
|
|
403
|
+
if (pattern === "weekdays") return dayOfWeek >= 1 && dayOfWeek <= 5;
|
|
404
|
+
if (pattern.startsWith("weekly:")) {
|
|
405
|
+
const days = pattern.slice(7).split(",").map(Number);
|
|
406
|
+
return days.some((d) => d % 7 === dayOfWeek);
|
|
407
|
+
}
|
|
408
|
+
if (pattern.startsWith("monthly:")) {
|
|
409
|
+
const days = pattern.slice(8).split(",").map(Number);
|
|
410
|
+
return days.includes(dayOfMonth);
|
|
411
|
+
}
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
386
414
|
function groupByRepo(items) {
|
|
387
415
|
const map = /* @__PURE__ */ new Map();
|
|
388
416
|
for (const item of items) {
|
|
@@ -573,17 +601,19 @@ function registerTodoAddTool(server) {
|
|
|
573
601
|
content: z3.string().min(1).max(2e3).optional().describe("Contenido del TODO (para crear uno solo)"),
|
|
574
602
|
topic: z3.string().optional().describe("Topic para el TODO individual"),
|
|
575
603
|
priority: priorityEnum.optional().default("normal").describe("Prioridad del TODO individual"),
|
|
576
|
-
remind_at: z3.string().optional().describe(
|
|
604
|
+
remind_at: z3.string().optional().describe("Fecha recordatorio unica (YYYY-MM-DD)"),
|
|
605
|
+
remind_pattern: z3.string().optional().describe("Patron recurrente: daily, weekdays, weekly:2 (martes), weekly:1,3 (lun+mie), monthly:1 (dia 1), monthly:1,15"),
|
|
577
606
|
items: z3.array(
|
|
578
607
|
z3.object({
|
|
579
608
|
content: z3.string().min(1).max(2e3).describe("Contenido del TODO"),
|
|
580
609
|
topic: z3.string().optional().describe("Topic"),
|
|
581
610
|
priority: priorityEnum.optional().default("normal").describe("Prioridad"),
|
|
582
|
-
remind_at: z3.string().optional().describe("Fecha recordatorio (YYYY-MM-DD)")
|
|
611
|
+
remind_at: z3.string().optional().describe("Fecha recordatorio (YYYY-MM-DD)"),
|
|
612
|
+
remind_pattern: z3.string().optional().describe("Patron recurrente")
|
|
583
613
|
})
|
|
584
614
|
).max(50).optional().describe("Array de TODOs para crear varios a la vez (max 50)")
|
|
585
615
|
},
|
|
586
|
-
async ({ content, topic, priority, remind_at, items }) => {
|
|
616
|
+
async ({ content, topic, priority, remind_at, remind_pattern, items }) => {
|
|
587
617
|
try {
|
|
588
618
|
if (!content && (!items || items.length === 0)) {
|
|
589
619
|
return {
|
|
@@ -597,10 +627,11 @@ function registerTodoAddTool(server) {
|
|
|
597
627
|
const db2 = getDb();
|
|
598
628
|
const repo = autoRegisterRepo(db2);
|
|
599
629
|
const repoId = repo?.id ?? null;
|
|
600
|
-
const todoItems = items ? items : [{ content, topic, priority: priority ?? "normal", remind_at }];
|
|
630
|
+
const todoItems = items ? items : [{ content, topic, priority: priority ?? "normal", remind_at, remind_pattern }];
|
|
601
631
|
const results = [];
|
|
602
632
|
for (const item of todoItems) {
|
|
603
|
-
const
|
|
633
|
+
const hasReminder = item.remind_at || item.remind_pattern;
|
|
634
|
+
const effectiveTopic = hasReminder && !item.topic ? "reminder" : item.topic;
|
|
604
635
|
const topicId = effectiveTopic ? resolveTopicId(db2, effectiveTopic) : null;
|
|
605
636
|
const todo = insertTodo(
|
|
606
637
|
db2,
|
|
@@ -608,7 +639,8 @@ function registerTodoAddTool(server) {
|
|
|
608
639
|
topicId,
|
|
609
640
|
item.content,
|
|
610
641
|
item.priority ?? "normal",
|
|
611
|
-
item.remind_at
|
|
642
|
+
item.remind_at,
|
|
643
|
+
item.remind_pattern
|
|
612
644
|
);
|
|
613
645
|
results.push(todo);
|
|
614
646
|
}
|
|
@@ -757,7 +789,7 @@ import { z as z5 } from "zod";
|
|
|
757
789
|
function registerTodoDoneTool(server) {
|
|
758
790
|
server.tool(
|
|
759
791
|
"logbook_todo_done",
|
|
760
|
-
"Marca TODOs como hechos o los devuelve a pendiente (undo).
|
|
792
|
+
"Marca TODOs como hechos o los devuelve a pendiente (undo). Los recordatorios recurrentes se marcan como hechos por hoy y vuelven automaticamente el proximo dia que toque.",
|
|
761
793
|
{
|
|
762
794
|
ids: z5.union([z5.number(), z5.array(z5.number())]).describe("ID o array de IDs de TODOs a marcar"),
|
|
763
795
|
undo: z5.boolean().optional().default(false).describe("Si true, devuelve a pendiente en vez de marcar como hecho")
|
|
@@ -766,15 +798,41 @@ function registerTodoDoneTool(server) {
|
|
|
766
798
|
try {
|
|
767
799
|
const db2 = getDb();
|
|
768
800
|
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
769
|
-
const
|
|
770
|
-
const
|
|
801
|
+
const regularIds = [];
|
|
802
|
+
const recurringIds = [];
|
|
803
|
+
for (const id of idArray) {
|
|
804
|
+
const todo = db2.prepare("SELECT remind_pattern FROM todos WHERE id = ?").get(id);
|
|
805
|
+
if (todo?.remind_pattern && !undo) {
|
|
806
|
+
recurringIds.push(id);
|
|
807
|
+
} else {
|
|
808
|
+
regularIds.push(id);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const results = [];
|
|
812
|
+
if (regularIds.length > 0) {
|
|
813
|
+
const status = undo ? "pending" : "done";
|
|
814
|
+
const updated = updateTodoStatus(db2, regularIds, status);
|
|
815
|
+
results.push(...updated);
|
|
816
|
+
}
|
|
817
|
+
for (const id of recurringIds) {
|
|
818
|
+
ackRecurringReminder(db2, id);
|
|
819
|
+
const todo = db2.prepare(
|
|
820
|
+
`SELECT t.*, r.name as repo_name, tp.name as topic_name, 'manual' as source
|
|
821
|
+
FROM todos t
|
|
822
|
+
LEFT JOIN repos r ON t.repo_id = r.id
|
|
823
|
+
LEFT JOIN topics tp ON t.topic_id = tp.id
|
|
824
|
+
WHERE t.id = ?`
|
|
825
|
+
).get(id);
|
|
826
|
+
results.push(todo);
|
|
827
|
+
}
|
|
771
828
|
return {
|
|
772
829
|
content: [{
|
|
773
830
|
type: "text",
|
|
774
831
|
text: JSON.stringify({
|
|
775
832
|
action: undo ? "undo" : "done",
|
|
776
|
-
updated:
|
|
777
|
-
|
|
833
|
+
updated: results.length,
|
|
834
|
+
recurring_acked: recurringIds.length,
|
|
835
|
+
todos: results
|
|
778
836
|
})
|
|
779
837
|
}]
|
|
780
838
|
};
|
|
@@ -1034,31 +1092,24 @@ function registerRemindersResource(server) {
|
|
|
1034
1092
|
"reminders",
|
|
1035
1093
|
"logbook://reminders",
|
|
1036
1094
|
{
|
|
1037
|
-
description: "Recordatorios pendientes para hoy y atrasados, agrupados por proyecto",
|
|
1095
|
+
description: "Recordatorios pendientes para hoy y atrasados, agrupados por proyecto. Vacio si no hay ninguno.",
|
|
1038
1096
|
mimeType: "application/json"
|
|
1039
1097
|
},
|
|
1040
1098
|
async (uri) => {
|
|
1041
1099
|
try {
|
|
1042
1100
|
const db2 = getDb();
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1101
|
+
const result = getDueReminders(db2);
|
|
1102
|
+
if (!result) {
|
|
1103
|
+
return { contents: [] };
|
|
1104
|
+
}
|
|
1045
1105
|
return {
|
|
1046
1106
|
contents: [{
|
|
1047
1107
|
uri: uri.href,
|
|
1048
|
-
text: JSON.stringify(
|
|
1049
|
-
hasReminders,
|
|
1050
|
-
today,
|
|
1051
|
-
overdue
|
|
1052
|
-
})
|
|
1108
|
+
text: JSON.stringify(result)
|
|
1053
1109
|
}]
|
|
1054
1110
|
};
|
|
1055
1111
|
} catch {
|
|
1056
|
-
return {
|
|
1057
|
-
contents: [{
|
|
1058
|
-
uri: uri.href,
|
|
1059
|
-
text: JSON.stringify({ hasReminders: false, today: [], overdue: [] })
|
|
1060
|
-
}]
|
|
1061
|
-
};
|
|
1112
|
+
return { contents: [] };
|
|
1062
1113
|
}
|
|
1063
1114
|
}
|
|
1064
1115
|
);
|