@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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- var VERSION = true ? "0.1.2" : "0.0.0";
4
+ var VERSION = true ? "0.3.0" : "0.0.0";
5
5
  async function runCli(argv) {
6
6
  if (argv.includes("--version") || argv.includes("-v")) {
7
7
  console.log(`logbook-mcp v${VERSION}`);
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-RBETEXFK.js");
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-T2BAPOJO.js");
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
- let topicId = null;
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
- let topicId = null;
576
- if (item.topic) {
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
- let topicId;
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.1.2" : "0.0.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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocaxcode/logbook-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server — cuaderno de bitácora del developer. Notas, TODOs y code TODOs sin salir de tu AI.",
5
5
  "type": "module",
6
6
  "bin": {