diff --git a/integrations/kubernetes-deployment/index.ts b/integrations/kubernetes-deployment/index.ts index 906fdeb1..e8c1769a 100644 --- a/integrations/kubernetes-deployment/index.ts +++ b/integrations/kubernetes-deployment/index.ts @@ -133,12 +133,13 @@ server.registerTool( const client = await pool.connect(); try { const result = await client.queryObject<{ + id: string; content: string; metadata: Record; similarity: number; created_at: string; }>( - `SELECT content, metadata, created_at, + `SELECT id, content, metadata, created_at, 1 - (embedding <=> $1::vector) AS similarity FROM thoughts WHERE 1 - (embedding <=> $1::vector) >= $2 @@ -157,6 +158,7 @@ server.registerTool( const m = t.metadata || {}; const parts = [ `--- Result ${i + 1} (${(t.similarity * 100).toFixed(1)}% match) ---`, + `ID: ${t.id}`, `Captured: ${new Date(t.created_at).toLocaleDateString()}`, `Type: ${m.type || "unknown"}`, ]; @@ -235,11 +237,12 @@ server.registerTool( const client = await pool.connect(); try { const result = await client.queryObject<{ + id: string; content: string; metadata: Record; created_at: string; }>( - `SELECT content, metadata, created_at + `SELECT id, content, metadata, created_at FROM thoughts ${whereClause} ORDER BY created_at DESC @@ -254,7 +257,7 @@ server.registerTool( const results = result.rows.map((t, i) => { const m = t.metadata || {}; const tags = Array.isArray(m.topics) ? (m.topics as string[]).join(", ") : ""; - return `${i + 1}. [${new Date(t.created_at).toLocaleDateString()}] (${m.type || "??"}${tags ? " - " + tags : ""})\n ${t.content}`; + return `${i + 1}. [${t.id}] [${new Date(t.created_at).toLocaleDateString()}] (${m.type || "??"}${tags ? " - " + tags : ""})\n ${t.content}`; }); return { @@ -410,6 +413,62 @@ server.registerTool( } ); +// Tool 5: Delete Thought +server.registerTool( + "delete_thought", + { + title: "Delete Thought", + description: + "Delete a thought from the Open Brain by its ID. Irreversible — use search_thoughts or list_thoughts to confirm the correct ID before deleting. Operates across the full database regardless of user context (single-user deployments only).", + inputSchema: { + id: z.string().describe("The ID of the thought to delete (from search_thoughts or list_thoughts output)"), + }, + }, + async ({ id }) => { + try { + const client = await pool.connect(); + try { + // First fetch the thought so we can confirm what was deleted + const existing = await client.queryObject<{ id: string; content: string; metadata: Record }>( + `SELECT id, content, metadata FROM thoughts WHERE id = $1`, + [id] + ); + + if (!existing.rows.length) { + return { + content: [{ type: "text" as const, text: `No thought found with ID: ${id}` }], + isError: true, + }; + } + + await client.queryObject(`DELETE FROM thoughts WHERE id = $1`, [id]); + + const t = existing.rows[0]; + const m = (t.metadata || {}) as Record; + const preview = t.content.length > 100 + ? t.content.substring(0, 100) + "..." + : t.content; + + return { + content: [ + { + type: "text" as const, + text: `Deleted thought ${id} (${m.type || "unknown"}):\n${preview}`, + }, + ], + }; + } finally { + client.release(); + } + } catch (err: unknown) { + return { + content: [{ type: "text" as const, text: `Error: ${(err as Error).message}` }], + isError: true, + }; + } + } +); + // --- Hono App with Auth Check --- const app = new Hono(); diff --git a/server/index.ts b/server/index.ts index b40149f8..114f6f73 100644 --- a/server/index.ts +++ b/server/index.ts @@ -113,6 +113,7 @@ server.registerTool( const results = data.map( ( t: { + id: string; content: string; metadata: Record; similarity: number; @@ -123,6 +124,7 @@ server.registerTool( const m = t.metadata || {}; const parts = [ `--- Result ${i + 1} (${(t.similarity * 100).toFixed(1)}% match) ---`, + `ID: ${t.id}`, `Captured: ${new Date(t.created_at).toLocaleDateString()}`, `Type: ${m.type || "unknown"}`, ]; @@ -173,7 +175,7 @@ server.registerTool( try { let q = supabase .from("thoughts") - .select("content, metadata, created_at") + .select("id, content, metadata, created_at") .order("created_at", { ascending: false }) .limit(limit); @@ -201,12 +203,12 @@ server.registerTool( const results = data.map( ( - t: { content: string; metadata: Record; created_at: string }, + t: { id: string; content: string; metadata: Record; created_at: string }, i: number ) => { const m = t.metadata || {}; const tags = Array.isArray(m.topics) ? (m.topics as string[]).join(", ") : ""; - return `${i + 1}. [${new Date(t.created_at).toLocaleDateString()}] (${m.type || "??"}${tags ? " - " + tags : ""})\n ${t.content}`; + return `${i + 1}. [${t.id}] [${new Date(t.created_at).toLocaleDateString()}] (${m.type || "??"}${tags ? " - " + tags : ""})\n ${t.content}`; } ); @@ -362,6 +364,66 @@ server.registerTool( } ); +// Tool 5: Delete Thought +server.registerTool( + "delete_thought", + { + title: "Delete Thought", + description: + "Delete a thought from the Open Brain by its ID. Irreversible — use search_thoughts or list_thoughts to confirm the correct ID before deleting. Operates across the full database regardless of user context (single-user deployments only).", + inputSchema: { + id: z.string().describe("The UUID of the thought to delete (from search_thoughts or list_thoughts output)"), + }, + }, + async ({ id }) => { + try { + // First fetch the thought so we can confirm what was deleted + const { data: existing, error: fetchError } = await supabase + .from("thoughts") + .select("id, content, metadata") + .eq("id", id) + .single(); + + if (fetchError || !existing) { + return { + content: [{ type: "text" as const, text: `No thought found with ID: ${id}` }], + isError: true, + }; + } + + const { error } = await supabase + .from("thoughts") + .delete() + .eq("id", id); + + if (error) { + return { + content: [{ type: "text" as const, text: `Failed to delete: ${error.message}` }], + isError: true, + }; + } + + const m = (existing.metadata || {}) as Record; + const preview = existing.content.length > 100 + ? existing.content.substring(0, 100) + "..." + : existing.content; + return { + content: [ + { + type: "text" as const, + text: `Deleted thought ${id} (${m.type || "unknown"}):\n${preview}`, + }, + ], + }; + } catch (err: unknown) { + return { + content: [{ type: "text" as const, text: `Error: ${(err as Error).message}` }], + isError: true, + }; + } + } +); + // --- Hono App with Auth + CORS --- const corsHeaders = {