Skip to content

Instantly share code, notes, and snippets.

@merlinmann
Last active January 20, 2026 13:16
Show Gist options
  • Select an option

  • Save merlinmann/e3895000d9f6787a517fd128cf08e21a to your computer and use it in GitHub Desktop.

Select an option

Save merlinmann/e3895000d9f6787a517fd128cf08e21a to your computer and use it in GitHub Desktop.
How Claude Code integrates with Drafts app - for Greg Pierce

Drafts Integration with Claude Code

For: Greg Pierce, Drafts developer
From: A Claude Code user's repo

The Setup

Claude Code is Anthropic's CLI tool for AI-assisted coding. It runs in Terminal/Prompt and has filesystem access plus shell execution. This creates an interesting integration point with Drafts.

How We Connect

1. AppleScript Bridge (bidirectional)

Claude Code can read and write drafts via osascript:

# Read the current draft
osascript -e 'tell application "Drafts" to return content of current draft'

# Create a new draft
osascript -e 'tell application "Drafts" to make new draft with properties {content:"Hello from Claude"}'

This lets an AI assistant pull context from wherever you were writing, or push results back.

2. URL Scheme (for targeting specific drafts)

open "drafts://search?query=GENERATED%20TODAY%20NOTE"
open "drafts://open?uuid=XXXXXXXX"

3. Drafts Actions as Code Artifacts

The user stores Drafts action JavaScript in their repo at config/drafts/:

Action What It Does
today-note.js Creates or opens a daily note, uses HTML comment marker for disambiguation
insert-today-link.js Inserts [[Today Wednesday 2026-01-20]] wiki-link at cursor
count-tags.js Queries all drafts, outputs tag frequency table as Markdown

These are version-controlled, shareable, and Claude Code can read/modify them.

Patterns That Might Be Interesting

Disambiguation via HTML comments

let marker = `<!-- GENERATED TODAY NOTE -->`;
let existing = Draft.query("", "all", []).filter(d =>
  d.title === title && d.content.includes(marker)
);

Using invisible markers to identify programmatically-created drafts, avoiding title collisions.

AI as Drafts Action author

Claude Code wrote count-tags.js from a natural language request. The user described wanting tag analytics; Claude produced working Drafts JavaScript using the Drafts scripting API.

Cross-tool workflow

  • Capture in Drafts (mobile or Mac)
  • Claude Code reads via AppleScript
  • AI processes/transforms
  • Result pushed back to Drafts or written to repo

Current Limitations Discovered

  • Can't bulk-query tags via AppleScript — tag data lives in CloudKit, not exposed to AppleScript. Workaround: Drafts Action that outputs to clipboard/file.
  • No direct CloudKit access — Claude Code can't query the Drafts database directly.
  • URL scheme is fire-and-forget — no return value, so x-callback-url patterns need Shortcuts or another callback handler.

What Would Make This Better

  1. AppleScript search drafts command returning a list of draft references
  2. Export tag list via AppleScript (even just tag names)
  3. Headless action execution — run a Drafts Action from CLI without UI focus

Example Actions

today-note.js

// Drafts Action: Create/Open Today Note
// Disambiguation: Uses marker <!-- GENERATED TODAY NOTE --> to identify these drafts

let now = new Date();

let options = {
  timeZone: "America/Los_Angeles",
  weekday: "long",
  year: "numeric",
  month: "2-digit",
  day: "2-digit"
};

let formatter = new Intl.DateTimeFormat("en-US", options);
let parts = formatter.formatToParts(now);

let weekday = parts.find(p => p.type === "weekday").value;
let month = parts.find(p => p.type === "month").value;
let day = parts.find(p => p.type === "day").value;
let year = parts.find(p => p.type === "year").value;

let iso = `${year}-${month}-${day}`;
let title = `Today ${weekday} ${iso}`;
let marker = `<!-- GENERATED TODAY NOTE -->`;

let existing = Draft.query("", "all", []).filter(d =>
  d.title === title && d.content.includes(marker)
);

let targetDraft;

if (existing.length > 0) {
  targetDraft = existing[0];
} else {
  targetDraft = Draft.create();
  targetDraft.content = `${title}\n\n${marker}\n\n- `;
  targetDraft.update();
}

editor.load(targetDraft);
editor.activate();

count-tags.js

// Drafts Action: Count Tag Usage
// Outputs top tags sorted by frequency

let allDrafts = Draft.query("", "all", []);
let tagCounts = {};

for (let d of allDrafts) {
    for (let tag of d.tags) {
        tagCounts[tag] = (tagCounts[tag] || 0) + 1;
    }
}

let sorted = Object.entries(tagCounts)
    .sort((a, b) => b[1] - a[1]);

let output = `# Tag Usage (${allDrafts.length} drafts)\n\n`;
output += `| Tag | Count |\n|-----|-------|\n`;

for (let [tag, count] of sorted.slice(0, 50)) {
    output += `| ${tag} | ${count} |\n`;
}

if (sorted.length > 50) {
    output += `\n_...and ${sorted.length - 50} more tags_\n`;
}

let resultDraft = Draft.create();
resultDraft.content = output;
resultDraft.update();
editor.load(resultDraft);

Created by Merlin Mann via Claude Code, January 2026

Comments are disabled for this gist.