Skip to content

Instantly share code, notes, and snippets.

@mybuddymichael
Created November 12, 2025 18:09
Show Gist options
  • Select an option

  • Save mybuddymichael/1631d9a90edff633a2426dcc9067c06d to your computer and use it in GitHub Desktop.

Select an option

Save mybuddymichael/1631d9a90edff633a2426dcc9067c06d to your computer and use it in GitHub Desktop.
Claude skill to save design inspiration in my Obsidian notes
#!/usr/bin/env bun
import { $ } from "bun";
import { readdir } from "node:fs/promises";
import { createHash } from "node:crypto";
// Parse arguments
const [sourceUrl, reason, imageUrl] = Bun.argv.slice(2);
if (!sourceUrl) {
console.error("Usage: dinspo.ts <source_url> [reason] [image_url]");
process.exit(1);
}
// Get current date and time
const now = new Date();
const date = now.toISOString().split('T')[0]; // YYYY-MM-DD
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const time = `${hours}${minutes}`; // HHMM
// Generate filename from reason
function generateFilename(reason: string | undefined, date: string, time: string): string {
if (!reason) {
return `Design inspiration ${date} ${time}`;
}
let filename = reason.substring(0, 50);
// Sentence case (capitalize first letter)
filename = filename.charAt(0).toUpperCase() + filename.slice(1);
// Remove trailing period
filename = filename.replace(/\.$/, '');
return filename;
}
const summaryFilename = generateFilename(reason, date, time);
// Hash a file
async function hashFile(path: string): Promise<string> {
const bytes = await Bun.file(path).bytes();
return createHash('sha256').update(bytes).digest('hex');
}
// Check for duplicates in media directory
async function isDuplicate(filePath: string): Promise<string | null> {
const newHash = await hashFile(filePath);
try {
const files = await readdir('media');
for (const file of files) {
const existingPath = `media/${file}`;
try {
const existingHash = await hashFile(existingPath);
if (existingHash === newHash) {
return file;
}
} catch (err) {
// Skip files that can't be read
continue;
}
}
} catch (err) {
console.error("Error reading media directory:", err);
}
return null;
}
// Main logic
async function main() {
let mediaFilename: string;
let mediaExtension: string;
let tmpPath: string;
let finalPath: string;
try {
if (imageUrl) {
// Download image to /tmp
const tmpImage = `/tmp/dinspo-${Date.now()}.tmp`;
console.log(`πŸ“₯ Downloading image from ${imageUrl}...`);
const curlResult = await $`curl -sL ${imageUrl} -o ${tmpImage}`.nothrow();
if (curlResult.exitCode !== 0) {
console.error("❌ Failed to download image");
process.exit(1);
}
// Convert to WebP in temp
mediaExtension = 'webp';
mediaFilename = `${summaryFilename}.${mediaExtension}`;
tmpPath = `/tmp/dinspo-${Date.now()}.webp`;
console.log(`πŸ”„ Converting to WebP...`);
const cwebpResult = await $`cwebp ${tmpImage} -o ${tmpPath}`.nothrow();
if (cwebpResult.exitCode !== 0) {
console.error("❌ Failed to convert to WebP");
process.exit(1);
}
// Clean up temp image
await $`rm ${tmpImage}`.nothrow();
} else {
// Download video to temp with yt-dlp
mediaExtension = 'mp4';
mediaFilename = `${summaryFilename}.${mediaExtension}`;
tmpPath = `/tmp/dinspo-${Date.now()}.mp4`;
console.log(`πŸ“₯ Downloading video from ${sourceUrl}...`);
const ytdlpResult = await $`yt-dlp ${sourceUrl} -o ${tmpPath}`.nothrow();
if (ytdlpResult.exitCode !== 0) {
console.error("❌ Failed to download video");
process.exit(1);
}
}
// Check for duplicates before moving to final location
console.log(`πŸ” Checking for duplicates...`);
const duplicate = await isDuplicate(tmpPath);
if (duplicate) {
console.log(`⚠️ Duplicate found: ${duplicate}`);
console.log(`πŸ—‘οΈ Removing downloaded file...`);
await $`rm ${tmpPath}`.nothrow();
process.exit(1);
}
// Move to final location
finalPath = `media/${mediaFilename}`;
await $`mv ${tmpPath} ${finalPath}`.nothrow();
// Read template
const template = await Bun.file('templates/design inspiration template.md').text();
// Create note content
const reasonField = reason ? `reason: ${reason}\n` : '';
const noteContent = `---
tags:
- design-inspiration
created: ${date}
time: ${time}
source: ${sourceUrl}
${reasonField}media: "[[${mediaFilename}]]"
---
![[${mediaFilename}]]
`;
// Write note
const notePath = `${summaryFilename}.md`;
await Bun.write(notePath, noteContent);
console.log(`βœ… Created note: ${notePath}`);
console.log(`πŸ“ Saved media: ${finalPath}`);
} catch (error) {
console.error("❌ Error:", error);
process.exit(1);
}
}
main();
name description allowed-tools
save-design-inspiration
Save design inspiration from URLs (tweets, websites, videos) with media. Use when user shares URLs for design inspiration, mentions 'dinspo' or 'design inspiration', or wants to save visual references.
Bash(mise:*), Bash(bun:*)

Save Design Inspiration

Saves design inspiration from URLs with automatic media download and note creation.

When to use

  • User shares a URL (tweet, website, video) and mentions it's design inspiration
  • User explicitly says "dinspo" or "design inspiration"
  • User wants to save a visual reference with context

Instructions

  1. Parse user input to extract:

    • Source URL (required)
    • Reason for inspiration (optional)
    • Optional image URL (if provided separately from source)
  2. Run the script:

    mise exec -- bun .claude/skills/save-design-inspiration/scripts/dinspo.ts "<source_url>" ["<reason>"] ["<image_url>"]
  3. Script handles:

    • Downloads media (image or video)
    • Converts images to WebP
    • Checks for duplicates
    • Creates markdown note in current directory

Examples

With image URL:

User: "Save this design inspo: https://twitter.com/user/status/123 - Great use of whitespace https://example.com/image.png"
β†’ mise exec -- bun .claude/skills/save-design-inspiration/scripts/dinspo.ts "https://twitter.com/user/status/123" "Great use of whitespace" "https://example.com/image.png"

Video from URL:

User: "dinspo https://youtube.com/watch?v=abc - Smooth animations"
β†’ mise exec -- bun .claude/skills/save-design-inspiration/scripts/dinspo.ts "https://youtube.com/watch?v=abc" "Smooth animations"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment