Skip to content

Instantly share code, notes, and snippets.

@vojtaholik
Created December 18, 2025 12:28
Show Gist options
  • Select an option

  • Save vojtaholik/48482d57fc5fdf3489189c56bb9d20e9 to your computer and use it in GitHub Desktop.

Select an option

Save vojtaholik/48482d57fc5fdf3489189c56bb9d20e9 to your computer and use it in GitHub Desktop.
// Name: Save to MyMind
// Description: Save bookmarks, notes, or images to MyMind
// Shortcut: cmd+shift+s
import "@johnlindquist/kit";
import { readFile } from "node:fs/promises";
import { extname, basename } from "node:path";
const MYMIND_URL = "http://localhost:1234";
type Action =
| "bookmark-page"
| "bookmark-clipboard"
| "note"
| "image-clipboard"
| "image-file";
const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
// Get media type from file extension
function getMediaType(filePath: string): string | null {
const ext = extname(filePath).toLowerCase();
const map: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
};
return map[ext] || null;
}
// Check if a file is an image based on extension
function isImageFile(filePath: string): boolean {
const ext = extname(filePath).toLowerCase();
return IMAGE_EXTENSIONS.includes(ext);
}
// Check if Finder is the frontmost app
async function isFinderFrontmost(): Promise<boolean> {
try {
const frontApp = await applescript(`
tell application "System Events"
set frontApp to name of first application process whose frontmost is true
end tell
return frontApp
`);
return frontApp === "Finder";
} catch {
return false;
}
}
// Get selected image files from Finder
async function getSelectedImageFiles(): Promise<string[]> {
try {
// getSelectedFile works best when Finder is frontmost
const selected = await getSelectedFile();
if (!selected) return [];
const files = selected.split("\n").filter((f) => f.trim());
return files.filter(isImageFile);
} catch {
return [];
}
}
// Check if Chrome/Arc/Safari is the frontmost app
async function getBrowserUrl(): Promise<string | null> {
const browsers = [
"Google Chrome",
"Arc",
"Safari",
"Brave Browser",
"Microsoft Edge",
];
try {
const frontApp = await applescript(`
tell application "System Events"
set frontApp to name of first application process whose frontmost is true
end tell
return frontApp
`);
if (!browsers.some((b) => frontApp.includes(b))) {
return null;
}
// Get URL from the active tab
const appName =
browsers.find((b) => frontApp.includes(b)) || "Google Chrome";
if (appName === "Safari") {
return await applescript(`
tell application "Safari"
return URL of current tab of front window
end tell
`);
} else {
// Chrome-based browsers
return await applescript(`
tell application "${appName}"
return URL of active tab of front window
end tell
`);
}
} catch {
return null;
}
}
// Check clipboard for image
async function getClipboardImage(): Promise<{
data: Buffer;
mediaType: string;
} | null> {
try {
const imageBuffer = await clipboard.readImage();
if (imageBuffer && imageBuffer.byteLength > 0) {
// Script Kit returns PNG format from clipboard
return { data: imageBuffer, mediaType: "image/png" };
}
} catch {
// No image in clipboard
}
return null;
}
// Save a bookmark
async function saveBookmark(url: string, notes?: string): Promise<void> {
const res = await fetch(`${MYMIND_URL}/api/items`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url, notes }),
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || "Failed to save bookmark");
}
await notify({
title: data.isNew ? "Bookmark Saved" : "Bookmark Already Exists",
message: data.item?.title || url,
});
}
// Save a note
async function saveNote(content: string, title?: string): Promise<void> {
const res = await fetch(`${MYMIND_URL}/api/notes`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content, title }),
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || "Failed to save note");
}
await notify({
title: "Note Saved",
message: data.item?.title || "New note saved",
});
}
// Save an image via the chat API (for AI description)
async function saveImage(imageData: Buffer, mediaType: string): Promise<void> {
const base64 = imageData.toString("base64");
// Use the chat API to let Claude analyze and save the image
const res = await fetch(`${MYMIND_URL}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: mediaType,
data: base64,
},
},
{
type: "text",
text: "Save this image to my collection.",
},
],
},
],
}),
});
if (!res.ok) {
throw new Error("Failed to save image");
}
// The response is SSE, we need to consume it
const reader = res.body?.getReader();
if (!reader) throw new Error("No response body");
const decoder = new TextDecoder();
let saved = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
try {
const event = JSON.parse(line.slice(6));
// result exists means success, error exists means failure
if (
event.type === "tool_call_result" &&
event.result &&
!event.error
) {
saved = true;
}
} catch {
// ignore parse errors
}
}
}
}
if (saved) {
await notify({
title: "Image Saved",
message: "Image analyzed and saved to MyMind",
});
} else {
throw new Error("Image save not confirmed");
}
}
// Main flow
async function main() {
const finderIsFront = await isFinderFrontmost();
// Only check for selected files if Finder is frontmost
const selectedImageFiles = finderIsFront ? await getSelectedImageFiles() : [];
// Only check for browser URL if Finder is NOT frontmost
const browserUrl = finderIsFront ? null : await getBrowserUrl();
const clipboardImage = await getClipboardImage();
const clipboardText = await clipboard.readText();
const choices: { name: string; value: Action; description: string }[] = [];
// Priority: Selected image file in Finder (when Finder is frontmost)
if (selectedImageFiles.length > 0) {
const fileNames = selectedImageFiles.map((f) => basename(f)).join(", ");
choices.push({
name: `πŸ“ Save image${
selectedImageFiles.length > 1 ? "s" : ""
} from Finder`,
value: "image-file",
description:
fileNames.substring(0, 60) + (fileNames.length > 60 ? "..." : ""),
});
}
// Image in clipboard
if (clipboardImage) {
choices.push({
name: "πŸ“Έ Save image from clipboard",
value: "image-clipboard",
description: `${Math.round(
clipboardImage.data.byteLength / 1024
)}KB image`,
});
}
// If browser is open (and Finder is not frontmost), offer to bookmark current page
if (browserUrl) {
choices.push({
name: "πŸ”– Bookmark current page",
value: "bookmark-page",
description:
browserUrl.substring(0, 60) + (browserUrl.length > 60 ? "..." : ""),
});
}
// Check if clipboard has text (and it's not the same as the URL)
if (clipboardText && clipboardText.trim() && clipboardText !== browserUrl) {
const isUrl = /^https?:\/\//.test(clipboardText.trim());
if (isUrl) {
choices.push({
name: "πŸ”– Bookmark URL from clipboard",
value: "bookmark-clipboard",
description:
clipboardText.substring(0, 60) +
(clipboardText.length > 60 ? "..." : ""),
});
} else {
choices.push({
name: "πŸ“ Save text as note",
value: "note",
description:
clipboardText.substring(0, 60) +
(clipboardText.length > 60 ? "..." : ""),
});
}
}
if (choices.length === 0) {
await notify({
title: "Nothing to save",
message: "No browser URL, image file, or clipboard content",
});
return;
}
// If only one choice, just do it. Otherwise, ask.
let action: Action;
if (choices.length === 1) {
action = choices[0].value;
} else {
action = await arg("What do you want to save?", choices);
}
try {
if (action === "image-file" && selectedImageFiles.length > 0) {
await hide();
// Save each selected image
for (const filePath of selectedImageFiles) {
const mediaType = getMediaType(filePath);
if (!mediaType) continue;
await notify({ title: "Saving image...", message: basename(filePath) });
const imageData = await readFile(filePath);
await saveImage(imageData, mediaType);
}
} else if (action === "image-clipboard" && clipboardImage) {
await hide();
await notify({ title: "Saving image...", message: "Analyzing with AI" });
await saveImage(clipboardImage.data, clipboardImage.mediaType);
} else if (action === "bookmark-page" || action === "bookmark-clipboard") {
const url =
action === "bookmark-clipboard" ? clipboardText?.trim() : browserUrl;
if (!url) throw new Error("No URL available");
// Optional note - just press Enter to skip
const notes = await arg({
placeholder: "Add a note (optional), Enter to save",
hint: url,
});
// Hide the prompt and let it work in background
await hide();
await notify({ title: "Saving bookmark...", message: url });
await saveBookmark(url, notes?.trim() || undefined);
} else if (action === "note") {
const content = clipboardText?.trim();
if (!content) throw new Error("No text in clipboard");
// Preview and optionally edit
const finalContent = await editor({
value: content,
hint: "Edit your note if needed, then press Cmd+S to save",
});
if (!finalContent?.trim()) {
await notify({ title: "Cancelled", message: "Note was empty" });
return;
}
await hide();
await notify({ title: "Saving note...", message: "Processing" });
await saveNote(finalContent);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
await notify({
title: "Error",
message,
});
}
}
await main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment