Action
Query Drafts with Claude
Name: AI Note Query
Description:
Ask a question in plain language and let Claude search your notes to answer it. Uses a two-pass approach: first, note summaries are sent to Claude to identify relevant drafts; then the full content of those drafts is used to generate a grounded answer. Results are saved as a new draft or appended to your current one, with wiki-style [[links]] back to source notes.
Setup:
Requires the Anthropic AI credentials configured in Drafts (Settings → Credentials → Anthropic).
Usage:
1. Run the action from any draft
2. Type your question in plain language
3. Select your preferred Claude model
4. Choose whether to create a new draft or append to the current one
5. The answer appears with a timestamp, model name, and links to source notes
Caveats & Privacy:
- Note previews are sent to the Anthropic API. During the filtering pass, the first line and a 150-character preview of every draft in your collection is transmitted to Claude to determine relevance. Your full Drafts library size and note titles are included in this request.
- Relevant note content is sent in full. Notes identified as relevant are sent to the API in their entirety during the answer pass.
- This applies to all drafts in your library, including archived drafts. Review your Anthropic data retention settings if this is a concern.
- The script does not store or transmit credentials — API authentication is handled by Drafts’ built-in Anthropic integration.
Steps
-
script
// AI-Powered Note Query for Drafts // Two-pass approach: Filter relevant notes, then answer with context // Get available Claude models const models = AnthropicAI.knownModels(); // === HELPER FUNCTIONS === function generateDraftSummaries(drafts) { return drafts.map((d, index) => { const firstLine = d.content.split('\n')[0] || "(Untitled)"; const preview = d.content.substring(0, 150).replace(/\n/g, ' '); const tags = d.tags.length > 0 ? ` [Tags: ${d.tags.join(', ')}]` : ''; return { index: index, uuid: d.uuid, summary: `[${index}] ${firstLine}\n${preview}${tags}` }; }); } function filterRelevantDrafts(ai, model, question, summaries) { const summaryText = summaries.map(s => s.summary).join('\n\n'); const filterPrompt = `You are helping filter notes for relevance to a user's question. USER'S QUESTION: "${question}" AVAILABLE NOTES: ${summaryText} Task: Analyze which notes are relevant to answering the user's question. Return a JSON array of note indices that are relevant. Be generous in your selection - include notes that might contain useful context or related information. IMPORTANT: - Return ONLY a valid JSON array of numbers, nothing else - If no notes are relevant, return an empty array: [] - Example valid responses: [0, 3, 7] or [] or [1, 2, 3, 4, 5] Response:`; try { const response = ai.quickPrompt(filterPrompt, {"model": model, "temperature": 0}); // Clean up response - remove any markdown code blocks let cleaned = response.trim(); cleaned = cleaned.replace(/```json\n?/g, '').replace(/```\n?/g, ''); const relevantIndices = JSON.parse(cleaned); return relevantIndices; } catch (e) { console.log("Error parsing filter response: " + e.message); console.log("Raw response: " + response); // If parsing fails, return empty array (will answer without notes) return []; } } function answerWithContext(ai, model, question, relevantDrafts) { let prompt; if (relevantDrafts.length === 0) { prompt = `The user asked: "${question}" No relevant notes were found in their collection. Please answer this question using your general knowledge. Start your response by saying "No relevant notes found in your collection." and then provide a helpful answer.`; } else { const contextText = relevantDrafts.map((d, i) => { const title = d.content.split('\n')[0] || "(Untitled)"; return `--- Note ${i + 1}: ${title} ---\n${d.content}`; }).join('\n\n'); prompt = `You are answering a question using the user's personal notes as context. USER'S QUESTION: "${question}" RELEVANT NOTES FROM USER'S COLLECTION: ${contextText} Task: Answer the user's question based primarily on the information in their notes. If the notes don't fully answer the question, you may augment with your general knowledge, but clearly distinguish between what's from their notes vs. your knowledge. Be conversational and helpful. Do not explicitly list which notes you're referencing unless it adds value to the answer. Answer:`; } return ai.quickPrompt(prompt, {"model": model}); } // === MAIN SCRIPT === let f = () => { // Prompt user for question, model, and output preference let p = new Prompt(); p.title = "AI Note Query"; p.message = "Ask a question about your notes"; p.addTextView("question", "Your Question", ""); p.addLabel("label1", "Claude will search your notes and answer your question"); p.addSelect("model", "Model", models, [models[0]], false); p.addSelect("output", "Output", ["New Draft", "Append to Current"], ["New Draft"], false ); p.addButton("Search & Answer"); if (!p.show()) { return false; } const question = p.fieldValues["question"]; const selectedModel = p.fieldValues["model"][0]; const outputMode = p.fieldValues["output"][0]; if (!question || question.trim().length === 0) { alert("Please enter a question."); return false; } // Get all drafts const allDrafts = Draft.query("", "all", [], [], "modified", false, false); console.log(`Found ${allDrafts.length} total drafts`); // Generate summaries const summaries = generateDraftSummaries(allDrafts); // Initialize AI const ai = new AnthropicAI(); // PASS 1: Filter relevant drafts console.log("Pass 1: Filtering relevant drafts..."); const relevantIndices = filterRelevantDrafts(ai, selectedModel, question, summaries); console.log(`Found ${relevantIndices.length} relevant drafts`); // Get full content of relevant drafts const relevantDrafts = relevantIndices.map(idx => allDrafts[idx]); // PASS 2: Answer the question console.log("Pass 2: Generating answer..."); const answer = answerWithContext(ai, selectedModel, question, relevantDrafts); // Check if we got an answer if (!answer || answer.length === 0) { alert("No response from Claude. Please check your API credentials."); return false; } // Format the result const timestamp = new Date().toLocaleString(); // Add sources section with wiki-style links if we found relevant drafts let sourcesSection = ""; if (relevantDrafts.length > 0) { const sourceLinks = relevantDrafts.map(d => { const title = d.content.split('\n')[0].replace(/^#+\s*/, '') || "(Untitled)"; return `- [[${title}]]`; }).join('\n'); sourcesSection = `\n\n## Sources\n\n${sourceLinks}`; } const result = `# Q: ${question} **Model:** ${selectedModel} **Date:** ${timestamp} **Notes Found:** ${relevantIndices.length} --- ${answer}${sourcesSection}`; // Output based on user preference if (outputMode === "New Draft") { const newDraft = Draft.create(); newDraft.content = result; newDraft.addTag("project/temporary/ai-query"); newDraft.update(); // Load the new draft in the editor editor.load(newDraft); console.log("Created new draft with answer"); } else { // Append to current draft draft.content = draft.content + "\n\n" + result; draft.update(); console.log("Appended answer to current draft"); } // Show success message app.displaySuccessMessage("Query complete!"); return true; }; // Run the function if (!f()) { context.fail(); }
Options
-
After Success Default Notification Info Log Level Info