diff --git a/README.md b/README.md index a1a82d593..fcccd6f14 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@
- GitHub contributors + GitHub contributors - Twitter Follow + Twitter Follow
diff --git a/commands/README.md b/commands/README.md index 6583775b2..c50ff8db0 100644 --- a/commands/README.md +++ b/commands/README.md @@ -25,6 +25,8 @@ This repository contains sample commands and documentation to write your own one ### Categories +- [Ai](#ai) + - [Gemini](#gemini) - [Apps](#apps) - [Agenda](#agenda) - [Amphetamine](#amphetamine) @@ -47,6 +49,7 @@ This repository contains sample commands and documentation to write your own one - [ExpressVPN](#expressvpn) - [Fantastical](#fantastical) - [Ferdi](#ferdi) + - [Find My](#find-my) - [Focus](#focus) - [GoodLinks](#goodlinks) - [HazeOver](#hazeover) @@ -159,11 +162,20 @@ This repository contains sample commands and documentation to write your own one - [Samsung TV](#samsung-tv) - [System](#system) - [Audio](#audio) + - [Magic Keyboard Switcher](#magic-keyboard-switcher) - [VPN](#vpn) - [Vpnutil](#vpnutil) - [Web Searches](#web-searches) - [WordPress](#wordpress) +## Ai + +#### Gemini + +| Icon | Title | Description | Author | Args | Templ | Lang | +| :--: | ----- | ----------- | :----: | :--: | :---: | :--: | + | | [Ask Gemini](ai/gemini/gemini.js) | Open Gemini in Chrome browser and submit a prompt with optional selected text as context | [Est7](https://github.com/est7) and [Nimo Beeren](https://github.com/nimobeeren) | βœ… | | | + ## Apps #### Agenda @@ -364,6 +376,12 @@ This repository contains sample commands and documentation to write your own one | | [Open Service by Index](apps/ferdi/ferdi-open-service-by-index.applescript) | N/A | [Jakub Lanski](https://github.com/jaklan) | βœ… | | | | | [Open Service by Name](apps/ferdi/ferdi-open-service-by-name.applescript) | N/A | [Jakub Lanski](https://github.com/jaklan) | βœ… | | | +#### Find My + +| Icon | Title | Description | Author | Args | Templ | Lang | +| :--: | ----- | ----------- | :----: | :--: | :---: | :--: | + | | [Find My Phone (Auto Sound)](apps/find-my/fmp.js) | N/A | Raycast | | | | + #### Focus | Icon | Title | Description | Author | Args | Templ | Lang | @@ -1000,6 +1018,7 @@ This repository contains sample commands and documentation to write your own one | πŸ” | [Strong Password Generator](developer-utils/strong-password-generator.sh) | Generate a strong password of requested character length | [Nitin Gupta](https://twitter.com/gniting) | βœ… | | | | πŸ•’ | [Time Between Dates](developer-utils/time-between-dates.js) | Given two dates returns the time between them in multiple units of measure. | [Federico Miraglia](https://github.com/Mitra98t) | βœ… | | | | πŸ•’ | [Time Calculator](developer-utils/time-calculator.js) | Add or Subtract specified amount of time from given date. | [Federico Miraglia](https://github.com/Mitra98t) | βœ… | | | + | πŸ”’ | [Toggle SSH SOCKS Tunnel](developer-utils/toggle_ssh_proxy_tunnel.template.sh) | Toggles an SSH SOCKS proxy tunnel on and off. | [Andrii Barabash](https://github.com/AndriiBarabash) | | βœ… | | | πŸ”  | [Transform Case](developer-utils/transform-case.sh) | Transform the case of clipboard content. Defaults to lower case if no conversion type is specified. | [Nitin Gupta](https://twitter.com/gniting) | βœ… | | | | βœ‚ | [Trim Git Commit Hash](developer-utils/trim-git-commit-hash.sh) | Trim full git commit hash down to seven characters. | [Caleb Stauffer](https://github.com/crstauf) | βœ… | | | | πŸ”— | [Unshorten URL](developer-utils/unshorten-url.sh) | Unshortens clipboard content url and copies the result again. | [Nikita Galaiko](https://github.com/ngalaiko) | | | | @@ -1481,6 +1500,7 @@ This repository contains sample commands and documentation to write your own one | | [Copy Finder Selection to Clipboard](system/copy-selection-to-clipboard.applescript) | Copy contents of selected items in Finder to the clipboard. If there's more than one file selected, they will be combined and added to the clipboard. | [Felipe Turcheti](https://felipeturcheti.com) | | | | | πŸ’ | [Copy Last Download](system/copy-last-download.swift) | Copy the last downloaded file to the clipboard. | Raycast | | | | | πŸ“Έ | [Copy Last Screenshot](system/copy-last-screenshot.swift) | Copies the last screenshot to the clipboard. | Raycast | | | | + | πŸ“ | [Copy Meeting Summary](system/meeting_summary_script.swift) | Copies a summary of today's meetings to the clipboard. | Raycast | | | | | πŸ“Ÿ | [Copy Wi-Fi Password](system/wifi-password.sh) | Copy Wi-Fi password from current session | [Astrit Malsia](https://github.com/astrit) | | | | | πŸ“§ | [Create Email](system/new-email.sh) | Opens default email application, and creates a new email with the given inputs. | [Brandon Escamilla](https://github.com/brandonescamilla) | βœ… | | | | πŸ“„ | [Create New File](system/create-new-file.applescript) | Create files in the front window or desktop of the visit | [LokHsu](https://github.com/lokhsu) | | | | @@ -1494,7 +1514,7 @@ This repository contains sample commands and documentation to write your own one | β˜•οΈ | [Disable Caffeinate](system/caffeinate-disable.sh) | Stops all caffeinate sessions | [Yan Smaliak](https://github.com/ysmaliak) | | | | | πŸ’Ύ | [Disk Free](system/disk-free.sh) | Show free space in your mounted disks | [Juan Luis Romero](https://github.com/JuanluR8) | | | | | πŸ’Ώ | [Disk Usage](system/disk-usage.sh) | Show disk usage for / (root) | [Jesse Claven](https://github.com/jesse-c) | | | | - | πŸ”• | [Dismiss Notifications](system/dismiss-notifications.applescript) | Close all notification alerts staying on screen, e.g., Calendar notifications. | [dlvhdr](github.com/dlvhdr) | | | | + | πŸ”• | [Dismiss Notifications](system/dismiss-notifications.applescript) | Close all notification alerts staying on screen, e.g., Calendar notifications. | [benyn](github.com/benyn) | | | | | πŸ”• | [Do Not Disturb](system/do-not-disturb.sh) | Do Not Disturb | [Antonio Dal Sie](https://github.com/exodusanto) | βœ… | | | | πŸ€– | [Dock Position](system/dock-set-position.sh) | Set the position of the Dock in the screen | [Jelte Lagendijk](https://raycast.com/j3lte) | βœ… | | | | πŸ€– | [Dock Set Autohide](system/dock-set-autohide.sh) | Set the Dock autohide | [Jelte Lagendijk](https://raycast.com/j3lte) | βœ… | | | @@ -1548,6 +1568,7 @@ This repository contains sample commands and documentation to write your own one | πŸ‘“ | [Toggle Hidden Files](system/toggle-hidden-files.applescript) | Show and hide hidden files/folders which starts with "." (dot), i.e: .bash_rc, .ssh | [Thiago Holanda](https://twitter.com/tholanda) | | | | | 🐚 | [Toggle Lid Sleep](system/toggle-lid-sleep.sh) | Prevent sleep from closing laptop lid (clamshell mode) | [Ivan Rybalko](https://github.com/ivribalko) | | | | | πŸ–± | [Toggle Natural Scrolling](system/toggle-natural-scrolling.applescript) | Script Command to change natural trackpad/mouse scrolling setting. Reverting the setting value each time. | [Wiley Marques](https://twitter.com/wileymarques) | | | | + | πŸ–± | [Toggle Natural Scrolling (macOS 15+)](system/toggle-natural-scrolling-macos15.applescript) | Toggle natural trackpad/mouse scrolling setting for macOS 15.6.1+ | [Raphael-KR](https://github.com/Raphael-KR) | | | | | 🌘 | [Toggle Night Shift](system/nightshift.sh) | Toggle Night Shift mode (until tomorrow/sunrise). Required [nightlight](https://github.com/smudge/nightlight) | [BhEaN](https://github.com/bhean) | | | | | πŸŒ— | [Toggle System Appearance](system/toggle-system-appearance.applescript) | Script Command to switch between the system appearance, light and dark mode. | [Thiago Holanda](https://twitter.com/tholanda) | | | | | | [Toggle Wi-Fi](system/wifi.template.applescript) | Toggle your Wi-Fi connection. | [Vincent DΓΆrig](https://github.com/vincentdoerig) | | βœ… | | @@ -1571,6 +1592,12 @@ This repository contains sample commands and documentation to write your own one | πŸŽ™ | [Toggle Microphone](system/audio/toggle-mic.applescript) | Toggles microphone. | [Matthew Morek](https://github.com/matthewmorek) | | | | | πŸ”” | [Toggle Mute Notifcation Sounds](system/audio/toggle-mute-notification-sounds.applescript) | Toggles notification sounds. | [Annie Ma](http://www.anniema.co/) | | | | +#### Magic Keyboard Switcher + +| Icon | Title | Description | Author | Args | Templ | Lang | +| :--: | ----- | ----------- | :----: | :--: | :---: | :--: | + | | [Magic Keyboard switcher](system/magic-keyboard-switcher/magic-keyboard-switcher.template.sh) | Switch a single magic keyboard between computers | [blastik](https://github.com/blastik) | | βœ… | | + #### VPN | Icon | Title | Description | Author | Args | Templ | Lang | diff --git a/commands/ai/gemini/gemini.js b/commands/ai/gemini/gemini.js new file mode 100755 index 000000000..20eaa42d8 --- /dev/null +++ b/commands/ai/gemini/gemini.js @@ -0,0 +1,231 @@ +#!/usr/bin/env node + +// Dependencies: +// This script requires the following software to be installed: +// - `node` https://nodejs.org +// - `chrome-cli` https://github.com/prasmussen/chrome-cli +// Install via homebrew: `brew install node chrome-cli` + +// This script needs to run JavaScript in your browser, which requires your permission. +// To do so, open Chrome and find the menu bar item: +// View > Developer > Allow JavaScript from Apple Events + +// Required parameters: +// @raycast.schemaVersion 1 +// @raycast.title Ask Gemini +// @raycast.mode silent +// @raycast.packageName Gemini + +// Optional parameters: +// @raycast.icon ./images/icon-gemini.svg +// @raycast.argument1 { "type": "text", "placeholder": "Selected Text", "optional": true } +// @raycast.argument2 { "type": "text", "placeholder": "Prompt"} + +// Documentation: +// @raycast.description Open Gemini in Chrome browser and submit a prompt with optional selected text as context +// @raycast.author Est7 +// @raycast.authorURL https://github.com/est7 + +// @raycast.author Nimo Beeren +// @raycast.authorURL https://github.com/nimobeeren + +const { execSync } = require("child_process"); + +const selectedText = process.argv[2] || ""; // Get the text from "Selected Text" argument, or use an empty string if argument is empty. +const prompt = process.argv[3]; + +process.env.OUTPUT_FORMAT = "json"; + +/** Escape a string so that it can be used in JavaScript code when wrapped in double quotes. */ +function escapeJsString(str) { + return str.replaceAll(`\\`, `\\\\`).replaceAll(`"`, `\\"`); +} + +/** Escape a string so that it can be used in a shell command when wrapped in single quotes. */ +function escapeShellString(str) { + return str.replaceAll(`'`, `'"'"'`); +} + +// used to wait for Chrome to activate. +function sleep(ms) { + const start = Date.now(); + while (Date.now() - start < ms) {} +} + +/** + * Verifies that all required dependencies are installed. + * Exits the process with an error message if any dependency is missing. + */ +function checkDependencies() { + // Check if Google Chrome is installed + try { + execSync("osascript -e 'tell application \"Google Chrome\" to get version'" , { stdio: "ignore" }); + } catch { + try { + execSync("osascript -e 'tell application \"Chrome\" to get version'" , { stdio: "ignore" }); + } catch { + console.error("Google Chrome is required to run this script"); + process.exit(1); + } + } + + // Check if chrome-cli is installed + try { + execSync("which chrome-cli"); + } catch { + console.error( + "chrome-cli is required to run this script (https://github.com/prasmussen/chrome-cli)" + ); + process.exit(1); + } +} + +// Verify all dependencies are installed +checkDependencies(); + +// Bring Chrome to the foreground first. +try { + // Try to activate Chrome through AppleScript, supporting different possible application names. + execSync("osascript -e 'tell application \"Google Chrome\" to activate'", { + stdio: "ignore", + }); +} catch (e) { + try { + // If the first naming method fails, try possible alternatives. + execSync("osascript -e 'tell application \"Chrome\" to activate'", { + stdio: "ignore", + }); + } catch (err) { + console.error( + "Unable to activate Chrome browser, continue with other operations", + ); + } +} + +// Give Chrome a little time to make sure it is activated +sleep(300); + +// Find the Gemini tab if one is already open +let tabs = JSON.parse(execSync("chrome-cli list tabs")).tabs; +let geminiTab = tabs.find((tab) => + tab.url.startsWith("https://gemini.google.com/"), +); + +// If there is a Gemini tab open, get its info. Otherwise, open Gemini in a new window. +let geminiTabInfo; +if (geminiTab) { + // Focus on existing tags, do not refresh the page + execSync(`chrome-cli activate -t ${geminiTab.id}`); + // Get tab info + geminiTabInfo = JSON.parse(execSync(`chrome-cli info -t ${geminiTab.id}`)); +} else { + // Open a Gemini session in a new tab, focus it and return the tab info + geminiTabInfo = JSON.parse( + execSync("chrome-cli open 'https://gemini.google.com/app'"), + ); +} + +// Wait for the tab to be loaded, then execute the script +let interval = setInterval(() => { + if (geminiTabInfo.loading) { + geminiTabInfo = JSON.parse( + execSync(`chrome-cli info -t ${geminiTabInfo.id}`), + ); + } else { + clearInterval(interval); + executeScript(); + } +}, 100); + +function executeScript() { + const script = async function (selectedText, prompt) { + // Wait for prompt element to be on the page + let promptElement; + await new Promise((resolve) => { + let interval = setInterval(() => { + promptElement = document.querySelector( + 'div[aria-label="Enter a prompt here"]', + ); + if (promptElement) { + clearInterval(interval); + resolve(); + } + }, 100); + }); + + // Prepare the final text + let finalText = ""; + if (selectedText && selectedText.trim() !== "") { + finalText += `${selectedText}\n\n${prompt}`; + } else { + finalText = prompt; + } + + // Focus the input element first + promptElement.focus(); + + // Check if there's existing content + const hasExistingContent = promptElement.textContent.trim() !== ""; + + // Clear existing content if needed - safely without innerHTML + if (!hasExistingContent) { + // If empty, we'll just add our content + // No need to clear anything + } else { + // If we want to append to existing content, add a newline + // Create a new paragraph for separation + const selection = window.getSelection(); + const range = document.createRange(); + + // Move cursor to the end of existing content + range.selectNodeContents(promptElement); + range.collapse(false); // false means collapse to end + selection.removeAllRanges(); + selection.addRange(range); + + // Insert two newlines to separate content + document.execCommand("insertText", false, "\n\n"); + } + + // Insert the content using execCommand which is safer than innerHTML + // Split by newlines and insert with proper paragraph formatting + const paragraphs = finalText.split("\n"); + paragraphs.forEach((paragraph, index) => { + if (index > 0) { + // Insert newline between paragraphs (not before the first one) + document.execCommand("insertText", false, "\n"); + } + + // Insert the paragraph text + document.execCommand("insertText", false, paragraph || "\u200B"); + }); + + // Trigger input event to notify Gemini of changes + const inputEvent = new Event("input", { bubbles: true }); + promptElement.dispatchEvent(inputEvent); + + // Ensure cursor is at the end and visible + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(promptElement); + range.collapse(false); // false means collapse to end + selection.removeAllRanges(); + selection.addRange(range); + + // Scroll to make cursor visible + promptElement.scrollTop = promptElement.scrollHeight; + + // Additional scroll after a short delay to ensure visibility + setTimeout(() => { + promptElement.scrollTop = promptElement.scrollHeight; + }, 100); + }; + + const functionString = escapeShellString(script.toString()); + const selectedTextString = escapeShellString(escapeJsString(selectedText)); + const promptString = escapeShellString(escapeJsString(prompt)); + + execSync( + `chrome-cli execute '(${functionString})(\"${selectedTextString}\", \"${promptString}\")' -t ${geminiTabInfo.id}`, + ); +} diff --git a/commands/ai/gemini/images/icon-gemini.svg b/commands/ai/gemini/images/icon-gemini.svg new file mode 100644 index 000000000..4545c8331 --- /dev/null +++ b/commands/ai/gemini/images/icon-gemini.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/commands/apps/find-my/.env.example b/commands/apps/find-my/.env.example new file mode 100644 index 000000000..6e3a6c985 --- /dev/null +++ b/commands/apps/find-my/.env.example @@ -0,0 +1,3 @@ +ICLOUD_EMAIL= +ICLOUD_PASSWORD= +DEVICE= \ No newline at end of file diff --git a/commands/apps/find-my/README.md b/commands/apps/find-my/README.md new file mode 100644 index 000000000..55057353e --- /dev/null +++ b/commands/apps/find-my/README.md @@ -0,0 +1,58 @@ +# Find My Phone Raycast + +I lose my phone often, so I made this. + +This is a Node.js script that uses Playwright to automate iCloud Find My and trigger a sound on your Apple device. It's perfect for people like me who misplace their phones and want a quick, automated way to make them ringβ€”with Raycast. + +## Usage + +```sh +git clone https://github.com/vsvaidya27/fmp-raycast +cd fmp-raycast +npm install +cp .env.example .env +# Edit .env with your real credentials +chmod +x fmp.js +``` + +### Add to Raycast + +1. Open Raycast and go to **Extensions**. +2. Click **Add**. +3. Select **Add Script Directory**. +4. Choose the `fmp-raycast` directory you just cloned. +5. Set fmp as your alias for calling the script. + +Now you can trigger the script directly from Raycast! + +## How it works + +- Automates login to iCloud Find My using Playwright +- Selects your device by name +- Triggers the "Play Sound" feature to help you locate your device + +**Note:** You'll need to provide your iCloud credentials and device name in the `.env` file. Sometimes a 2FA check may pop up, but usually they allow you to bypass this and manual intervention is not neccesary (since you often need the very thing you are trying to find for 2FA). + +--- + +## πŸ›  Troubleshooting + +### If you get the error `env: node: No such file or directory` + +Raycast uses a limited shell environment, so it may not find your Node.js install. + +To fix it: + +1. Run this in Terminal to find your full Node path: + ```bash + which node + ``` +2. Edit the top of `fmp.js`: + Replace: + ```sh + #!/usr/bin/env node + ``` + With: + ```sh + #!/full/path/to/node + ``` diff --git a/commands/apps/find-my/fmp.js b/commands/apps/find-my/fmp.js new file mode 100755 index 000000000..e672b032b --- /dev/null +++ b/commands/apps/find-my/fmp.js @@ -0,0 +1,62 @@ +#!/usr/bin/env node + +// Required parameters: +// @raycast.schemaVersion 1 +// @raycast.title Find My Phone (Auto Sound) +// @raycast.mode silent +// @raycast.packageName FindMy +// @raycast.icon images/find-my-icon.png + +require('dotenv').config(); +const { chromium } = require('playwright'); + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + // 1. Go to iCloud Find Devices + await page.goto('https://www.icloud.com/find'); + + // 2. Click "Sign In" + await page.waitForSelector('text=Sign In', { timeout: 20000 }); + await page.click('text=Sign In'); + + // 3. Wait for the Apple login iframe to appear + const frameLocator = page.frameLocator('iframe[name="aid-auth-widget"]'); + + // 4. Wait for the email/phone input inside the iframe + await frameLocator.getByRole('textbox', { name: 'Email or Phone Number' }).waitFor({ timeout: 20000 }); + + // 5. Fill in the email/phone + await frameLocator.getByRole('textbox', { name: 'Email or Phone Number' }).fill(process.env.ICLOUD_EMAIL); + + // 6. Click the right-arrow button + await frameLocator.getByRole('button').first().click(); + + // 7. Wait for the "Continue with Password" button to appear, then click it + await frameLocator.getByRole('button', { name: /Continue with Password/i }).waitFor({ timeout: 20000 }); + await frameLocator.getByRole('button', { name: /Continue with Password/i }).click(); + + // 8. Wait for password input + await frameLocator.getByRole('textbox', { name: 'Password' }).waitFor({ timeout: 20000 }); + await frameLocator.getByRole('textbox', { name: 'Password' }).fill(process.env.ICLOUD_PASSWORD); + + // 9. Click the arrow button to continue + await frameLocator.getByRole('button').first().click(); + + // 10. Wait for 2FA if needed; Hopefully not because this kinda defeats the purpose of automation even though its prob for the best security-wise :( + console.log('If prompted, please complete 2FA in the browser window. :('); + + // 12. Click on your iPhone using the precise selector inside the second iframe + await page.locator('iframe').nth(1).contentFrame().getByTitle(process.env.DEVICE).getByTestId('show-device-name').click(); + + // 13. Wait for the device details panel to load + await page.waitForTimeout(2000); + + // 14. Click the "Play Sound" button + await page.locator('iframe').nth(1).contentFrame().getByRole('button', { name: 'Play Sound' }).click(); + + console.log('βœ… Play Sound triggered for ' + process.env.DEVICE + '!'); + await browser.close(); +})(); + diff --git a/commands/apps/find-my/images/find-my-icon.png b/commands/apps/find-my/images/find-my-icon.png new file mode 100644 index 000000000..0862bfec8 Binary files /dev/null and b/commands/apps/find-my/images/find-my-icon.png differ diff --git a/commands/apps/find-my/package.json b/commands/apps/find-my/package.json new file mode 100644 index 000000000..6e278971c --- /dev/null +++ b/commands/apps/find-my/package.json @@ -0,0 +1,16 @@ +{ + "name": "findmyraycast", + "version": "1.0.0", + "main": "fmp.js", + "scripts": { + "start": "node fmp.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "dotenv": "^16.5.0", + "playwright": "^1.52.0" + } +} diff --git a/commands/developer-utils/toggle_ssh_proxy_tunnel.template.sh b/commands/developer-utils/toggle_ssh_proxy_tunnel.template.sh new file mode 100755 index 000000000..80f8e054d --- /dev/null +++ b/commands/developer-utils/toggle_ssh_proxy_tunnel.template.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Raycast Script Command +# +# Required parameters: +# @raycast.schemaVersion 1 +# @raycast.title Toggle SSH SOCKS Tunnel +# @raycast.mode silent +# +# Optional parameters: +# @raycast.icon πŸ”’ +# @raycast.packageName Developer Utilities +# +# Documentation: +# @raycast.description Toggles an SSH SOCKS proxy tunnel on and off. +# @raycast.author Andrii Barabash +# @raycast.authorURL https://github.com/AndriiBarabash + +# --- Configuration --- +# Replace these with your own values +SSH_USER="" +SSH_HOST="" +SSH_PORT="" +INTERFACE="" # e.g., "Wi-Fi" or "Ethernet" +PROXY_PORT="" # e.g., 1080 +# --- End Configuration --- + +if pgrep -f "ssh -D $PROXY_PORT" >/dev/null; then + echo "SSH SOCKS tunnel is running. Turning it off..." + + # Kill the SSH process + pkill -f "ssh -D $PROXY_PORT" + + # Disable the SOCKS proxy + networksetup -setsocksfirewallproxystate "$INTERFACE" off + + echo "Tunnel and proxy disabled." +else + echo "SSH SOCKS tunnel is not running. Turning it on..." + + # Start the SSH tunnel + ssh -D "$PROXY_PORT" -f -C -q -N -p "$SSH_PORT" "$SSH_USER"@"$SSH_HOST" + + # Enable the SOCKS proxy + networksetup -setsocksfirewallproxy "$INTERFACE" 127.0.0.1 "$PROXY_PORT" + networksetup -setsocksfirewallproxystate "$INTERFACE" on + + echo "Tunnel and proxy enabled." +fi diff --git a/commands/extensions.json b/commands/extensions.json index 334f316db..ded9889a8 100644 --- a/commands/extensions.json +++ b/commands/extensions.json @@ -4981,6 +4981,34 @@ "title" : "Uninstall with AppCleaner", "updatedAt" : "2021-03-18T14:47:46-03:00" }, + { + "authors" : [ + { + "name" : "Raphael-KR", + "url" : "https:\/\/github.com\/Raphael-KR" + } + ], + "createdAt" : "2025-08-23T18:49:50+09:00", + "currentDirectoryPath" : null, + "description" : "Toggle natural trackpad\/mouse scrolling setting for macOS 15.6.1+", + "filename" : "toggle-natural-scrolling-macos15.applescript", + "hasArguments" : false, + "icon" : { + "dark" : null, + "light" : "πŸ–±" + }, + "identifier" : "3ccaeabe09d36538f01f592bc7b3e51e", + "isTemplate" : false, + "language" : "applescript", + "mode" : "silent", + "needsConfirmation" : null, + "packageName" : "System", + "path" : "system\/", + "refreshTime" : null, + "schemaVersion" : 1, + "title" : "Toggle Natural Scrolling (macOS 15+)", + "updatedAt" : "2025-08-25T09:08:49Z" + }, { "authors" : [ { @@ -5012,8 +5040,8 @@ { "authors" : [ { - "name" : "dlvhdr", - "url" : "github.com\/dlvhdr" + "name" : "benyn", + "url" : "github.com\/benyn" } ], "createdAt" : "2023-05-06T14:02:11-07:00", @@ -5035,7 +5063,7 @@ "refreshTime" : null, "schemaVersion" : 1, "title" : "Dismiss Notifications", - "updatedAt" : "2024-12-09T09:05:58-08:00" + "updatedAt" : "2025-05-18T15:56:50-07:00" }, { "authors" : [ @@ -6182,7 +6210,7 @@ "url" : "https:\/\/github.com\/AndriiBarabash" } ], - "createdAt" : "2025-03-10T10:57:38+01:00", + "createdAt" : "2025-03-10T00:16:14+01:00", "currentDirectoryPath" : null, "description" : "Put your Mac to sleep (in X minutes).", "filename" : "sleep-timer.applescript", @@ -6191,7 +6219,7 @@ "dark" : null, "light" : "😴" }, - "identifier" : "44762fff108fef0069a546e2cf83c966", + "identifier" : "ff43a32961d94da6001ad37ab79dd032", "isTemplate" : false, "language" : "applescript", "mode" : "silent", @@ -7184,6 +7212,29 @@ "title" : "Network Status", "updatedAt" : "2021-03-11T11:38:48-08:00" }, + { + "authors" : null, + "createdAt" : "2025-09-05T10:08:41-05:00", + "currentDirectoryPath" : null, + "description" : "Copies a summary of today's meetings to the clipboard.", + "filename" : "meeting_summary_script.swift", + "hasArguments" : false, + "icon" : { + "dark" : null, + "light" : "πŸ“" + }, + "identifier" : "3542086084234d14d5e94041c7c2915b", + "isTemplate" : false, + "language" : "swift", + "mode" : "silent", + "needsConfirmation" : null, + "packageName" : "System", + "path" : "system\/", + "refreshTime" : null, + "schemaVersion" : 1, + "title" : "Copy Meeting Summary", + "updatedAt" : "2025-09-05T10:08:41-05:00" + }, { "authors" : null, "createdAt" : "2020-09-30T12:56:01+01:00", @@ -7434,6 +7485,41 @@ } ] }, + { + "name" : "Magic Keyboard Switcher", + "path" : "magic-keyboard-switcher", + "readme" : "system\/magic-keyboard-switcher\/README.md", + "scriptCommands" : [ + { + "authors" : [ + { + "name" : "blastik", + "url" : "https:\/\/github.com\/blastik" + } + ], + "createdAt" : "2025-09-24T17:50:16+02:00", + "currentDirectoryPath" : null, + "description" : "Switch a single magic keyboard between computers", + "filename" : "magic-keyboard-switcher.template.sh", + "hasArguments" : false, + "icon" : { + "dark" : null, + "light" : "images\/logo.png" + }, + "identifier" : "ebd6d04790272dd639e870be745a66db", + "isTemplate" : true, + "language" : "bash", + "mode" : "silent", + "needsConfirmation" : null, + "packageName" : "System", + "path" : "system\/magic-keyboard-switcher\/", + "refreshTime" : null, + "schemaVersion" : 1, + "title" : "Magic Keyboard switcher", + "updatedAt" : "2025-09-24T17:59:07+02:00" + } + ] + }, { "name" : "Vpnutil", "path" : "vpnutil", @@ -7874,6 +7960,53 @@ } ] }, + { + "name" : "Ai", + "path" : "ai", + "scriptCommands" : [ + + ], + "subGroups" : [ + { + "name" : "Gemini", + "path" : "gemini", + "scriptCommands" : [ + { + "authors" : [ + { + "name" : "Est7", + "url" : "https:\/\/github.com\/est7" + }, + { + "name" : "Nimo Beeren", + "url" : "https:\/\/github.com\/nimobeeren" + } + ], + "createdAt" : "2025-04-24T11:38:46+08:00", + "currentDirectoryPath" : null, + "description" : "Open Gemini in Chrome browser and submit a prompt with optional selected text as context", + "filename" : "gemini.js", + "hasArguments" : true, + "icon" : { + "dark" : null, + "light" : ".\/images\/icon-gemini.svg" + }, + "identifier" : "2a9c8b6698663cc8834ace7501bc9bec", + "isTemplate" : false, + "language" : "node", + "mode" : "silent", + "needsConfirmation" : null, + "packageName" : "Gemini", + "path" : "ai\/gemini\/", + "refreshTime" : null, + "schemaVersion" : 1, + "title" : "Ask Gemini", + "updatedAt" : "2025-05-13T08:32:11Z" + } + ] + } + ] + }, { "name" : "Developer Utils", "path" : "developer-utils", @@ -8166,6 +8299,34 @@ "title" : "Is It Up?", "updatedAt" : "2023-02-21T22:56:29-05:00" }, + { + "authors" : [ + { + "name" : "Andrii Barabash", + "url" : "https:\/\/github.com\/AndriiBarabash" + } + ], + "createdAt" : "2025-09-27T20:40:28+02:00", + "currentDirectoryPath" : null, + "description" : "Toggles an SSH SOCKS proxy tunnel on and off.", + "filename" : "toggle_ssh_proxy_tunnel.template.sh", + "hasArguments" : false, + "icon" : { + "dark" : null, + "light" : "πŸ”’" + }, + "identifier" : "aae74278107ffa5f34b3733eb2c8a813", + "isTemplate" : true, + "language" : "bash", + "mode" : "silent", + "needsConfirmation" : null, + "packageName" : "Developer Utilities", + "path" : "developer-utils\/", + "refreshTime" : null, + "schemaVersion" : 1, + "title" : "Toggle SSH SOCKS Tunnel", + "updatedAt" : "2025-09-29T13:44:50Z" + }, { "authors" : [ { @@ -12301,7 +12462,7 @@ "refreshTime" : null, "schemaVersion" : 1, "title" : "Random Emoji", - "updatedAt" : "2025-01-28T15:23:28Z" + "updatedAt" : "2025-03-25T19:27:06+09:00" } ] }, @@ -15766,6 +15927,36 @@ } ] }, + { + "name" : "Find My", + "path" : "find-my", + "readme" : "apps\/find-my\/README.md", + "scriptCommands" : [ + { + "authors" : null, + "createdAt" : "2025-06-12T06:53:33-07:00", + "currentDirectoryPath" : null, + "description" : null, + "filename" : "fmp.js", + "hasArguments" : false, + "icon" : { + "dark" : null, + "light" : "images\/find-my-icon.png" + }, + "identifier" : "206851d53c58c074472f7acb20ceeb08", + "isTemplate" : false, + "language" : "node", + "mode" : "silent", + "needsConfirmation" : null, + "packageName" : "FindMy", + "path" : "apps\/find-my\/", + "refreshTime" : null, + "schemaVersion" : 1, + "title" : "Find My Phone (Auto Sound)", + "updatedAt" : "2025-06-12T06:53:33-07:00" + } + ] + }, { "name" : "Bear", "path" : "bear", @@ -24015,11 +24206,6 @@ "icon" : "icon-bash.png", "name" : "bash" }, - { - "displayName" : "Ruby", - "icon" : "icon-ruby.png", - "name" : "ruby" - }, { "displayName" : "Node", "icon" : "icon-nodejs.png", @@ -24034,11 +24220,16 @@ "displayName" : "AppleScript", "icon" : "icon-applescript.png", "name" : "applescript" + }, + { + "displayName" : "Ruby", + "icon" : "icon-ruby.png", + "name" : "ruby" } ], "metadata" : [ ], - "totalScriptCommands" : 835, - "updatedAt" : "2025-03-10T13:53:09Z" + "totalScriptCommands" : 841, + "updatedAt" : "2025-09-29T13:46:20Z" } \ No newline at end of file diff --git a/commands/system/dismiss-notifications.applescript b/commands/system/dismiss-notifications.applescript index c19a01a0a..2c3c2716b 100755 --- a/commands/system/dismiss-notifications.applescript +++ b/commands/system/dismiss-notifications.applescript @@ -11,19 +11,39 @@ # Documentation: # @raycast.description Close all notification alerts staying on screen, e.g., Calendar notifications. -# @raycast.author dlvhdr -# @raycast.authorURL github.com/dlvhdr - -tell application "System Events" to tell application process "NotificationCenter" - try - repeat with uiElement in (actions of UI elements of scroll area 1 of group 1 of group 1 of window "Notification Center" of application process "NotificationCenter" of application "System Events") - if description of uiElement contains "Close" then - perform uiElement - end if - if description of uiElement contains "Clear" then - perform uiElement - end if +# @raycast.author benyn +# @raycast.authorURL github.com/benyn + +tell application "System Events" to tell process "NotificationCenter" + -- Exit if there are no visible notifications. + if not (window "Notification Center" exists) then return + + -- `notificationContainer` refers to the UI element (of class `group`) holding notifications and can be either: + -- - A single, individual notification, or + -- - A collection of individual notifications and groups of stacked notifications. + set notificationContainer to a reference to group 1 of scroll area 1 of group 1 of group 1 of window "Notification Center" + + -- If it is a collection, close notifications and groups in reverse order to avoid index changes. + set notificationGroups to a reference to groups of notificationContainer + repeat with i from (number of notificationGroups) to 1 by -1 + set g to item i of notificationGroups + repeat with a in (actions of g whose description is "Close" or description starts with "Clear") + -- Ignore errors that happen if the last remaining item is an individual notification, + -- and `notificationCenter` is no longer a `group` of `group`s. + -- The final remaining notification will be closed in the second repeat statement below. + ignoring application responses + perform a + end ignoring end repeat - return "" - end try + end repeat + + -- Close the `notificationContainer` itself. This handles: + -- - A single, individual notification that was `notificationContainer` from the start, or + -- - The last remaining individual notification after the loop above. + repeat with a in (actions of notificationContainer whose description is "Close" or description starts with "Clear") + perform a + end repeat end tell + +-- Prevent Raycast from displaying the successful result message. +return diff --git a/commands/system/magic-keyboard-switcher/README.md b/commands/system/magic-keyboard-switcher/README.md new file mode 100644 index 000000000..a6ce88ad3 --- /dev/null +++ b/commands/system/magic-keyboard-switcher/README.md @@ -0,0 +1,21 @@ +# magic-keyboard-switcher +Script command to easility switch the bluetooth connectivity of a single Magic Keyboard between several computers. + +## How to setup + +1. Install [blueutil](https://github.com/toy/blueutil). You can use [brew](https://brew.sh/) - `brew install blueutil` +2. Find out your Magic Keyboard bluetooth MAC address. With the keyboard connected, keep the Option key pressed while you click on your status bar bluetooth icon. +
+ Bluetooth menu +
+ +3. Open the script file, set your Magic Keyboard bluetooth MAC address in the `BTMAC` variable and review the blueutil binary location set in the `BIN` variable. +4. Remove `.template` from the file name. +5. Say Ok when prompted about giving Bluetooth permissions to Raycast after executing the script command. + +## How to switch the Magic Keyboard between computers + +It's desirable to have Raycast and this script command installed in both computers. + +First, run the script command in the one that has the keyboard currently connected. It will disconnect it and make it discoverable. +Second, run the script command in the other computer. It should connect it. diff --git a/commands/system/magic-keyboard-switcher/images/bluetooth menu.png b/commands/system/magic-keyboard-switcher/images/bluetooth menu.png new file mode 100644 index 000000000..de5d4070a Binary files /dev/null and b/commands/system/magic-keyboard-switcher/images/bluetooth menu.png differ diff --git a/commands/system/magic-keyboard-switcher/images/logo.png b/commands/system/magic-keyboard-switcher/images/logo.png new file mode 100644 index 000000000..200a262fe Binary files /dev/null and b/commands/system/magic-keyboard-switcher/images/logo.png differ diff --git a/commands/system/magic-keyboard-switcher/magic-keyboard-switcher.template.sh b/commands/system/magic-keyboard-switcher/magic-keyboard-switcher.template.sh new file mode 100755 index 000000000..e27d2b939 --- /dev/null +++ b/commands/system/magic-keyboard-switcher/magic-keyboard-switcher.template.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Dependency: This script requires `blueutil` cli installed: https://github.com/toy/blueutil +# Install via homebrew: `brew install blueutil` + +# Required parameters: +# @raycast.schemaVersion 1 +# @raycast.title Magic Keyboard switcher +# @raycast.mode silent + +# Optional parameters: +# @raycast.icon images/logo.png +# @raycast.packageName System + +# Documentation: +# @raycast.author blastik +# @raycast.authorURL https://github.com/blastik +# @raycast.description Switch a single magic keyboard between computers + +# blueutil location +BIN=/opt/homebrew/bin/blueutil + +# Your Magic Keyboard MAC Address +BTMAC='XX:XX:XX:XX:XX:XX' + +CMD_VAL="$($BIN --is-connected $BTMAC)" +CMD_UNPAIR="$BIN --unpair $BTMAC" +CMD_PAIR="$BIN --pair $BTMAC" +CMD_CONN="$BIN --connect $BTMAC" + +if ! command -v blueutil &> /dev/null; then + echo "blueutil command is required (https://github.com/toy/blueutil)."; + exit 1; +fi + +if [[ "$CMD_VAL" -eq 1 ]]; then + echo "Connected to $BTMAC" + echo "Going to disconnect $BTMAC" + $($CMD_UNPAIR) + if [[ $? -eq 0 ]]; then + echo "Disconnected from $BTMAC" + else + echo "Failed to disconnect from $BTMAC" + exit 1 + fi +else + echo "Not connected to $BTMAC" + $($CMD_PAIR) + sleep 1 + $($CMD_CONN) + if [[ $? -eq 0 ]]; then + echo "Connected to $BTMAC" + else + echo "Failed to connect to $BTMAC" + exit 1 + fi +fi diff --git a/commands/system/meeting_summary_script.swift b/commands/system/meeting_summary_script.swift new file mode 100755 index 000000000..57053400c --- /dev/null +++ b/commands/system/meeting_summary_script.swift @@ -0,0 +1,92 @@ +#!/usr/bin/swift + +// Required parameters: +// @raycast.schemaVersion 1 +// @raycast.title Copy Meeting Summary +// @raycast.mode silent +// @raycast.packageName System +// +// Optional parameters: +// @raycast.icon πŸ“ +// +// Documentation: +// @raycast.description Copies a summary of today's meetings to the clipboard. + +import AppKit +import EventKit + +let now = Date() +let calendar: Calendar = .current + +do { + let today: (startDate: Date, endDate: Date, events: [EKEvent]) + + do { + // Retrieve the range for today (from 00:00 to 23:59) + today.startDate = calendar.startOfDay(for: now) + today.endDate = calendar.date(byAdding: .day, value: 1, to: today.startDate)! + + // Retrieve all the events for today + let store = EKEventStore() + let predicate = store.predicateForEvents(withStart: today.startDate, end: today.endDate, calendars: nil) + today.events = store.events(matching: predicate) + .filter { !$0.isAllDay } // Exclude all-day events + .sorted { $0.startDate < $1.startDate } // Sort by start time + } + + let summary: String + if today.events.isEmpty { + summary = "No meetings scheduled for today." + } else { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .full + dateFormatter.timeStyle = .none + let todayString = dateFormatter.string(from: now) + + let timeFormatter = DateFormatter() + timeFormatter.dateStyle = .none + timeFormatter.timeStyle = .short + timeFormatter.timeZone = TimeZone.current + + let meetingList = today.events.map { event in + let startTime = timeFormatter.string(from: event.startDate) + let title = event.title ?? "Untitled Event" + let timezone = timeFormatter.timeZone.abbreviation() ?? "" + + var meetingInfo = timezone.isEmpty ? "β€’ \(startTime): \(title)" : "β€’ \(startTime) \(timezone): \(title)" + + // Add location if available + if let location = event.location, !location.isEmpty { + meetingInfo += " (\(location))" + } + + // Add notes if available (first line only to keep it concise) + if let notes = event.notes, !notes.isEmpty { + let firstLine = notes.components(separatedBy: .newlines).first ?? "" + let cleanedNotes = firstLine.trimmingCharacters(in: .whitespacesAndNewlines) + // Filter out junk characters (like the -::~: pattern) + let validNotes = cleanedNotes.filter { char in + char.isLetter || char.isNumber || char.isWhitespace || char.isPunctuation && !"-:~".contains(char) + } + if !validNotes.isEmpty && validNotes.count <= 100 && !validNotes.allSatisfy({ "-:~".contains($0) }) { + meetingInfo += "\n Notes: \(String(validNotes))" + } + } + + return meetingInfo + }.joined(separator: "\n") + + let meetingCount = today.events.count + + summary = """ +Meetings: +\(meetingList) +""" + } + + NSPasteboard.general.declareTypes([NSPasteboard.PasteboardType.string], owner: nil) + NSPasteboard.general.setString(summary, forType: NSPasteboard.PasteboardType.string) + print("Copied meeting summary to clipboard") +} catch { + print("Error retrieving calendar events: \(error)") +} \ No newline at end of file diff --git a/commands/system/toggle-natural-scrolling-macos15.applescript b/commands/system/toggle-natural-scrolling-macos15.applescript new file mode 100755 index 000000000..710b7e435 --- /dev/null +++ b/commands/system/toggle-natural-scrolling-macos15.applescript @@ -0,0 +1,69 @@ +#!/usr/bin/osascript + +# Required parameters: +# @raycast.schemaVersion 1 +# @raycast.title Toggle Natural Scrolling (macOS 15+) +# @raycast.mode silent +# @raycast.packageName System + +# Optional parameters: +# @raycast.icon πŸ–± +# @raycast.author Raphael-KR +# @raycast.authorURL https://github.com/Raphael-KR +# @raycast.description Toggle natural trackpad/mouse scrolling setting for macOS 15.6.1+ + +set macVersion to system version of (system info) + +if macVersion is greater than or equal to "15" then + try + set isNaturalScrolling to (do shell script "defaults read .GlobalPreferences 'com.apple.swipescrolldirection' 2>/dev/null || echo '1'") + + if isNaturalScrolling is "1" then + do shell script "defaults write .GlobalPreferences 'com.apple.swipescrolldirection' -bool NO" + display notification "Natural Scrolling: OFF" with title "Trackpad Settings" + else + do shell script "defaults write .GlobalPreferences 'com.apple.swipescrolldirection' -bool YES" + display notification "Natural Scrolling: ON" with title "Trackpad Settings" + end if + + do shell script "/System/Library/PrivateFrameworks/SystemAdministration.framework/Resources/activateSettings -u" + + on error e + display notification "Using GUI method as fallback" with title "Trackpad Settings" + + tell application "System Settings" + activate + set current pane to pane "com.apple.preference.trackpad" + end tell + + delay 0.6 + + tell application "System Events" + tell process "System Settings" + click radio button 2 of tab group 1 of window "Trackpad" + click checkbox 1 of tab group 1 of window "Trackpad" + end tell + end tell + + tell application "System Settings" to quit + end try + +else + display notification "For macOS 14 and earlier, use the original script" with title "Compatibility Note" + + tell application "System Settings" + activate + set current pane to pane "com.apple.preference.trackpad" + end tell + + delay 0.6 + + tell application "System Events" + tell process "System Settings" + click radio button 2 of tab group 1 of window "Trackpad" + click checkbox 1 of tab group 1 of window "Trackpad" + end tell + end tell + + tell application "System Settings" to quit +end if