@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.
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
372
|
-
|
|
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 {
|
|
408
|
+
import { execFileSync } from "child_process";
|
|
396
409
|
import { basename } from "path";
|
|
397
410
|
function detectRepoPath() {
|
|
398
411
|
try {
|
|
399
|
-
const result =
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
552
|
-
|
|
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
|
|
847
|
-
|
|
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.
|
|
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",
|