mindpm 1.2.0 → 1.2.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/dist/index.js +422 -402
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -705,302 +705,6 @@ function registerNoteTools(server2) {
|
|
|
705
705
|
|
|
706
706
|
// src/tools/sessions.ts
|
|
707
707
|
import { z as z5 } from "zod/v4";
|
|
708
|
-
function getActivitySince(db2, projectId, cutoffTime) {
|
|
709
|
-
return db2.prepare(`
|
|
710
|
-
SELECT 'task_created' as type, id, title, created_at as timestamp
|
|
711
|
-
FROM tasks
|
|
712
|
-
WHERE project_id = ? AND created_at > ?
|
|
713
|
-
UNION ALL
|
|
714
|
-
SELECT 'task_updated' as type, id, title, updated_at as timestamp
|
|
715
|
-
FROM tasks
|
|
716
|
-
WHERE project_id = ? AND updated_at > ? AND updated_at != created_at
|
|
717
|
-
UNION ALL
|
|
718
|
-
SELECT 'decision' as type, id, title, created_at as timestamp
|
|
719
|
-
FROM decisions
|
|
720
|
-
WHERE project_id = ? AND created_at > ?
|
|
721
|
-
UNION ALL
|
|
722
|
-
SELECT 'note' as type, id, substr(content, 1, 80) as title, created_at as timestamp
|
|
723
|
-
FROM notes
|
|
724
|
-
WHERE project_id = ? AND created_at > ?
|
|
725
|
-
ORDER BY timestamp DESC
|
|
726
|
-
`).all(
|
|
727
|
-
projectId,
|
|
728
|
-
cutoffTime,
|
|
729
|
-
projectId,
|
|
730
|
-
cutoffTime,
|
|
731
|
-
projectId,
|
|
732
|
-
cutoffTime,
|
|
733
|
-
projectId,
|
|
734
|
-
cutoffTime
|
|
735
|
-
);
|
|
736
|
-
}
|
|
737
|
-
function registerSessionTools(server2) {
|
|
738
|
-
server2.registerTool(
|
|
739
|
-
"start_session",
|
|
740
|
-
{
|
|
741
|
-
title: "Start Session",
|
|
742
|
-
description: "Begin a work session for a project. Returns the full project overview including last session's next_steps, active tasks, blockers, and recent decisions. Call this at the start of every conversation.",
|
|
743
|
-
inputSchema: {
|
|
744
|
-
project: z5.string().optional().describe("Project name or ID")
|
|
745
|
-
}
|
|
746
|
-
},
|
|
747
|
-
async ({ project }) => {
|
|
748
|
-
const resolved = resolveProjectOrDefault(project);
|
|
749
|
-
if (!resolved) {
|
|
750
|
-
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found. Create a project first." }], isError: true };
|
|
751
|
-
}
|
|
752
|
-
const db2 = getDb();
|
|
753
|
-
const projectRow = db2.prepare("SELECT * FROM projects WHERE id = ?").get(resolved.id);
|
|
754
|
-
let lastSession = db2.prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(resolved.id);
|
|
755
|
-
const cutoffTime = lastSession?.created_at ?? "1970-01-01";
|
|
756
|
-
const recentActivity = getActivitySince(db2, resolved.id, cutoffTime);
|
|
757
|
-
if (recentActivity.length > 0) {
|
|
758
|
-
const taskIds = [...new Set(
|
|
759
|
-
recentActivity.filter((a) => a.type === "task_created" || a.type === "task_updated").map((a) => a.id)
|
|
760
|
-
)];
|
|
761
|
-
const decisionIds = [...new Set(
|
|
762
|
-
recentActivity.filter((a) => a.type === "decision").map((a) => a.id)
|
|
763
|
-
)];
|
|
764
|
-
const syntheticId = generateId();
|
|
765
|
-
db2.prepare(
|
|
766
|
-
`INSERT INTO sessions (id, project_id, summary, tasks_worked_on, decisions_made) VALUES (?, ?, ?, ?, ?)`
|
|
767
|
-
).run(
|
|
768
|
-
syntheticId,
|
|
769
|
-
resolved.id,
|
|
770
|
-
`Auto-generated: ${recentActivity.length} activities since last session`,
|
|
771
|
-
taskIds.length > 0 ? JSON.stringify(taskIds) : null,
|
|
772
|
-
decisionIds.length > 0 ? JSON.stringify(decisionIds) : null
|
|
773
|
-
);
|
|
774
|
-
lastSession = db2.prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(resolved.id);
|
|
775
|
-
}
|
|
776
|
-
const activeTasks = db2.prepare(
|
|
777
|
-
`SELECT id, title, status, priority, tags FROM tasks
|
|
778
|
-
WHERE project_id = ? AND status NOT IN ('done', 'cancelled')
|
|
779
|
-
ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`
|
|
780
|
-
).all(resolved.id);
|
|
781
|
-
const blockedTasks = db2.prepare("SELECT id, title, blocked_by FROM tasks WHERE project_id = ? AND status = 'blocked'").all(resolved.id);
|
|
782
|
-
const recentDecisions = db2.prepare("SELECT id, title, decision, created_at FROM decisions WHERE project_id = ? ORDER BY created_at DESC LIMIT 5").all(resolved.id);
|
|
783
|
-
const taskCounts = db2.prepare("SELECT status, COUNT(*) as count FROM tasks WHERE project_id = ? GROUP BY status").all(resolved.id);
|
|
784
|
-
const contextItems = db2.prepare("SELECT key, value, category FROM context WHERE project_id = ? ORDER BY category, key").all(resolved.id);
|
|
785
|
-
db2.prepare("UPDATE projects SET status = status WHERE id = ?").run(resolved.id);
|
|
786
|
-
const result = {
|
|
787
|
-
project: projectRow,
|
|
788
|
-
last_session: lastSession ? {
|
|
789
|
-
summary: lastSession.summary,
|
|
790
|
-
next_steps: lastSession.next_steps,
|
|
791
|
-
when: lastSession.created_at
|
|
792
|
-
} : null,
|
|
793
|
-
recent_activity: recentActivity.slice(0, 20),
|
|
794
|
-
task_summary: taskCounts,
|
|
795
|
-
active_tasks: activeTasks,
|
|
796
|
-
blocked_tasks: blockedTasks,
|
|
797
|
-
recent_decisions: recentDecisions,
|
|
798
|
-
context: contextItems
|
|
799
|
-
};
|
|
800
|
-
return {
|
|
801
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
);
|
|
805
|
-
server2.registerTool(
|
|
806
|
-
"end_session",
|
|
807
|
-
{
|
|
808
|
-
title: "End Session",
|
|
809
|
-
description: "End a work session with a summary of what was accomplished and what to do next. Call this when the user is done working.",
|
|
810
|
-
inputSchema: {
|
|
811
|
-
project: z5.string().optional().describe("Project name or ID"),
|
|
812
|
-
summary: z5.string().describe("Summary of what was accomplished this session"),
|
|
813
|
-
tasks_worked_on: z5.array(z5.string()).optional().describe("Task IDs that were worked on"),
|
|
814
|
-
decisions_made: z5.array(z5.string()).optional().describe("Decision IDs that were made"),
|
|
815
|
-
next_steps: z5.string().optional().describe("What to do next time")
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
async ({ project, summary, tasks_worked_on, decisions_made, next_steps }) => {
|
|
819
|
-
const resolved = resolveProjectOrDefault(project);
|
|
820
|
-
if (!resolved) {
|
|
821
|
-
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
822
|
-
}
|
|
823
|
-
const db2 = getDb();
|
|
824
|
-
const id = generateId();
|
|
825
|
-
db2.prepare(
|
|
826
|
-
`INSERT INTO sessions (id, project_id, summary, tasks_worked_on, decisions_made, next_steps) VALUES (?, ?, ?, ?, ?, ?)`
|
|
827
|
-
).run(
|
|
828
|
-
id,
|
|
829
|
-
resolved.id,
|
|
830
|
-
summary,
|
|
831
|
-
tasks_worked_on ? JSON.stringify(tasks_worked_on) : null,
|
|
832
|
-
decisions_made ? JSON.stringify(decisions_made) : null,
|
|
833
|
-
next_steps ?? null
|
|
834
|
-
);
|
|
835
|
-
return {
|
|
836
|
-
content: [{
|
|
837
|
-
type: "text",
|
|
838
|
-
text: JSON.stringify({ session_id: id, message: `Session ended for ${resolved.name}. Summary saved.` })
|
|
839
|
-
}]
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// src/tools/queries.ts
|
|
846
|
-
import { z as z6 } from "zod/v4";
|
|
847
|
-
function registerQueryTools(server2) {
|
|
848
|
-
server2.registerTool(
|
|
849
|
-
"query",
|
|
850
|
-
{
|
|
851
|
-
title: "Query Database",
|
|
852
|
-
description: "Execute a read-only SQL query against the database. Only SELECT statements are allowed. Use this for custom queries not covered by other tools.",
|
|
853
|
-
inputSchema: {
|
|
854
|
-
sql: z6.string().describe("SQL SELECT query to execute")
|
|
855
|
-
}
|
|
856
|
-
},
|
|
857
|
-
async ({ sql }) => {
|
|
858
|
-
const trimmed = sql.trim();
|
|
859
|
-
if (!trimmed.toUpperCase().startsWith("SELECT")) {
|
|
860
|
-
return { content: [{ type: "text", text: "Only SELECT queries are allowed." }], isError: true };
|
|
861
|
-
}
|
|
862
|
-
const db2 = getDb();
|
|
863
|
-
try {
|
|
864
|
-
const stmt = db2.prepare(trimmed);
|
|
865
|
-
if (!stmt.reader) {
|
|
866
|
-
return { content: [{ type: "text", text: "Only read-only queries are allowed." }], isError: true };
|
|
867
|
-
}
|
|
868
|
-
const rows = stmt.all();
|
|
869
|
-
return {
|
|
870
|
-
content: [{ type: "text", text: JSON.stringify({ rows, count: rows.length }, null, 2) }]
|
|
871
|
-
};
|
|
872
|
-
} catch (e) {
|
|
873
|
-
return { content: [{ type: "text", text: `Query error: ${e.message}` }], isError: true };
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
);
|
|
877
|
-
server2.registerTool(
|
|
878
|
-
"get_project_summary",
|
|
879
|
-
{
|
|
880
|
-
title: "Get Project Summary",
|
|
881
|
-
description: "High-level summary of a project: total tasks by status, recent activity, open blockers, and upcoming priorities.",
|
|
882
|
-
inputSchema: {
|
|
883
|
-
project: z6.string().optional().describe("Project name or ID")
|
|
884
|
-
}
|
|
885
|
-
},
|
|
886
|
-
async ({ project }) => {
|
|
887
|
-
const resolved = resolveProjectOrDefault(project);
|
|
888
|
-
if (!resolved) {
|
|
889
|
-
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
890
|
-
}
|
|
891
|
-
const db2 = getDb();
|
|
892
|
-
const tasksByStatus = db2.prepare("SELECT status, COUNT(*) as count FROM tasks WHERE project_id = ? GROUP BY status").all(resolved.id);
|
|
893
|
-
const blockers = db2.prepare("SELECT id, title, blocked_by FROM tasks WHERE project_id = ? AND status = 'blocked'").all(resolved.id);
|
|
894
|
-
const upcomingPriorities = db2.prepare(
|
|
895
|
-
`SELECT id, title, priority, status FROM tasks
|
|
896
|
-
WHERE project_id = ? AND status IN ('todo', 'in_progress')
|
|
897
|
-
ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END
|
|
898
|
-
LIMIT 10`
|
|
899
|
-
).all(resolved.id);
|
|
900
|
-
const recentActivity = db2.prepare(
|
|
901
|
-
`SELECT 'task' as type, title, updated_at FROM tasks WHERE project_id = ? AND updated_at > datetime('now', '-7 days')
|
|
902
|
-
UNION ALL
|
|
903
|
-
SELECT 'decision' as type, title, created_at as updated_at FROM decisions WHERE project_id = ? AND created_at > datetime('now', '-7 days')
|
|
904
|
-
UNION ALL
|
|
905
|
-
SELECT 'note' as type, substr(content, 1, 50) as title, created_at as updated_at FROM notes WHERE project_id = ? AND created_at > datetime('now', '-7 days')
|
|
906
|
-
ORDER BY updated_at DESC
|
|
907
|
-
LIMIT 20`
|
|
908
|
-
).all(resolved.id, resolved.id, resolved.id);
|
|
909
|
-
const totalNotes = db2.prepare("SELECT COUNT(*) as count FROM notes WHERE project_id = ?").get(resolved.id);
|
|
910
|
-
const totalDecisions = db2.prepare("SELECT COUNT(*) as count FROM decisions WHERE project_id = ?").get(resolved.id);
|
|
911
|
-
const totalSessions = db2.prepare("SELECT COUNT(*) as count FROM sessions WHERE project_id = ?").get(resolved.id);
|
|
912
|
-
return {
|
|
913
|
-
content: [{
|
|
914
|
-
type: "text",
|
|
915
|
-
text: JSON.stringify(
|
|
916
|
-
{
|
|
917
|
-
project: resolved.name,
|
|
918
|
-
tasks_by_status: tasksByStatus,
|
|
919
|
-
blockers,
|
|
920
|
-
upcoming_priorities: upcomingPriorities,
|
|
921
|
-
recent_activity: recentActivity,
|
|
922
|
-
totals: { notes: totalNotes.count, decisions: totalDecisions.count, sessions: totalSessions.count }
|
|
923
|
-
},
|
|
924
|
-
null,
|
|
925
|
-
2
|
|
926
|
-
)
|
|
927
|
-
}]
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
);
|
|
931
|
-
server2.registerTool(
|
|
932
|
-
"get_blockers",
|
|
933
|
-
{
|
|
934
|
-
title: "Get Blockers",
|
|
935
|
-
description: "List all blocked tasks with what's blocking them.",
|
|
936
|
-
inputSchema: {
|
|
937
|
-
project: z6.string().optional().describe("Project name or ID")
|
|
938
|
-
}
|
|
939
|
-
},
|
|
940
|
-
async ({ project }) => {
|
|
941
|
-
const resolved = resolveProjectOrDefault(project);
|
|
942
|
-
if (!resolved) {
|
|
943
|
-
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
944
|
-
}
|
|
945
|
-
const db2 = getDb();
|
|
946
|
-
const blockers = db2.prepare("SELECT * FROM tasks WHERE project_id = ? AND status = 'blocked'").all(resolved.id);
|
|
947
|
-
const enriched = blockers.map((task) => {
|
|
948
|
-
let blockingTasks = [];
|
|
949
|
-
if (task.blocked_by) {
|
|
950
|
-
try {
|
|
951
|
-
const ids = JSON.parse(task.blocked_by);
|
|
952
|
-
blockingTasks = ids.map((id) => {
|
|
953
|
-
const blocking = db2.prepare("SELECT id, title, status FROM tasks WHERE id = ?").get(id);
|
|
954
|
-
return blocking ?? { id, title: "Unknown task", status: "unknown" };
|
|
955
|
-
});
|
|
956
|
-
} catch {
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
return { ...task, blocking_tasks: blockingTasks };
|
|
960
|
-
});
|
|
961
|
-
return {
|
|
962
|
-
content: [{ type: "text", text: JSON.stringify({ project: resolved.name, blockers: enriched }, null, 2) }]
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
);
|
|
966
|
-
server2.registerTool(
|
|
967
|
-
"search",
|
|
968
|
-
{
|
|
969
|
-
title: "Search Everything",
|
|
970
|
-
description: "Full-text search across tasks, notes, and decisions for a project.",
|
|
971
|
-
inputSchema: {
|
|
972
|
-
project: z6.string().optional().describe("Project name or ID"),
|
|
973
|
-
query: z6.string().describe("Search query")
|
|
974
|
-
}
|
|
975
|
-
},
|
|
976
|
-
async ({ project, query }) => {
|
|
977
|
-
const resolved = resolveProjectOrDefault(project);
|
|
978
|
-
if (!resolved) {
|
|
979
|
-
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
980
|
-
}
|
|
981
|
-
const db2 = getDb();
|
|
982
|
-
const pattern = `%${query}%`;
|
|
983
|
-
const tasks = db2.prepare("SELECT id, title, description, status, priority, 'task' as type FROM tasks WHERE project_id = ? AND (title LIKE ? OR description LIKE ?)").all(resolved.id, pattern, pattern);
|
|
984
|
-
const notes = db2.prepare("SELECT id, content, category, 'note' as type FROM notes WHERE project_id = ? AND content LIKE ?").all(resolved.id, pattern);
|
|
985
|
-
const decisions = db2.prepare("SELECT id, title, decision, reasoning, 'decision' as type FROM decisions WHERE project_id = ? AND (title LIKE ? OR decision LIKE ? OR reasoning LIKE ?)").all(resolved.id, pattern, pattern, pattern);
|
|
986
|
-
return {
|
|
987
|
-
content: [{
|
|
988
|
-
type: "text",
|
|
989
|
-
text: JSON.stringify(
|
|
990
|
-
{
|
|
991
|
-
project: resolved.name,
|
|
992
|
-
query,
|
|
993
|
-
results: { tasks, notes, decisions },
|
|
994
|
-
total: tasks.length + notes.length + decisions.length
|
|
995
|
-
},
|
|
996
|
-
null,
|
|
997
|
-
2
|
|
998
|
-
)
|
|
999
|
-
}]
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
);
|
|
1003
|
-
}
|
|
1004
708
|
|
|
1005
709
|
// src/server/http.ts
|
|
1006
710
|
import { createServer } from "http";
|
|
@@ -1008,6 +712,7 @@ import { readFile } from "fs/promises";
|
|
|
1008
712
|
import { join, extname } from "path";
|
|
1009
713
|
import { fileURLToPath } from "url";
|
|
1010
714
|
import { dirname as dirname2 } from "path";
|
|
715
|
+
import { spawn } from "child_process";
|
|
1011
716
|
|
|
1012
717
|
// src/server/routes.ts
|
|
1013
718
|
var listProjects = async (_req, res) => {
|
|
@@ -1201,129 +906,444 @@ async function handleApiRequest(req, res) {
|
|
|
1201
906
|
await route.handler(req, res, params);
|
|
1202
907
|
return;
|
|
1203
908
|
}
|
|
1204
|
-
}
|
|
1205
|
-
sendJson(res, 404, { error: "Not found" });
|
|
909
|
+
}
|
|
910
|
+
sendJson(res, 404, { error: "Not found" });
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/server/http.ts
|
|
914
|
+
function openBrowser(url) {
|
|
915
|
+
const platform = process.platform;
|
|
916
|
+
const cmd = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
|
|
917
|
+
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
918
|
+
spawn(cmd, args, { detached: true, stdio: "ignore" }).unref();
|
|
919
|
+
}
|
|
920
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
921
|
+
var __dirname = dirname2(__filename);
|
|
922
|
+
function resolveStaticDir() {
|
|
923
|
+
const scriptDir = dirname2(process.argv[1] || __filename);
|
|
924
|
+
return join(scriptDir, "ui");
|
|
925
|
+
}
|
|
926
|
+
var MIME_TYPES = {
|
|
927
|
+
".html": "text/html; charset=utf-8",
|
|
928
|
+
".js": "application/javascript; charset=utf-8",
|
|
929
|
+
".css": "text/css; charset=utf-8",
|
|
930
|
+
".json": "application/json; charset=utf-8",
|
|
931
|
+
".svg": "image/svg+xml",
|
|
932
|
+
".png": "image/png",
|
|
933
|
+
".jpg": "image/jpeg",
|
|
934
|
+
".ico": "image/x-icon",
|
|
935
|
+
".woff": "font/woff",
|
|
936
|
+
".woff2": "font/woff2",
|
|
937
|
+
".ttf": "font/ttf"
|
|
938
|
+
};
|
|
939
|
+
function parseBody(req) {
|
|
940
|
+
return new Promise((resolve2, reject) => {
|
|
941
|
+
const chunks = [];
|
|
942
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
943
|
+
req.on("end", () => {
|
|
944
|
+
if (chunks.length === 0) {
|
|
945
|
+
resolve2({});
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
try {
|
|
949
|
+
resolve2(JSON.parse(Buffer.concat(chunks).toString()));
|
|
950
|
+
} catch {
|
|
951
|
+
resolve2({});
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
req.on("error", reject);
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
function matchRoute(pattern, pathname) {
|
|
958
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
959
|
+
const pathParts = pathname.split("/").filter(Boolean);
|
|
960
|
+
if (patternParts.length !== pathParts.length) return null;
|
|
961
|
+
const params = {};
|
|
962
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
963
|
+
if (patternParts[i].startsWith(":")) {
|
|
964
|
+
params[patternParts[i].slice(1)] = decodeURIComponent(pathParts[i]);
|
|
965
|
+
} else if (patternParts[i] !== pathParts[i]) {
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return params;
|
|
970
|
+
}
|
|
971
|
+
function sendJson(res, status, data) {
|
|
972
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
973
|
+
res.end(JSON.stringify(data));
|
|
974
|
+
}
|
|
975
|
+
async function serveStatic(req, res) {
|
|
976
|
+
const staticDir = resolveStaticDir();
|
|
977
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
978
|
+
let filePath = join(staticDir, url.pathname === "/" ? "index.html" : url.pathname);
|
|
979
|
+
try {
|
|
980
|
+
const content = await readFile(filePath);
|
|
981
|
+
const ext = extname(filePath);
|
|
982
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
983
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
984
|
+
res.end(content);
|
|
985
|
+
} catch {
|
|
986
|
+
try {
|
|
987
|
+
const indexPath = join(staticDir, "index.html");
|
|
988
|
+
const content = await readFile(indexPath);
|
|
989
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
990
|
+
res.end(content);
|
|
991
|
+
} catch {
|
|
992
|
+
res.writeHead(503, { "Content-Type": "text/html; charset=utf-8" });
|
|
993
|
+
res.end(
|
|
994
|
+
"<html><body><h1>mindpm UI not built</h1><p>Run <code>npm run build:ui</code> to build the Kanban UI.</p></body></html>"
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
var _httpPort = null;
|
|
1000
|
+
function getHttpPort() {
|
|
1001
|
+
return _httpPort;
|
|
1002
|
+
}
|
|
1003
|
+
function startHttpServer(port) {
|
|
1004
|
+
const server2 = createServer(async (req, res) => {
|
|
1005
|
+
try {
|
|
1006
|
+
if (req.url?.startsWith("/api/")) {
|
|
1007
|
+
await handleApiRequest(req, res);
|
|
1008
|
+
} else {
|
|
1009
|
+
await serveStatic(req, res);
|
|
1010
|
+
}
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
process.stderr.write(`[mindpm] HTTP error: ${err}
|
|
1013
|
+
`);
|
|
1014
|
+
if (!res.headersSent) {
|
|
1015
|
+
sendJson(res, 500, { error: "Internal server error" });
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
server2.on("error", (err) => {
|
|
1020
|
+
if (err.code === "EADDRINUSE") {
|
|
1021
|
+
process.stderr.write(
|
|
1022
|
+
`[mindpm] Warning: Port ${port} is in use. Kanban UI not available. MCP server continues.
|
|
1023
|
+
`
|
|
1024
|
+
);
|
|
1025
|
+
} else {
|
|
1026
|
+
process.stderr.write(`[mindpm] HTTP server error: ${err.message}
|
|
1027
|
+
`);
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
server2.listen(port, () => {
|
|
1031
|
+
_httpPort = port;
|
|
1032
|
+
const url = `http://localhost:${port}`;
|
|
1033
|
+
process.stderr.write(`[mindpm] Kanban UI available at ${url}
|
|
1034
|
+
`);
|
|
1035
|
+
if (process.env.MINDPM_OPEN_BROWSER === "1") {
|
|
1036
|
+
openBrowser(url);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
return server2;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/tools/sessions.ts
|
|
1043
|
+
function getActivitySince(db2, projectId, cutoffTime) {
|
|
1044
|
+
return db2.prepare(`
|
|
1045
|
+
SELECT 'task_created' as type, id, title, created_at as timestamp
|
|
1046
|
+
FROM tasks
|
|
1047
|
+
WHERE project_id = ? AND created_at > ?
|
|
1048
|
+
UNION ALL
|
|
1049
|
+
SELECT 'task_updated' as type, id, title, updated_at as timestamp
|
|
1050
|
+
FROM tasks
|
|
1051
|
+
WHERE project_id = ? AND updated_at > ? AND updated_at != created_at
|
|
1052
|
+
UNION ALL
|
|
1053
|
+
SELECT 'decision' as type, id, title, created_at as timestamp
|
|
1054
|
+
FROM decisions
|
|
1055
|
+
WHERE project_id = ? AND created_at > ?
|
|
1056
|
+
UNION ALL
|
|
1057
|
+
SELECT 'note' as type, id, substr(content, 1, 80) as title, created_at as timestamp
|
|
1058
|
+
FROM notes
|
|
1059
|
+
WHERE project_id = ? AND created_at > ?
|
|
1060
|
+
ORDER BY timestamp DESC
|
|
1061
|
+
`).all(
|
|
1062
|
+
projectId,
|
|
1063
|
+
cutoffTime,
|
|
1064
|
+
projectId,
|
|
1065
|
+
cutoffTime,
|
|
1066
|
+
projectId,
|
|
1067
|
+
cutoffTime,
|
|
1068
|
+
projectId,
|
|
1069
|
+
cutoffTime
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
function registerSessionTools(server2) {
|
|
1073
|
+
server2.registerTool(
|
|
1074
|
+
"start_session",
|
|
1075
|
+
{
|
|
1076
|
+
title: "Start Session",
|
|
1077
|
+
description: "Begin a work session for a project. Returns the full project overview including last session's next_steps, active tasks, blockers, and recent decisions. Call this at the start of every conversation. IMPORTANT: Always show the kanban_url to the user as a clickable link so they can open the Kanban board.",
|
|
1078
|
+
inputSchema: {
|
|
1079
|
+
project: z5.string().optional().describe("Project name or ID")
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
async ({ project }) => {
|
|
1083
|
+
const resolved = resolveProjectOrDefault(project);
|
|
1084
|
+
if (!resolved) {
|
|
1085
|
+
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found. Create a project first." }], isError: true };
|
|
1086
|
+
}
|
|
1087
|
+
const db2 = getDb();
|
|
1088
|
+
const projectRow = db2.prepare("SELECT * FROM projects WHERE id = ?").get(resolved.id);
|
|
1089
|
+
let lastSession = db2.prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(resolved.id);
|
|
1090
|
+
const cutoffTime = lastSession?.created_at ?? "1970-01-01";
|
|
1091
|
+
const recentActivity = getActivitySince(db2, resolved.id, cutoffTime);
|
|
1092
|
+
if (recentActivity.length > 0) {
|
|
1093
|
+
const taskIds = [...new Set(
|
|
1094
|
+
recentActivity.filter((a) => a.type === "task_created" || a.type === "task_updated").map((a) => a.id)
|
|
1095
|
+
)];
|
|
1096
|
+
const decisionIds = [...new Set(
|
|
1097
|
+
recentActivity.filter((a) => a.type === "decision").map((a) => a.id)
|
|
1098
|
+
)];
|
|
1099
|
+
const syntheticId = generateId();
|
|
1100
|
+
db2.prepare(
|
|
1101
|
+
`INSERT INTO sessions (id, project_id, summary, tasks_worked_on, decisions_made) VALUES (?, ?, ?, ?, ?)`
|
|
1102
|
+
).run(
|
|
1103
|
+
syntheticId,
|
|
1104
|
+
resolved.id,
|
|
1105
|
+
`Auto-generated: ${recentActivity.length} activities since last session`,
|
|
1106
|
+
taskIds.length > 0 ? JSON.stringify(taskIds) : null,
|
|
1107
|
+
decisionIds.length > 0 ? JSON.stringify(decisionIds) : null
|
|
1108
|
+
);
|
|
1109
|
+
lastSession = db2.prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(resolved.id);
|
|
1110
|
+
}
|
|
1111
|
+
const activeTasks = db2.prepare(
|
|
1112
|
+
`SELECT id, title, status, priority, tags FROM tasks
|
|
1113
|
+
WHERE project_id = ? AND status NOT IN ('done', 'cancelled')
|
|
1114
|
+
ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`
|
|
1115
|
+
).all(resolved.id);
|
|
1116
|
+
const blockedTasks = db2.prepare("SELECT id, title, blocked_by FROM tasks WHERE project_id = ? AND status = 'blocked'").all(resolved.id);
|
|
1117
|
+
const recentDecisions = db2.prepare("SELECT id, title, decision, created_at FROM decisions WHERE project_id = ? ORDER BY created_at DESC LIMIT 5").all(resolved.id);
|
|
1118
|
+
const taskCounts = db2.prepare("SELECT status, COUNT(*) as count FROM tasks WHERE project_id = ? GROUP BY status").all(resolved.id);
|
|
1119
|
+
const contextItems = db2.prepare("SELECT key, value, category FROM context WHERE project_id = ? ORDER BY category, key").all(resolved.id);
|
|
1120
|
+
db2.prepare("UPDATE projects SET status = status WHERE id = ?").run(resolved.id);
|
|
1121
|
+
const port = getHttpPort();
|
|
1122
|
+
const result = {
|
|
1123
|
+
kanban_url: port ? `http://localhost:${port}` : null,
|
|
1124
|
+
project: projectRow,
|
|
1125
|
+
last_session: lastSession ? {
|
|
1126
|
+
summary: lastSession.summary,
|
|
1127
|
+
next_steps: lastSession.next_steps,
|
|
1128
|
+
when: lastSession.created_at
|
|
1129
|
+
} : null,
|
|
1130
|
+
recent_activity: recentActivity.slice(0, 20),
|
|
1131
|
+
task_summary: taskCounts,
|
|
1132
|
+
active_tasks: activeTasks,
|
|
1133
|
+
blocked_tasks: blockedTasks,
|
|
1134
|
+
recent_decisions: recentDecisions,
|
|
1135
|
+
context: contextItems
|
|
1136
|
+
};
|
|
1137
|
+
return {
|
|
1138
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
);
|
|
1142
|
+
server2.registerTool(
|
|
1143
|
+
"end_session",
|
|
1144
|
+
{
|
|
1145
|
+
title: "End Session",
|
|
1146
|
+
description: "End a work session with a summary of what was accomplished and what to do next. Call this when the user is done working.",
|
|
1147
|
+
inputSchema: {
|
|
1148
|
+
project: z5.string().optional().describe("Project name or ID"),
|
|
1149
|
+
summary: z5.string().describe("Summary of what was accomplished this session"),
|
|
1150
|
+
tasks_worked_on: z5.array(z5.string()).optional().describe("Task IDs that were worked on"),
|
|
1151
|
+
decisions_made: z5.array(z5.string()).optional().describe("Decision IDs that were made"),
|
|
1152
|
+
next_steps: z5.string().optional().describe("What to do next time")
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
async ({ project, summary, tasks_worked_on, decisions_made, next_steps }) => {
|
|
1156
|
+
const resolved = resolveProjectOrDefault(project);
|
|
1157
|
+
if (!resolved) {
|
|
1158
|
+
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
1159
|
+
}
|
|
1160
|
+
const db2 = getDb();
|
|
1161
|
+
const id = generateId();
|
|
1162
|
+
db2.prepare(
|
|
1163
|
+
`INSERT INTO sessions (id, project_id, summary, tasks_worked_on, decisions_made, next_steps) VALUES (?, ?, ?, ?, ?, ?)`
|
|
1164
|
+
).run(
|
|
1165
|
+
id,
|
|
1166
|
+
resolved.id,
|
|
1167
|
+
summary,
|
|
1168
|
+
tasks_worked_on ? JSON.stringify(tasks_worked_on) : null,
|
|
1169
|
+
decisions_made ? JSON.stringify(decisions_made) : null,
|
|
1170
|
+
next_steps ?? null
|
|
1171
|
+
);
|
|
1172
|
+
return {
|
|
1173
|
+
content: [{
|
|
1174
|
+
type: "text",
|
|
1175
|
+
text: JSON.stringify({ session_id: id, message: `Session ended for ${resolved.name}. Summary saved.` })
|
|
1176
|
+
}]
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1206
1180
|
}
|
|
1207
1181
|
|
|
1208
|
-
// src/
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
".woff": "font/woff",
|
|
1225
|
-
".woff2": "font/woff2",
|
|
1226
|
-
".ttf": "font/ttf"
|
|
1227
|
-
};
|
|
1228
|
-
function parseBody(req) {
|
|
1229
|
-
return new Promise((resolve2, reject) => {
|
|
1230
|
-
const chunks = [];
|
|
1231
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
1232
|
-
req.on("end", () => {
|
|
1233
|
-
if (chunks.length === 0) {
|
|
1234
|
-
resolve2({});
|
|
1235
|
-
return;
|
|
1182
|
+
// src/tools/queries.ts
|
|
1183
|
+
import { z as z6 } from "zod/v4";
|
|
1184
|
+
function registerQueryTools(server2) {
|
|
1185
|
+
server2.registerTool(
|
|
1186
|
+
"query",
|
|
1187
|
+
{
|
|
1188
|
+
title: "Query Database",
|
|
1189
|
+
description: "Execute a read-only SQL query against the database. Only SELECT statements are allowed. Use this for custom queries not covered by other tools.",
|
|
1190
|
+
inputSchema: {
|
|
1191
|
+
sql: z6.string().describe("SQL SELECT query to execute")
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
async ({ sql }) => {
|
|
1195
|
+
const trimmed = sql.trim();
|
|
1196
|
+
if (!trimmed.toUpperCase().startsWith("SELECT")) {
|
|
1197
|
+
return { content: [{ type: "text", text: "Only SELECT queries are allowed." }], isError: true };
|
|
1236
1198
|
}
|
|
1199
|
+
const db2 = getDb();
|
|
1237
1200
|
try {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1201
|
+
const stmt = db2.prepare(trimmed);
|
|
1202
|
+
if (!stmt.reader) {
|
|
1203
|
+
return { content: [{ type: "text", text: "Only read-only queries are allowed." }], isError: true };
|
|
1204
|
+
}
|
|
1205
|
+
const rows = stmt.all();
|
|
1206
|
+
return {
|
|
1207
|
+
content: [{ type: "text", text: JSON.stringify({ rows, count: rows.length }, null, 2) }]
|
|
1208
|
+
};
|
|
1209
|
+
} catch (e) {
|
|
1210
|
+
return { content: [{ type: "text", text: `Query error: ${e.message}` }], isError: true };
|
|
1241
1211
|
}
|
|
1242
|
-
});
|
|
1243
|
-
req.on("error", reject);
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
1246
|
-
function matchRoute(pattern, pathname) {
|
|
1247
|
-
const patternParts = pattern.split("/").filter(Boolean);
|
|
1248
|
-
const pathParts = pathname.split("/").filter(Boolean);
|
|
1249
|
-
if (patternParts.length !== pathParts.length) return null;
|
|
1250
|
-
const params = {};
|
|
1251
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
1252
|
-
if (patternParts[i].startsWith(":")) {
|
|
1253
|
-
params[patternParts[i].slice(1)] = decodeURIComponent(pathParts[i]);
|
|
1254
|
-
} else if (patternParts[i] !== pathParts[i]) {
|
|
1255
|
-
return null;
|
|
1256
1212
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1213
|
+
);
|
|
1214
|
+
server2.registerTool(
|
|
1215
|
+
"get_project_summary",
|
|
1216
|
+
{
|
|
1217
|
+
title: "Get Project Summary",
|
|
1218
|
+
description: "High-level summary of a project: total tasks by status, recent activity, open blockers, and upcoming priorities.",
|
|
1219
|
+
inputSchema: {
|
|
1220
|
+
project: z6.string().optional().describe("Project name or ID")
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
async ({ project }) => {
|
|
1224
|
+
const resolved = resolveProjectOrDefault(project);
|
|
1225
|
+
if (!resolved) {
|
|
1226
|
+
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
1227
|
+
}
|
|
1228
|
+
const db2 = getDb();
|
|
1229
|
+
const tasksByStatus = db2.prepare("SELECT status, COUNT(*) as count FROM tasks WHERE project_id = ? GROUP BY status").all(resolved.id);
|
|
1230
|
+
const blockers = db2.prepare("SELECT id, title, blocked_by FROM tasks WHERE project_id = ? AND status = 'blocked'").all(resolved.id);
|
|
1231
|
+
const upcomingPriorities = db2.prepare(
|
|
1232
|
+
`SELECT id, title, priority, status FROM tasks
|
|
1233
|
+
WHERE project_id = ? AND status IN ('todo', 'in_progress')
|
|
1234
|
+
ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END
|
|
1235
|
+
LIMIT 10`
|
|
1236
|
+
).all(resolved.id);
|
|
1237
|
+
const recentActivity = db2.prepare(
|
|
1238
|
+
`SELECT 'task' as type, title, updated_at FROM tasks WHERE project_id = ? AND updated_at > datetime('now', '-7 days')
|
|
1239
|
+
UNION ALL
|
|
1240
|
+
SELECT 'decision' as type, title, created_at as updated_at FROM decisions WHERE project_id = ? AND created_at > datetime('now', '-7 days')
|
|
1241
|
+
UNION ALL
|
|
1242
|
+
SELECT 'note' as type, substr(content, 1, 50) as title, created_at as updated_at FROM notes WHERE project_id = ? AND created_at > datetime('now', '-7 days')
|
|
1243
|
+
ORDER BY updated_at DESC
|
|
1244
|
+
LIMIT 20`
|
|
1245
|
+
).all(resolved.id, resolved.id, resolved.id);
|
|
1246
|
+
const totalNotes = db2.prepare("SELECT COUNT(*) as count FROM notes WHERE project_id = ?").get(resolved.id);
|
|
1247
|
+
const totalDecisions = db2.prepare("SELECT COUNT(*) as count FROM decisions WHERE project_id = ?").get(resolved.id);
|
|
1248
|
+
const totalSessions = db2.prepare("SELECT COUNT(*) as count FROM sessions WHERE project_id = ?").get(resolved.id);
|
|
1249
|
+
return {
|
|
1250
|
+
content: [{
|
|
1251
|
+
type: "text",
|
|
1252
|
+
text: JSON.stringify(
|
|
1253
|
+
{
|
|
1254
|
+
project: resolved.name,
|
|
1255
|
+
tasks_by_status: tasksByStatus,
|
|
1256
|
+
blockers,
|
|
1257
|
+
upcoming_priorities: upcomingPriorities,
|
|
1258
|
+
recent_activity: recentActivity,
|
|
1259
|
+
totals: { notes: totalNotes.count, decisions: totalDecisions.count, sessions: totalSessions.count }
|
|
1260
|
+
},
|
|
1261
|
+
null,
|
|
1262
|
+
2
|
|
1263
|
+
)
|
|
1264
|
+
}]
|
|
1265
|
+
};
|
|
1285
1266
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
await serveStatic(req, res);
|
|
1267
|
+
);
|
|
1268
|
+
server2.registerTool(
|
|
1269
|
+
"get_blockers",
|
|
1270
|
+
{
|
|
1271
|
+
title: "Get Blockers",
|
|
1272
|
+
description: "List all blocked tasks with what's blocking them.",
|
|
1273
|
+
inputSchema: {
|
|
1274
|
+
project: z6.string().optional().describe("Project name or ID")
|
|
1295
1275
|
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
if (!
|
|
1300
|
-
|
|
1276
|
+
},
|
|
1277
|
+
async ({ project }) => {
|
|
1278
|
+
const resolved = resolveProjectOrDefault(project);
|
|
1279
|
+
if (!resolved) {
|
|
1280
|
+
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
1301
1281
|
}
|
|
1282
|
+
const db2 = getDb();
|
|
1283
|
+
const blockers = db2.prepare("SELECT * FROM tasks WHERE project_id = ? AND status = 'blocked'").all(resolved.id);
|
|
1284
|
+
const enriched = blockers.map((task) => {
|
|
1285
|
+
let blockingTasks = [];
|
|
1286
|
+
if (task.blocked_by) {
|
|
1287
|
+
try {
|
|
1288
|
+
const ids = JSON.parse(task.blocked_by);
|
|
1289
|
+
blockingTasks = ids.map((id) => {
|
|
1290
|
+
const blocking = db2.prepare("SELECT id, title, status FROM tasks WHERE id = ?").get(id);
|
|
1291
|
+
return blocking ?? { id, title: "Unknown task", status: "unknown" };
|
|
1292
|
+
});
|
|
1293
|
+
} catch {
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return { ...task, blocking_tasks: blockingTasks };
|
|
1297
|
+
});
|
|
1298
|
+
return {
|
|
1299
|
+
content: [{ type: "text", text: JSON.stringify({ project: resolved.name, blockers: enriched }, null, 2) }]
|
|
1300
|
+
};
|
|
1302
1301
|
}
|
|
1303
|
-
|
|
1304
|
-
server2.
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1302
|
+
);
|
|
1303
|
+
server2.registerTool(
|
|
1304
|
+
"search",
|
|
1305
|
+
{
|
|
1306
|
+
title: "Search Everything",
|
|
1307
|
+
description: "Full-text search across tasks, notes, and decisions for a project.",
|
|
1308
|
+
inputSchema: {
|
|
1309
|
+
project: z6.string().optional().describe("Project name or ID"),
|
|
1310
|
+
query: z6.string().describe("Search query")
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
async ({ project, query }) => {
|
|
1314
|
+
const resolved = resolveProjectOrDefault(project);
|
|
1315
|
+
if (!resolved) {
|
|
1316
|
+
return { content: [{ type: "text", text: project ? `Project "${project}" not found.` : "No active projects found." }], isError: true };
|
|
1317
|
+
}
|
|
1318
|
+
const db2 = getDb();
|
|
1319
|
+
const pattern = `%${query}%`;
|
|
1320
|
+
const tasks = db2.prepare("SELECT id, title, description, status, priority, 'task' as type FROM tasks WHERE project_id = ? AND (title LIKE ? OR description LIKE ?)").all(resolved.id, pattern, pattern);
|
|
1321
|
+
const notes = db2.prepare("SELECT id, content, category, 'note' as type FROM notes WHERE project_id = ? AND content LIKE ?").all(resolved.id, pattern);
|
|
1322
|
+
const decisions = db2.prepare("SELECT id, title, decision, reasoning, 'decision' as type FROM decisions WHERE project_id = ? AND (title LIKE ? OR decision LIKE ? OR reasoning LIKE ?)").all(resolved.id, pattern, pattern, pattern);
|
|
1323
|
+
return {
|
|
1324
|
+
content: [{
|
|
1325
|
+
type: "text",
|
|
1326
|
+
text: JSON.stringify(
|
|
1327
|
+
{
|
|
1328
|
+
project: resolved.name,
|
|
1329
|
+
query,
|
|
1330
|
+
results: { tasks, notes, decisions },
|
|
1331
|
+
total: tasks.length + notes.length + decisions.length
|
|
1332
|
+
},
|
|
1333
|
+
null,
|
|
1334
|
+
2
|
|
1335
|
+
)
|
|
1336
|
+
}]
|
|
1337
|
+
};
|
|
1313
1338
|
}
|
|
1314
|
-
|
|
1315
|
-
server2.listen(port, () => {
|
|
1316
|
-
process.stderr.write(`[mindpm] Kanban UI available at http://localhost:${port}
|
|
1317
|
-
`);
|
|
1318
|
-
});
|
|
1319
|
-
return server2;
|
|
1339
|
+
);
|
|
1320
1340
|
}
|
|
1321
1341
|
|
|
1322
1342
|
// src/index.ts
|
|
1323
1343
|
var server = new McpServer(
|
|
1324
1344
|
{
|
|
1325
1345
|
name: "mindpm",
|
|
1326
|
-
version: "1.2.
|
|
1346
|
+
version: "1.2.1"
|
|
1327
1347
|
},
|
|
1328
1348
|
{
|
|
1329
1349
|
capabilities: {
|