Alfred workflows with a single shell script

 · 3 min · torgeir

There are so many great Alfred workflows! I use a lot of them every day. One gripe I have with a few of them is that they bundle some kind of unsigned binary that I cannot look at or vet. This post investigates how you can roll your own Alfred workflow with a single, standalone shell script.

Terminal Jq Bash Zsh Github Alfred Macos

This was the simplest one I could make:

  • +
  • Blank Workflow
  • Name it, e.g. test
  • Right click, add Inputs -> Script filter
  • Keyword: test
  • Check with space
  • Select Argument Required
  • Language: /bin/zsh
  • Select with input as {query}

Paste the following

echo '{ "items":[{ "title": "{query}", "arg": 1 }] }'

This creates a workflow that will echo what you type after test back as a single result in the Alfred dropdown. Titles of items are shown in the Alfred dropdown. The arg of the selected item will be passed to downstream actions you choose to connect to the Script Filter in your Alfred workflow.

Now for something a bit more useful.

A workflow to complete and open your github repos

Here’s a few lines to grab some example data to use for a workflow that will complete your github repos. It lists repos with gh and extracts the first column (print $1) from the first four. Jq wraps them in a compact (-c) json object, treating the input as raw strings (-R). The . sticks what was received on stdin in its place.

gh repo list torgeir \
    | sort \
    | awk '{print $1}' \
    | head -n 4 \
    | jq -cR '{ repo: . }'
{"repo":"torgeir/.emacs.d"}
{"repo":"torgeir/CSS-workshop"}
{"repo":"torgeir/brainjs"}
{"repo":"torgeir/cli"}

Now make it ready for Alfred, by adding title and arg to the json objects. Make it narrowable by grep-ing after the {query} you have given in the Alfred search field. Adjust or remove head -n 4 to tune the number of suggestions Alfred will show. The [inputs] part to jq will inline all new line delimited json objects given on stdin as separate items of the items array. Jq will add commas between them to produce correct json.

gh repo list torgeir --limit 1000 \
  | sort \
  | awk '{print $1}' \
  | grep --color=never "{query}" \
  | head -n 4 \
  | jq -cR '{ title: ., arg: . }' \
  | jq -cn '{ items: [inputs] }'
{"items":[{"title":"torgeir/.emacs.d","arg":"torgeir/.emacs.d"},{"title":"torgeir/7langs7weeks","arg":"torgeir/7langs7weeks"},{"title":"torgeir/AuthishModule","arg":"torgeir/AuthishModule"},{"title":"torgeir/BusBuddyJS","arg":"torgeir/BusBuddyJS"}]}

Right click the workflow and connect it to Actions -> Open URL and type http://github.com/{query}, and you have your own little workflow to complete and open your github repos.

Speed it up

By dumping all repos to a file, .gitrepos, and using it as input instead, if it is present, the workflow will feel a lot snappier. This can all be done from the same script. A simple solution to still keep the list fresh would be to delete it once it reaches a certain age, e.g. 1 day, using -mtime +1 as an arg to find, and only fetch new repos if the file is not present.

repos=$HOME/.gitrepos

if [[ $(find "$repos" -mtime +1 -print) ]]; then
  rm $repos
fi

if [ ! -f $repos ]; then
  # add more accounts/orgs here
  echo -n $(for owner in torgeir omniscientjs; do \
      gh repo list $owner -L 1000 | awk '{print $1}'; \
    done) > $repos
fi

cat $repos \
  | tr ' ' '\n' \
  | rg -i $(echo "{query}" | sed -E "s/[ ]+//g" | fold -w1 | tr '\n' '*' | sed -E "s/\*/\.\*/g") \
  | jq -cR '{title:., arg:.}' \
  | jq -cn '{items: [inputs]}'

This also adds a poor mans fuzzy search, by splitting each the input {query} into characters, each on a new line, replacing all newlines with asterisks, and again replacing each asterisk with .*, as an input to rg. This makes test become t.*e.*s.*t. It also ignores spaces, if you prefer putting spaces in your fuzzy search terms.

Have fun!