@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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
401
|
+
import { execFileSync } from "child_process";
|
|
396
402
|
import { basename } from "path";
|
|
397
403
|
function detectRepoPath() {
|
|
398
404
|
try {
|
|
399
|
-
const result =
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
552
|
-
|
|
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
|
}
|