@cocaxcode/logbook-mcp 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -53,7 +53,7 @@ Your TODOs live in 5 different places: Jira, sticky notes, code comments, your h
53
53
  ### Claude Code (recommended)
54
54
 
55
55
  ```bash
56
- claude mcp add logbook-mcp -- npx @cocaxcode/logbook-mcp --mcp
56
+ claude mcp add logbook-mcp -- npx @cocaxcode/logbook-mcp@latest --mcp
57
57
  ```
58
58
 
59
59
  ### Claude Desktop
@@ -65,7 +65,7 @@ Add to your `claude_desktop_config.json`:
65
65
  "mcpServers": {
66
66
  "logbook-mcp": {
67
67
  "command": "npx",
68
- "args": ["@cocaxcode/logbook-mcp", "--mcp"]
68
+ "args": ["@cocaxcode/logbook-mcp@latest", "--mcp"]
69
69
  }
70
70
  }
71
71
  }
@@ -88,7 +88,7 @@ Add to `.cursor/mcp.json` in your project:
88
88
  "mcpServers": {
89
89
  "logbook-mcp": {
90
90
  "command": "npx",
91
- "args": ["@cocaxcode/logbook-mcp", "--mcp"]
91
+ "args": ["@cocaxcode/logbook-mcp@latest", "--mcp"]
92
92
  }
93
93
  }
94
94
  }
@@ -103,7 +103,7 @@ Add to `.windsurf/mcp.json`:
103
103
  "mcpServers": {
104
104
  "logbook-mcp": {
105
105
  "command": "npx",
106
- "args": ["@cocaxcode/logbook-mcp", "--mcp"]
106
+ "args": ["@cocaxcode/logbook-mcp@latest", "--mcp"]
107
107
  }
108
108
  }
109
109
  }
@@ -118,7 +118,7 @@ Add to `.vscode/mcp.json`:
118
118
  "servers": {
119
119
  "logbook-mcp": {
120
120
  "command": "npx",
121
- "args": ["@cocaxcode/logbook-mcp", "--mcp"]
121
+ "args": ["@cocaxcode/logbook-mcp@latest", "--mcp"]
122
122
  }
123
123
  }
124
124
  }
@@ -127,7 +127,7 @@ Add to `.vscode/mcp.json`:
127
127
  ### Codex CLI
128
128
 
129
129
  ```bash
130
- codex mcp add logbook-mcp -- npx @cocaxcode/logbook-mcp --mcp
130
+ codex mcp add logbook-mcp -- npx @cocaxcode/logbook-mcp@latest --mcp
131
131
  ```
132
132
 
133
133
  ### Gemini CLI
@@ -139,7 +139,7 @@ Add to `.gemini/settings.json`:
139
139
  "mcpServers": {
140
140
  "logbook-mcp": {
141
141
  "command": "npx",
142
- "args": ["@cocaxcode/logbook-mcp", "--mcp"]
142
+ "args": ["@cocaxcode/logbook-mcp@latest", "--mcp"]
143
143
  }
144
144
  }
145
145
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- var VERSION = true ? "0.1.0" : "0.0.0";
4
+ var VERSION = true ? "0.1.1" : "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}`);
@@ -16,7 +16,7 @@ async function runCli(argv) {
16
16
  console.log("Para usar con Claude Code, a\xF1ade a tu configuracion MCP:");
17
17
  console.log(' "logbook-mcp": {');
18
18
  console.log(' "command": "npx",');
19
- console.log(' "args": ["@cocaxcode/logbook-mcp", "--mcp"]');
19
+ console.log(' "args": ["@cocaxcode/logbook-mcp@latest", "--mcp"]');
20
20
  console.log(" }");
21
21
  }
22
22
  export {
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-5ZT2YKCQ.js");
9
+ const { createServer } = await import("./server-5CKGZQZD.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-TYBWWQAH.js");
15
+ const { runCli } = await import("./cli-JFMNT5GT.js");
16
16
  await runCli(argv);
17
17
  }
18
18
  }
@@ -131,6 +131,9 @@ function insertRepo(db2, name, path) {
131
131
  function getRepoByPath(db2, path) {
132
132
  return db2.prepare("SELECT * FROM repos WHERE path = ?").get(path);
133
133
  }
134
+ function getRepoByName(db2, name) {
135
+ return db2.prepare("SELECT * FROM repos WHERE name = ?").get(name);
136
+ }
134
137
  function getTopicByName(db2, name) {
135
138
  return db2.prepare("SELECT * FROM topics WHERE name = ?").get(name);
136
139
  }
@@ -229,6 +232,7 @@ function getTodos(db2, filters = {}) {
229
232
  ).all(...params, limit);
230
233
  }
231
234
  function updateTodoStatus(db2, ids, status) {
235
+ if (ids.length === 0) return [];
232
236
  const completedAt = status === "done" ? (/* @__PURE__ */ new Date()).toISOString() : null;
233
237
  const placeholders = ids.map(() => "?").join(",");
234
238
  db2.prepare(
@@ -261,6 +265,7 @@ function updateTodo(db2, id, fields) {
261
265
  return db2.prepare(`${TODO_WITH_META_SQL} WHERE t.id = ?`).get(id);
262
266
  }
263
267
  function deleteTodos(db2, ids) {
268
+ if (ids.length === 0) return [];
264
269
  const placeholders = ids.map(() => "?").join(",");
265
270
  const existing = db2.prepare(`SELECT id FROM todos WHERE id IN (${placeholders})`).all(...ids);
266
271
  const existingIds = existing.map((r) => r.id);
@@ -345,7 +350,9 @@ function getCompletedTodos(db2, filters = {}) {
345
350
  ).all(...params);
346
351
  }
347
352
  function sanitizeFts(query) {
348
- return query.split(/\s+/).filter(Boolean).map((word) => `"${word.replace(/"/g, "")}"`).join(" ");
353
+ const words = query.replace(/\0/g, "").split(/\s+/).filter(Boolean).map((word) => word.replace(/"/g, "")).filter(Boolean);
354
+ if (words.length === 0) return '""';
355
+ return words.map((word) => `"${word}"`).join(" ");
349
356
  }
350
357
 
351
358
  // src/tools/topics.ts
@@ -355,8 +362,8 @@ function registerTopicsTool(server) {
355
362
  "Lista o crea temas para organizar notas y TODOs. Temas predefinidos: feature, fix, chore, idea, decision, blocker.",
356
363
  {
357
364
  action: z.enum(["list", "add"]).default("list").describe("Accion: list (ver temas) o add (crear tema custom)"),
358
- name: z.string().optional().describe("Nombre del nuevo tema (solo para action=add, lowercase, sin espacios)"),
359
- description: z.string().optional().describe("Descripcion del nuevo tema (solo para action=add)")
365
+ name: z.string().min(1).max(50).regex(/^[a-z0-9-]+$/, "Solo letras minusculas, numeros y guiones").optional().describe("Nombre del nuevo tema (solo para action=add, lowercase, sin espacios)"),
366
+ description: z.string().max(200).optional().describe("Descripcion del nuevo tema (solo para action=add)")
360
367
  },
361
368
  async ({ action, name, description }) => {
362
369
  try {
@@ -368,8 +375,14 @@ function registerTopicsTool(server) {
368
375
  content: [{ type: "text", text: 'El parametro "name" es obligatorio para action=add' }]
369
376
  };
370
377
  }
371
- const normalized = name.toLowerCase().replace(/\s+/g, "-");
372
- const topic = insertTopic(db2, normalized, description);
378
+ const existing = getTopicByName(db2, name);
379
+ if (existing) {
380
+ return {
381
+ isError: true,
382
+ content: [{ type: "text", text: `El topic "${name}" ya existe` }]
383
+ };
384
+ }
385
+ const topic = insertTopic(db2, name, description);
373
386
  return {
374
387
  content: [{ type: "text", text: JSON.stringify(topic) }]
375
388
  };
@@ -381,7 +394,7 @@ function registerTopicsTool(server) {
381
394
  } catch (err) {
382
395
  return {
383
396
  isError: true,
384
- content: [{ type: "text", text: `Error: ${err.message}` }]
397
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }]
385
398
  };
386
399
  }
387
400
  }
@@ -392,11 +405,11 @@ function registerTopicsTool(server) {
392
405
  import { z as z2 } from "zod";
393
406
 
394
407
  // src/git/detect-repo.ts
395
- import { execSync } from "child_process";
408
+ import { execFileSync } from "child_process";
396
409
  import { basename } from "path";
397
410
  function detectRepoPath() {
398
411
  try {
399
- const result = execSync("git rev-parse --show-toplevel", {
412
+ const result = execFileSync("git", ["rev-parse", "--show-toplevel"], {
400
413
  encoding: "utf-8",
401
414
  timeout: 5e3,
402
415
  stdio: ["pipe", "pipe", "pipe"]
@@ -411,7 +424,13 @@ function autoRegisterRepo(db2) {
411
424
  if (!repoPath) return null;
412
425
  const existing = getRepoByPath(db2, repoPath);
413
426
  if (existing) return existing;
414
- const name = basename(repoPath);
427
+ let name = basename(repoPath);
428
+ const nameConflict = getRepoByName(db2, name);
429
+ if (nameConflict) {
430
+ const parts = repoPath.split("/");
431
+ const parent = parts.length >= 2 ? parts[parts.length - 2] : "repo";
432
+ name = `${parent}-${name}`;
433
+ }
415
434
  return insertRepo(db2, name, repoPath);
416
435
  }
417
436
 
@@ -421,7 +440,7 @@ function registerNoteTool(server) {
421
440
  "logbook_note",
422
441
  "A\xF1ade una nota al logbook. Topics: feature, fix, chore, idea, decision, blocker. Si no se pasa topic, queda sin categorizar.",
423
442
  {
424
- content: z2.string().describe("Contenido de la nota"),
443
+ content: z2.string().min(1).max(5e3).describe("Contenido de la nota"),
425
444
  topic: z2.string().optional().describe("Topic: feature, fix, chore, idea, decision, blocker (o custom)")
426
445
  },
427
446
  async ({ content, topic }) => {
@@ -450,7 +469,7 @@ function registerNoteTool(server) {
450
469
  } catch (err) {
451
470
  return {
452
471
  isError: true,
453
- content: [{ type: "text", text: `Error creando nota: ${err.message}` }]
472
+ content: [{ type: "text", text: `Error creando nota: ${err instanceof Error ? err.message : String(err)}` }]
454
473
  };
455
474
  }
456
475
  }
@@ -465,16 +484,16 @@ function registerTodoAddTool(server) {
465
484
  "logbook_todo_add",
466
485
  "Crea uno o varios TODOs. Para uno solo usa content. Para varios usa items (array). El topic se puede pasar o dejar que la AI lo infiera.",
467
486
  {
468
- content: z3.string().optional().describe("Contenido del TODO (para crear uno solo)"),
487
+ content: z3.string().min(1).max(2e3).optional().describe("Contenido del TODO (para crear uno solo)"),
469
488
  topic: z3.string().optional().describe("Topic para el TODO individual"),
470
489
  priority: priorityEnum.optional().default("normal").describe("Prioridad del TODO individual"),
471
490
  items: z3.array(
472
491
  z3.object({
473
- content: z3.string().describe("Contenido del TODO"),
492
+ content: z3.string().min(1).max(2e3).describe("Contenido del TODO"),
474
493
  topic: z3.string().optional().describe("Topic"),
475
494
  priority: priorityEnum.optional().default("normal").describe("Prioridad")
476
495
  })
477
- ).optional().describe("Array de TODOs para crear varios a la vez")
496
+ ).max(50).optional().describe("Array de TODOs para crear varios a la vez (max 50)")
478
497
  },
479
498
  async ({ content, topic, priority, items }) => {
480
499
  try {
@@ -528,7 +547,7 @@ function registerTodoAddTool(server) {
528
547
  } catch (err) {
529
548
  return {
530
549
  isError: true,
531
- content: [{ type: "text", text: `Error creando TODO: ${err.message}` }]
550
+ content: [{ type: "text", text: `Error creando TODO: ${err instanceof Error ? err.message : String(err)}` }]
532
551
  };
533
552
  }
534
553
  }
@@ -539,7 +558,7 @@ function registerTodoAddTool(server) {
539
558
  import { z as z4 } from "zod";
540
559
 
541
560
  // src/git/code-todos.ts
542
- import { execSync as execSync2 } from "child_process";
561
+ import { execFileSync as execFileSync2 } from "child_process";
543
562
  var TAG_TO_TOPIC = {
544
563
  TODO: "feature",
545
564
  FIXME: "fix",
@@ -548,8 +567,9 @@ var TAG_TO_TOPIC = {
548
567
  };
549
568
  function scanCodeTodos(repoPath) {
550
569
  try {
551
- const output = execSync2(
552
- `git -C "${repoPath}" grep -n -E "(TODO|FIXME|HACK|BUG):" --no-color`,
570
+ const output = execFileSync2(
571
+ "git",
572
+ ["-C", repoPath, "grep", "-n", "-E", "(TODO|FIXME|HACK|BUG):", "--no-color"],
553
573
  { encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
554
574
  );
555
575
  return parseGitGrepOutput(output);
@@ -646,7 +666,7 @@ function registerTodoListTool(server) {
646
666
  } catch (err) {
647
667
  return {
648
668
  isError: true,
649
- content: [{ type: "text", text: `Error listando TODOs: ${err.message}` }]
669
+ content: [{ type: "text", text: `Error listando TODOs: ${err instanceof Error ? err.message : String(err)}` }]
650
670
  };
651
671
  }
652
672
  }
@@ -682,7 +702,7 @@ function registerTodoDoneTool(server) {
682
702
  } catch (err) {
683
703
  return {
684
704
  isError: true,
685
- content: [{ type: "text", text: `Error: ${err.message}` }]
705
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }]
686
706
  };
687
707
  }
688
708
  }
@@ -732,7 +752,7 @@ function registerTodoEditTool(server) {
732
752
  } catch (err) {
733
753
  return {
734
754
  isError: true,
735
- content: [{ type: "text", text: `Error editando TODO: ${err.message}` }]
755
+ content: [{ type: "text", text: `Error editando TODO: ${err instanceof Error ? err.message : String(err)}` }]
736
756
  };
737
757
  }
738
758
  }
@@ -765,7 +785,7 @@ function registerTodoRmTool(server) {
765
785
  } catch (err) {
766
786
  return {
767
787
  isError: true,
768
- content: [{ type: "text", text: `Error eliminando TODOs: ${err.message}` }]
788
+ content: [{ type: "text", text: `Error eliminando TODOs: ${err instanceof Error ? err.message : String(err)}` }]
769
789
  };
770
790
  }
771
791
  }
@@ -835,7 +855,7 @@ function registerLogTool(server) {
835
855
  } catch (err) {
836
856
  return {
837
857
  isError: true,
838
- content: [{ type: "text", text: `Error: ${err.message}` }]
858
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }]
839
859
  };
840
860
  }
841
861
  }
@@ -843,8 +863,9 @@ function registerLogTool(server) {
843
863
  }
844
864
  function resolveDates(period, from, to) {
845
865
  if (from) {
846
- const dateTo = to ?? new Date(Date.now() + 864e5).toISOString().split("T")[0];
847
- return { dateFrom: from, dateTo };
866
+ const endDate = to ?? from;
867
+ const nextDay = new Date(new Date(endDate).getTime() + 864e5).toISOString().split("T")[0];
868
+ return { dateFrom: from, dateTo: nextDay };
848
869
  }
849
870
  const now = /* @__PURE__ */ new Date();
850
871
  const today = now.toISOString().split("T")[0];
@@ -874,7 +895,7 @@ function registerSearchTool(server) {
874
895
  "logbook_search",
875
896
  "Busqueda full-text en notas y TODOs. Usa FTS5 para busqueda rapida.",
876
897
  {
877
- query: z9.string().describe("Texto a buscar"),
898
+ query: z9.string().min(1).max(500).describe("Texto a buscar"),
878
899
  type: z9.enum(["all", "notes", "todos"]).optional().default("all").describe("Buscar en: all, notes, todos"),
879
900
  topic: z9.string().optional().describe("Filtrar por topic"),
880
901
  scope: z9.enum(["project", "global"]).optional().default("project").describe("Scope: project o global"),
@@ -928,7 +949,7 @@ function registerSearchTool(server) {
928
949
  } catch (err) {
929
950
  return {
930
951
  isError: true,
931
- content: [{ type: "text", text: `Error buscando: ${err.message}` }]
952
+ content: [{ type: "text", text: `Error buscando: ${err instanceof Error ? err.message : String(err)}` }]
932
953
  };
933
954
  }
934
955
  }
@@ -936,7 +957,7 @@ function registerSearchTool(server) {
936
957
  }
937
958
 
938
959
  // src/server.ts
939
- var VERSION = true ? "0.1.0" : "0.0.0";
960
+ var VERSION = true ? "0.1.1" : "0.0.0";
940
961
  function createServer() {
941
962
  const server = new McpServer({
942
963
  name: "logbook-mcp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocaxcode/logbook-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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": {