feat: Add zap command for sending Bitcoin Lightning tips

- Implemented the `zap` command in the CLI to allow users to send sats to a user, event, or snippet using a NIP-60 wallet.
- Created a new `zap.ts` file to handle the command logic and integrated it into the MCP server.
- Added wallet balance command to check the balance of a user's wallet.
- Enhanced the MCP server to register the new zap command and wallet balance command.
- Introduced caching for wallets to optimize performance and reduce redundant network requests.
- Updated database schema to include snippets table for storing code snippets.
- Improved logging functionality for better debugging and tracking of operations.
- Added functionality to save snippets to the database upon retrieval.
- Updated project overview documentation to reflect new features and structure.
- Refactored existing commands and logic for better modularity and maintainability.
This commit is contained in:
pablof7z
2025-04-08 18:10:16 +01:00
parent f74736191a
commit 10fbca0824
19 changed files with 723 additions and 35 deletions

View File

@@ -1,4 +1,5 @@
import type { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
import { db } from "../../db.js";
import { ndk } from "../../ndk.js";
import type { CodeSnippet, FindSnippetsParams } from "../types/index.js";
import { log } from "../utils/log.js";
@@ -58,15 +59,22 @@ export async function getSnippets(params: FindSnippetsParams = {}): Promise<{
let maxMatchCount = 0;
/**
* Function to calculate the number of tags in an event that match the search tags.
* Used for ranking snippets based on tag relevance.
* If no tags are provided in params, all snippets are considered equally relevant (match count = 1).
*/
function getMatchCount(event: NDKEvent) {
if (!params.tags || params.tags.length === 0) return 1;
const aTags = event.tags
.filter((tag) => tag[0] === "t")
.map((tag) => tag[1])
.filter((t) => t !== undefined);
return params.tags.filter((tag) =>
aTags.some((t) => t.match(new RegExp(tag, "i")))
.filter((tag) => tag[0] === "t") // Filter for 't' tags
.map((tag) => tag[1]) // Get the tag value
.filter((t): t is string => t !== undefined); // Ensure tag value exists and narrow type
// Count how many of the searched tags are present in the event's tags
return params.tags.filter((searchTag) =>
aTags.some((eventTag) => eventTag.match(new RegExp(searchTag, "i"))) // Case-insensitive tag matching
).length;
}
@@ -90,6 +98,39 @@ export async function getSnippets(params: FindSnippetsParams = {}): Promise<{
const snippets = selectedEvents.map(toSnippet);
const otherSnippets = notSelectedEvents.map(toSnippet);
// --- BEGIN DATABASE INSERTION ---
const allSnippets = [...snippets, ...otherSnippets];
if (allSnippets.length > 0) {
log(`Saving ${allSnippets.length} snippets to the database...`);
const insertStmt = db.prepare(`
INSERT OR REPLACE INTO snippets (id, title, description, code, language, pubkey, createdAt, tags)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
try {
db.transaction(() => {
for (const snippet of allSnippets) {
insertStmt.run(
snippet.id,
snippet.title,
snippet.description,
snippet.code,
snippet.language,
snippet.pubkey,
snippet.createdAt,
JSON.stringify(snippet.tags) // Store tags as JSON string
);
}
})(); // Immediately invoke the transaction
log("Snippets saved successfully.");
} catch (error) {
console.error("Failed to save snippets to database:", error);
// Decide if we should throw or just log the error
// For now, just log it and continue returning fetched snippets
}
}
// --- END DATABASE INSERTION ---
return { snippets, otherSnippets };
}