// Name: Merge / Split Alfred clipboard// Description: Merge or split clipboard content using Alfred app's clipboardimport "@johnlindquist/kit"const Database = await npm("better-sqlite3");const databasePath = home('Library/Application Support/Alfred/Databases/clipboard.alfdb')if (!await pathExists(databasePath)) {notify("Alfred clipboard database not found" )exit()}const db = new Database(databasePath);const queryClipboard = async (sql, params) => {const stmt = db.prepare(sql);return sql.trim().toUpperCase().startsWith("SELECT") ? stmt.all(params) : stmt.run(params);};const getMergedClipboards = async (count, separator) => {const sql = `SELECT item FROM clipboard WHERE dataType = 0 order by ROWID desc LIMIT ?`;const clipboards = await queryClipboard(sql, [count]);return clipboards.map(row => row.item.trim()).join(separator);};const writeMergedClipboards = async (mergedText) => {await clipboard.writeText(mergedText);};const getSplitClipboard = async (separator, trim) => {const currentClipboard = await clipboard.readText();return currentClipboard.split(separator).map(item => trim ? item.trim() : item);};const writeSplitClipboard = async (splitText) => {const lastTsSql = `SELECT ts FROM clipboard WHERE dataType = 0 ORDER BY ts DESC LIMIT 1`;const lastTsResult = await queryClipboard(lastTsSql, []);let lastTs = lastTsResult.length > 0 ? Number(lastTsResult[0].ts) : 0;const insertSql = `INSERT INTO clipboard (item, ts, dataType, app, appPath) VALUES (?, ?, 0, 'Kit', '/Applications/Kit.app')`;for (let i = 0; i < splitText.length - 1; i++) {lastTs += 1;await queryClipboard(insertSql, [splitText[i], lastTs]);}await clipboard.writeText(splitText[splitText.length - 1]);};const action = await arg("Choose action", ["Merge", "Split"]);if (action === "Merge") {const count = await arg({placeholder: "Enter the number of clipboard items to merge",}, async (input) => {if (isNaN(Number(input)) || input.length === 0)return ''return md(`<pre>${await getMergedClipboards(input, '\n')}</pre>`)})const separator = await arg({placeholder: "Enter the separator for merging",}, async (input) => {if (input === '\\n') input = '\n'return md(`<pre>${await getMergedClipboards(count, input)}</pre>`)})const mergedText = await getMergedClipboards(count, separator);await writeMergedClipboards(mergedText);await notify("Merged clipboard items and copied to clipboard");} else {// const separator = await arg("Enter the separator for splitting");const separator = await arg({placeholder: "Enter the separator for splitting",}, async (input) => {if (input === '\\n') input = '\n'let strings = await getSplitClipboard(input, true);return md(`<pre>${strings.join('\n')}</pre>`)})const trim = await arg("Trim clipboard content?", ["Yes", "No"]);const splitText = await getSplitClipboard(separator, trim === "Yes");await writeSplitClipboard(splitText);await notify("Split clipboard content and stored in Alfred clipboard");}db.close();
// Name: Type Clipboard// Description: Get the content of the clipboard and "keystroke" it without pasting// Shortcut: ctrl+cmd+alt+shift+vimport "@johnlindquist/kit"const clipboardText = await clipboard.readText()if (clipboardText.length > 1000) {await notify("Clipboard content is too long")exit()}await applescript(String.raw`set chars to count (get the clipboard)tell application "System Events"delay 0.1keystroke (get the clipboard)end tell`)
// Name: Open in WhatsAppimport "@johnlindquist/kit"//get the text from the clipboardlet text = await clipboard.readText();//normalize the texttext = text.replace(/[-() ]/g, "");//validate if valid phone numberif (!text.match(/^(\+\d{12,13})|(\d{10,11})$/)) {notify("Invalid phone number");exit()}//assume Argentina if no country code since that's where I'm fromif (!text.startsWith("+")) {text = "+54" + text;}//open in WhatsAppopen(`https://wa.me/${text}`);
// Name: convert selected imagesimport "@johnlindquist/kit";// Grab selected filesconst files = (await getSelectedFile()).split("\n");// Set up whitelist of formatsconst supportedFormats = [".heic", ".png", ".gif", ".webp", ".jpg", ".jpeg"];// Filter files based on supported formatsconst selectedFiles = files.filter(file =>supportedFormats.some(format => file.toLowerCase().endsWith(format)));// Notify if no files are selectedif (!selectedFiles.length) {await notify("No supported files selected");exit();}const convertHeic = await npm("heic-convert");const sharp = await npm("sharp");// Select the output formatconst outputFormat = await arg("Choose an output format", ["jpg","png","webp",]);const getUniquePath = async (outputPath, suffix = "") => {if (await isFile(outputPath)) {const name = path.basename(outputPath, path.extname(outputPath));const newName = `${name}${suffix}-copy${path.extname(outputPath)}`;const newPath = path.join(path.dirname(outputPath), newName);return await getUniquePath(newPath, `${suffix}-copy`);} else {return outputPath;}};// Convert selected files to the chosen output format using appropriate librariesfor (const file of selectedFiles) {const content = await readFile(file);const name = path.basename(file).split(".")[0];const outputPath = path.join(path.dirname(file), name + `.${outputFormat}`);const uniqueOutputPath = await getUniquePath(outputPath);if (file.toLowerCase().endsWith(".heic")) {const formatMap = {jpg: "JPEG",png: "PNG",}const outputBuffer = await convertHeic({buffer: content,format: formatMap[outputFormat],quality: 0.5,});await writeFile(uniqueOutputPath, outputBuffer);} else {const sharpImage = sharp(content);switch (outputFormat) {case "jpg":await sharpImage.jpeg({ quality: 40 }).toFile(uniqueOutputPath);break;case "png":await sharpImage.png().toFile(uniqueOutputPath);break;case "webp":await sharpImage.webp({ quality: 40 }).toFile(uniqueOutputPath);break;}}}await notify(`Converted selected files to ${outputFormat.toUpperCase()}`);
// Name: Open URL in clipboardimport "@johnlindquist/kit"//get the clipboardlet text = await clipboard.readText();//get the first URL in the clipboard, if anylet url = text.match(/(https?:\/\/[^\s]+)/);//if there's a URL, open itif (url) {open(url[0]);} else {notify("No URL found in clipboard");}
// Name: Emoji Search// Description: Search and copy emoji to clipboard using SQLite databaseimport "@johnlindquist/kit"const Database = await npm("better-sqlite3")const databaseFile = projectPath("db", "emoji-search-emojilib.db")const emojilibURL = "https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json"const createDatabase = async () => {const response = await get(emojilibURL)const emojiData = response.data as Record<string, string[]>//create db and tableconst db = new Database(databaseFile)db.exec(`CREATE TABLE IF NOT EXISTS emojis(emoji TEXT PRIMARY KEY, name TEXT, keywords TEXT, used INTEGER DEFAULT 0)`)//populate with data from emojilibfor (const [emojiChar, emojiInfo] of Object.entries(emojiData)) {const description = emojiInfo[0]const tags = emojiInfo.slice(1).join(', ')db.prepare("INSERT OR REPLACE INTO emojis VALUES (?, ?, ?, 0)").run(emojiChar, description, tags)}db.close()};if (!await pathExists(databaseFile)) {await createDatabase()}const db = new Database(databaseFile)const queryEmojis = async () => {const sql = "SELECT emoji, name, keywords FROM emojis ORDER BY used DESC"const stmt = db.prepare(sql)return stmt.all()}const snakeToHuman = (text) => {return text.split('_').map((word, index) => index === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word).join(' ')}const emojis = await queryEmojis()const selectedEmoji = await arg("Search Emoji", emojis.map(({ emoji, name, keywords }) => ({name: `${snakeToHuman(name)} ${keywords}`,html: md(`<div class="flex items-center"><span class="text-5xl">${emoji}</span><div class="flex flex-col ml-2"><span class="text-2xl" style="color: lightgrey">${snakeToHuman(name)}</span><small style="color: darkgrey">${keywords}</small></div></div>`),value: emoji,})))await clipboard.writeText(selectedEmoji)// Update the 'used' countconst updateSql = "UPDATE emojis SET used = used + 1 WHERE emoji = ?"const updateStmt = db.prepare(updateSql)updateStmt.run(selectedEmoji)db.close()