Open Warp Launch Configurations from Raycast and Alfred
Launch configurations from Warp are an incredible tool in our development tool belt. Let's get them working with both Raycast and Alfred so we can utilize them without opening Warp.
I've been using Warp as my terminal for a couple of months now, and I have to say: I really like it.
One of my favorite features is their Launch Configurations:
Launch Configurations enables you to save your configuration of windows, tab, and panes, so that you can reopen the same set of windows, tab, and panes per project quickly.
This is basically a way more official version of what I was doing in iTerm2, so there's less hackery involved.
Warp launch configurations are stored as YAML files in ~/.warp/launch_configurations/
, and you can launch them in Warp by hitting ⌘+ctrl+L
:
That's... cool. For sure. But it's not super fast. I want to be able to dive into a project from wherever I am on my computer, which means that I need to integrate this into Alfred (or Raycast, depending on the day).
But Warp doesn't have a direct integration with either of these, and it doesn't seem to have a command line method of opening up launch configs either, so we'll have to reach into our JXA toolbox and just script exactly the same actions that we take manually, but automatically. Lezzdoit.
Hold On
Hey, Joe: I don't care how you built them. Just give me a link.
And to that I say: You got it, buddy.
Alfred Workflow
Raycast Extension
Building the Alfred Workflow
This is going to be pretty simple overall. We need to:
List the YAML files in
~/.warp/launch_configurations/
Grab the file base name and pass it to the JXA, running the commands in Warp as we normally would
We're going to list out the files in the launch configurations directory via a Script Filter because Alfred had trouble listing the contents of the dot directory. No problem:
1#!/usr/bin/osascript -l JavaScript 2 3// Modified from: https://deanishe.net/alfred-workflows/Search%20PDFs.alfredworkflow 4 5// Folder to search in 6const folder = '~/.warp/launch_configurations'; 7// Only show files with this extension 8const extension = 'yaml'; 9 10// Return list of files in folder with given extension.11function findFiles(folder, extension) {12 const fm = $.NSFileManager.defaultManager;13 const files = [];14 15 const root = $(folder).stringByExpandingTildeInPath;16 const contents = fm.contentsOfDirectoryAtPathError(root, null);17 18 for (let i = 0; i < contents.count; i++) {19 files.push(20 root.stringByAppendingPathComponent(contents.objectAtIndex(i)),21 );22 }23 24 if (extension) {25 return files.filter((p) => p.pathExtension.js == extension);26 }27 28 return files;29}30 31// Convert NSString path to an Alfred feedback item.32function alfredItem(path) {
33 return {34 title: path.lastPathComponent.js.replace('.yaml', ''),35 subtitle: path.stringByAbbreviatingWithTildeInPath.js,36 arg: path.stringByAbbreviatingWithTildeInPath.js,37 uid: path.js,38 valid: true,39 type: 'file',40 };41} 42 43function queryRegex(query) {
44 const pat = [];45 const iter = query[Symbol.iterator]();46 const char = iter.next();47 48 while (!char.done) {49 pat.push(char.value);50 char = iter.next();51 }52 53 return new RegExp(pat.join('.*'), 'i');54} 55 56function toJSON(files) {
57 return JSON.stringify({ items: files.map(alfredItem) }, null, 2);58} 59 60function run(argv) {61 const query = argv.length ? argv[0] : null;62 63 const files = findFiles(folder, extension);64 65 if (!query) {66 return toJSON(files);67 }68 69 const regex = queryRegex(query);70 71 return toJSON(72 files.filter((p) => p.lastPathComponent.js.match(regex) !== null),73 );74}
Then we do some transforming on Alfred's site (strip out the path and extension) and pass the result to our JXA:
1function run() { 2 const query = '{query}'; 3 const se = Application('System Events'); 4 5 Application('Warp').activate(); 6 7 se.keystroke('l', { using: ['command down', 'control down'] }); 8 se.keystroke(query); 9 se.keyCode(36);10}
And there we have it, we're opening launch configs via Alfred with this simple workflow:
Building the Raycast Extension
Principle is the same as the Alfred workflow, with some adjustments for Raycast:
1
2import { ActionPanel, Action, List, closeMainWindow } from '@raycast/api'; 3import { useEffect, useState } from 'react'; 4import fs from 'node:fs'; 5import path from 'node:path'; 6import os from 'node:os'; 7import { runJxa } from 'run-jxa'; 8 9export default function Command() {10 const [searchText, setSearchText] = useState('');11 const [results, setResults] = useState<SearchResult[]>([]);12 13 useEffect(() => {14 const fullPath = path.join(os.homedir(), '.warp/launch_configurations');15 16 fs.readdir(fullPath, (error, files) => {17 if (error) {18 console.log(error);19 }20 21 setResults(22 files.map((file) => ({23 name: file.replace('.yaml', ''),24 path: path.join(fullPath, file),25 })),26 );27 });28 }, []);29 30 return (31 <List32 isLoading={results.length === 0}33 onSearchTextChange={setSearchText}34 searchBarPlaceholder="Searching for launch configurations..."35 throttle36 >37 <List.Section title="Results" subtitle={results?.length + ''}>38 {results39 ?.filter((f) => f.name.includes(searchText.toLowerCase()))40 .map((searchResult) => (41 <SearchListItem42 key={searchResult.name}43 searchResult={searchResult}44 />45 ))}46 </List.Section>47 </List>48 );49}50 51function SearchListItem({ searchResult }: { searchResult: SearchResult }) {52 const appleScript = `const se = Application('System Events');53 Application('Warp').activate();54 se.keystroke('l', { using: ['command down', 'control down'] });55 se.keystroke('${searchResult.name}');56 se.keyCode(36);`;57 58 return (59 <List.Item60 title={searchResult.name}61 actions={62 <ActionPanel>63 <ActionPanel.Section>64 <Action65 title="Launch"66 onAction={() => {67 runJxa(appleScript);68 closeMainWindow();69 }}70 />71 </ActionPanel.Section>72 <ActionPanel.Section>73 <Action.ShowInFinder74 title="Reveal in Finder"75 path={searchResult.path}76 shortcut={{ modifiers: ['cmd'], key: '.' }}77 />78 <Action.Open79 title="Edit Launch Configuration"80 target={searchResult.path}81 shortcut={{ modifiers: ['cmd'], key: 'o' }}82 />83 </ActionPanel.Section>84 </ActionPanel>85 }86 />87 );88}89 90interface SearchResult {91 name: string;92 path: string;93}
Super simple, really. We've also added some extra actions that allow you to view the selected workflow in the Finder or edit it directly. Bing bang boom.
Wrapping Up
Hope you found this useful! Here are those links again if you're interested in using either of them: