@cocaxcode/logbook-mcp 0.1.0 → 0.1.1

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
  }
@@ -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-F2LQO3GU.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-54ECHTAC.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,7 @@ 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 topic = insertTopic(db2, name, description);
373
379
  return {
374
380
  content: [{ type: "text", text: JSON.stringify(topic) }]
375
381
  };
@@ -381,7 +387,7 @@ function registerTopicsTool(server) {
381
387
  } catch (err) {
382
388
  return {
383
389
  isError: true,
384
- content: [{ type: "text", text: `Error: ${err.message}` }]
390
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }]
385
391
  };
386
392
  }
387
393
  }
@@ -392,11 +398,11 @@ function registerTopicsTool(server) {
392
398
  import { z as z2 } from "zod";
393
399
 
394
400
  // src/git/detect-repo.ts
395
- import { execSync } from "child_process";
401
+ import { execFileSync } from "child_process";
396
402
  import { basename } from "path";
397
403
  function detectRepoPath() {
398
404
  try {
399
- const result = execSync("git rev-parse --show-toplevel", {
405
+ const result = execFileSync("git", ["rev-parse", "--show-toplevel"], {
400
406
  encoding: "utf-8",
401
407
  timeout: 5e3,
402
408
  stdio: ["pipe", "pipe", "pipe"]
@@ -411,7 +417,13 @@ function autoRegisterRepo(db2) {
411
417
  if (!repoPath) return null;
412
418
  const existing = getRepoByPath(db2, repoPath);
413
419
  if (existing) return existing;
414
- const name = basename(repoPath);
420
+ let name = basename(repoPath);
421
+ const nameConflict = getRepoByName(db2, name);
422
+ if (nameConflict) {
423
+ const parts = repoPath.split("/");
424
+ const parent = parts.length >= 2 ? parts[parts.length - 2] : "repo";
425
+ name = `${parent}-${name}`;
426
+ }
415
427
  return insertRepo(db2, name, repoPath);
416
428
  }
417
429
 
@@ -421,7 +433,7 @@ function registerNoteTool(server) {
421
433
  "logbook_note",
422
434
  "A\xF1ade una nota al logbook. Topics: feature, fix, chore, idea, decision, blocker. Si no se pasa topic, queda sin categorizar.",
423
435
  {
424
- content: z2.string().describe("Contenido de la nota"),
436
+ content: z2.string().min(1).max(5e3).describe("Contenido de la nota"),
425
437
  topic: z2.string().optional().describe("Topic: feature, fix, chore, idea, decision, blocker (o custom)")
426
438
  },
427
439
  async ({ content, topic }) => {
@@ -450,7 +462,7 @@ function registerNoteTool(server) {
450
462
  } catch (err) {
451
463
  return {
452
464
  isError: true,
453
- content: [{ type: "text", text: `Error creando nota: ${err.message}` }]
465
+ content: [{ type: "text", text: `Error creando nota: ${err instanceof Error ? err.message : String(err)}` }]
454
466
  };
455
467
  }
456
468
  }
@@ -465,16 +477,16 @@ function registerTodoAddTool(server) {
465
477
  "logbook_todo_add",
466
478
  "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
479
  {
468
- content: z3.string().optional().describe("Contenido del TODO (para crear uno solo)"),
480
+ content: z3.string().min(1).max(2e3).optional().describe("Contenido del TODO (para crear uno solo)"),
469
481
  topic: z3.string().optional().describe("Topic para el TODO individual"),
470
482
  priority: priorityEnum.optional().default("normal").describe("Prioridad del TODO individual"),
471
483
  items: z3.array(
472
484
  z3.object({
473
- content: z3.string().describe("Contenido del TODO"),
485
+ content: z3.string().min(1).max(2e3).describe("Contenido del TODO"),
474
486
  topic: z3.string().optional().describe("Topic"),
475
487
  priority: priorityEnum.optional().default("normal").describe("Prioridad")
476
488
  })
477
- ).optional().describe("Array de TODOs para crear varios a la vez")
489
+ ).max(50).optional().describe("Array de TODOs para crear varios a la vez (max 50)")
478
490
  },
479
491
  async ({ content, topic, priority, items }) => {
480
492
  try {
@@ -528,7 +540,7 @@ function registerTodoAddTool(server) {
528
540
  } catch (err) {
529
541
  return {
530
542
  isError: true,
531
- content: [{ type: "text", text: `Error creando TODO: ${err.message}` }]
543
+ content: [{ type: "text", text: `Error creando TODO: ${err instanceof Error ? err.message : String(err)}` }]
532
544
  };
533
545
  }
534
546
  }
@@ -539,7 +551,7 @@ function registerTodoAddTool(server) {
539
551
  import { z as z4 } from "zod";
540
552
 
541
553
  // src/git/code-todos.ts
542
- import { execSync as execSync2 } from "child_process";
554
+ import { execFileSync as execFileSync2 } from "child_process";
543
555
  var TAG_TO_TOPIC = {
544
556
  TODO: "feature",
545
557
  FIXME: "fix",
@@ -548,8 +560,9 @@ var TAG_TO_TOPIC = {
548
560
  };
549
561
  function scanCodeTodos(repoPath) {
550
562
  try {
551
- const output = execSync2(
552
- `git -C "${repoPath}" grep -n -E "(TODO|FIXME|HACK|BUG):" --no-color`,
563
+ const output = execFileSync2(
564
+ "git",
565
+ ["-C", repoPath, "grep", "-n", "-E", "(TODO|FIXME|HACK|BUG):", "--no-color"],
553
566
  { encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
554
567
  );
555
568
  return parseGitGrepOutput(output);
@@ -646,7 +659,7 @@ function registerTodoListTool(server) {
646
659
  } catch (err) {
647
660
  return {
648
661
  isError: true,
649
- content: [{ type: "text", text: `Error listando TODOs: ${err.message}` }]
662
+ content: [{ type: "text", text: `Error listando TODOs: ${err instanceof Error ? err.message : String(err)}` }]
650
663
  };
651
664
  }
652
665
  }
@@ -682,7 +695,7 @@ function registerTodoDoneTool(server) {
682
695
  } catch (err) {
683
696
  return {
684
697
  isError: true,
685
- content: [{ type: "text", text: `Error: ${err.message}` }]
698
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }]
686
699
  };
687
700
  }
688
701
  }
@@ -732,7 +745,7 @@ function registerTodoEditTool(server) {
732
745
  } catch (err) {
733
746
  return {
734
747
  isError: true,
735
- content: [{ type: "text", text: `Error editando TODO: ${err.message}` }]
748
+ content: [{ type: "text", text: `Error editando TODO: ${err instanceof Error ? err.message : String(err)}` }]
736
749
  };
737
750
  }
738
751
  }
@@ -765,7 +778,7 @@ function registerTodoRmTool(server) {
765
778
  } catch (err) {
766
779
  return {
767
780
  isError: true,
768
- content: [{ type: "text", text: `Error eliminando TODOs: ${err.message}` }]
781
+ content: [{ type: "text", text: `Error eliminando TODOs: ${err instanceof Error ? err.message : String(err)}` }]
769
782
  };
770
783
  }
771
784
  }
@@ -835,7 +848,7 @@ function registerLogTool(server) {
835
848
  } catch (err) {
836
849
  return {
837
850
  isError: true,
838
- content: [{ type: "text", text: `Error: ${err.message}` }]
851
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }]
839
852
  };
840
853
  }
841
854
  }
@@ -874,7 +887,7 @@ function registerSearchTool(server) {
874
887
  "logbook_search",
875
888
  "Busqueda full-text en notas y TODOs. Usa FTS5 para busqueda rapida.",
876
889
  {
877
- query: z9.string().describe("Texto a buscar"),
890
+ query: z9.string().min(1).max(500).describe("Texto a buscar"),
878
891
  type: z9.enum(["all", "notes", "todos"]).optional().default("all").describe("Buscar en: all, notes, todos"),
879
892
  topic: z9.string().optional().describe("Filtrar por topic"),
880
893
  scope: z9.enum(["project", "global"]).optional().default("project").describe("Scope: project o global"),
@@ -928,7 +941,7 @@ function registerSearchTool(server) {
928
941
  } catch (err) {
929
942
  return {
930
943
  isError: true,
931
- content: [{ type: "text", text: `Error buscando: ${err.message}` }]
944
+ content: [{ type: "text", text: `Error buscando: ${err instanceof Error ? err.message : String(err)}` }]
932
945
  };
933
946
  }
934
947
  }
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.1",
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": {