AI & App Launcher Tutorial
Note: This requires at least BetterTouchTool v4.788
Download the final result of this tutorial here (show the launcher via fn+d):
LauncherTutorial.bttpreset (21.5 KB)
I'll be creating some basic tutorials for some recent BTT features. This one is about creating a simple AI launcher (that can also launch apps). It just serves as an example what can be achieved with these new abilities:
- Predefined Action "Choose From List / Prompt" (Show List To Choose From · GitBook)
- Simple JSON Format ( Simple JSON Format · GitBook )
Instead of AI stuff or app launching you can of course modify this to trigger any other action in BTT and in the future you'll also be able to display floating menus within the "Choose From List / Prompt"
This is what the result of this tutorial will look like:
Step 1: Add a Keyboard Shortcut to show the launcher
Of course you could also use any other trigger in BTT, but for this tutorial we'll use a keyboard shortcut. In this example I choose fn+D.
Make sure to assign the predefined action "Show / Choose From List" to the keyboard shortcut.
If you now press fn+D you should see an empty launcher:
Step 2: Add some content to the launcher.
Add an action to summarize the currently selected text
Click the "Configure List Items" button to open the UI for adding items to the list, it will look like this:
Add a first item. We want the first item to be a AI function that summarizes the currently selected text.
Configure name & icon:
Configure the summarize action:
BTT includes a few AI actions, the one we'll be using here is called Transform & Replace Selection With ChatGPT.
BTT comes with a base contingent of free calls to the OpenAI API, however I recommend to provide your own API key, then you are also free to choose the model you want to use. For this tutorial we'll use the free contingent.
Add an action that uses the input from the launcher and (optionally) the selected text:
We want to use the text typed into the launcher as a prompt for ChatGPT. However by default the list is just filtered when typing something. To prevent that change the "When Entering Text" setting on the "Show / Choose From List" action to "Filter & Save Input To Variable BTTPromptInput".
This will make sure the text you type is saved to a variable that can be used later. However the list will still be filtered, so we won't be able to select an action that passes the input to ChatGPT.
To resolve this, we create a new list item and activate the "Always Show, Ignore Filter" option. By doing this, the item will always be shown even if it doesn't match the input / search.
Now to see whether the variable is correctly populated let's add a "Show HUD" action that shows the prompt. The Show HUD action allows to display content of variables by wrapping them in curly braces:
When testing this now, it will show a HUD with the text you entered:
Now let's add an action that forwards the input to ChatGPT. This time we'll use the "Use ChatGPT (Optionally on Selected Text). Copy Result To Clipboard / Variable". We can use the BTTPromptInput variable directly in the user prompt for ChatGPT. Also we'll put the response into the clipboard and into a custom variable "BTTChatGPTResponse".
This variable we can show in a HUD afterwards (or use it in any other way you can think of)
So when you use it now, it will look like this:
Now the AI part of this tutorial is pretty much finished. You can add more AI actions with different prompts and different configurations to meet your needs. This can get arbitrarily powerful & complex.
Step 3: Make it an app launcher as well
Now to demonstrate the new "Simple JSON Format", we'll also make this an app launcher. However we don't want to add the apps manually to the list of items. Instead we want to retrieve them dynamically.
Turn On Dynamic Content Retrieval
To achieve this enable the "Retrieve Content Dynamically via Java Script / JSON" option on the "Show / Choose From List" action:
Now this Java Script can use any functions described here: Using Java Script (not JXA) · GitBook to create an output JSON based on the Simple JSON Format. This is really powerful. We could run some terminal commands to retrieve a list of all apps and their icons whenever we open the list.
However retrieving all apps dynamically every time would be pretty slow. Instead we will create a cache file when BTT launches and just load that when the launcher is shown. So let's define a JSON file to read from:
async function retrieveJSON() {
return readFile("~/Library/Application Support/BetterTouchTool/app-cache.json");
}
That file does not exist yet, so nothing will happen at this point. Let's change that.
Load Apps Into Cache
A good place to load your System's apps into the cache and save them to the ~/Library/Application Support/BetterTouchTool/app-cache.json file would be the "After BetterTouchTool Did Launch" trigger in "Automations, Named & Other Triggers". Using this we can update the app cache once everytime BTT starts.
We need to create a script that retrieves the apps. I'm using this one, which makes use of the macOS "mdfind" command line utility:
async function createAppCache() {
let filePaths = await runShellScript({
// this will search the typical app folders
script: `mdfind -onlyin ~/Applications -onlyin /Applications -onlyin /System/Applications 'kMDItemFSName == "*.app"'`,
});
// Split the file paths by new line
let fileArray = filePaths.split("\n").filter(Boolean); // filter(Boolean) removes any empty lines
// Create an array of objects with filePath and fileName
let files = fileArray.map((filePath) => {
let fileName = filePath.split("/").pop(); // Extract the file name from the path
return { filePath, fileName };
});
let menuItems = [];
// Function to retrieve the icon path by reading the Info.plist
async function getAppIconPath(filePath) {
// Construct the path to the Info.plist file
let plistPath = `${filePath}/Contents/Info.plist`;
// Run a shell script to retrieve the CFBundleIconFile from the Info.plist
let iconName = await runShellScript({
script: `defaults read "${plistPath}" CFBundleIconFile || echo "AppIcon"`,
});
// Add the .icns extension if not present
iconName = iconName.trim();
if (!iconName.endsWith(".icns")) {
iconName += ".icns";
}
// Return the full path to the .icns file
return `${filePath}/Contents/Resources/${iconName}`;
}
// Create the list menu items that trigger a shell script to open the app
for (let file of files) {
let iconPath = await getAppIconPath(file.filePath); // Get the correct icon path from Info.plist
let item = {
title: file.fileName,
action: `js::(async () => {runShellScript({script: 'open "${file.filePath}"'})})()`,
icon: `path::${iconPath}::width@@30`,
};
menuItems.push(item);
}
// add finder manually because it's in an unusal location
menuItems.push({
title: "Finder",
action: `js::(async () => {runShellScript({script: 'open "/System/Library/CoreServices/Finder.app"'})})()`,
icon: `path::/System/Library/CoreServices/Finder.app/Contents/Resources/Finder.icns::width@@30`,
});
// write to the cache file
return writeStringToFile(
JSON.stringify(menuItems),
"~/Library/Application Support/BetterTouchTool/app-cache.json"
);
}
This script retrieves the paths of the apps on your system via mdfind. Then it creates an array of objects with this format:
{
"title": "Finder",
"action": `js::(async () => {runShellScript({script: 'open "/System/Library/CoreServices/Finder.app"'})})()`,
"icon": `path::/System/Library/CoreServices/Finder.app/Contents/Resources/Finder.icns::width@@30`,
}
The action runs some BTT Java Script to run the open terminal command to open the app.
Try running this script via the predefined action "Run Real Java Script" at least once, if it shows "done" as the script result (after a few seconds) everything worked fine:
Done!
Now everything should be ready, if you now open your launcher it should show the various apps on your system and allow you to open them.
Of course this is just one example of what can be achieved with the Simple JSON Format. You can do any sort of things using that and there are many more options available: Simple JSON Format · GitBook.
Download the final result of this tutorial here:
LauncherTutorial.bttpreset (21.5 KB)