Run Shortcuts in the Background from Drafts

Practically, since Apple made Shortcuts available on the Mac, Drafts has supported executing Drafts and processing the return. To do this, Drafts passes control to Shortcuts, bringing it to the foreground, and then switches back to Drafts at the end. This feels suspiciously like an x-callback-url call of a shortcut.

While x-callback-url is a very useful option, on the Mac, we have more options and control over how we interact with other automation applications, and in this post I’ll outline an alternative that allows you to run a shortcut in the background.

Calling Shortcuts

The main premise for being able to run a shortcut in the background is to call it via the command line interface rather than via a URL. I suspect that it can also be done with the AppleScript shortcuts too1.

However, I find shell scripting generally more logical for the way I think and so my personal preference is to use a shell script over AppleScript where I have the option.

A Core Function

While Drafts’ scripting language of choice is JavaScript (a good choice for cross-platform scripting), it supports calling shell script commands and processing results via it’s ShellScript class.

This allows us to build a function to run our shortcut without too much effort. The function below will run a shortcut with text input, and return the result. If there is an error, details will be written to the Drafts action log, the error will be displayed in a Drafts app message bar, and the function will return nothing.

// Run a shortcut and return the result
function shortcutRun(p_strShortcutName, p_strShortcutInput)
{
	// Define the script to run
	let objShell = ShellScript.create(`#!/bin/zsh
echo "${p_strShortcutInput}" | shortcuts run "${p_strShortcutName}" | tee`);

	// Run the script and return the result or write out any error to the log.
	if (objShell.execute()) return objShell.standardOutput;
	else
	{
		app.displayErrorMessage(objShell.standardError);
		console.log("STDERR: " + objShell.standardError);
		return;
	}
}

A simple call of a shortcut called ‘To Uppercase’ with an input of ‘foo bar’ and displaying the result in a pop up message would then be as follows:

alert(shortcutRun("To Uppercase", "foo bar"));

Appending and Inserting Results

While there are probably many instances where we might capture the return into a variable, there are probably an equal number of times we might want to get that content directly into a draft.

Since this is something that we might want to do fairly frequently, I created some convenience functions for appending to the end of the current draft, and inserting into the current cursor position(/selection) in the current draft.

// Run a shortcut and append the result to the end of the draft
function shortcutAppend(p_strShortcutName, p_strShortcutInput)
{
	// Append output to a new line at the end
	draft.content = draft.content + "\n" + shortcutRun(p_strShortcutName, p_strShortcutInput);
	return draft.update();
}

// Run a shortcut and insert the result at the current cursor position
function shortcutInsert(p_strShortcutName, p_strShortcutInput)
{
	return editor.setSelectedText(shortcutRun(p_strShortcutName, p_strShortcutInput));
}

Replacing Content in the Draft with Results

As well as inserting content, we may also find that we are replacing content within the draft. Again, I felt it made sense to whip up a few convenience functions to support some standard replacements.

// Run a shortcut and replace the draft content with the result
function shortcutReplaceDraft(p_strShortcutName, p_strShortcutInput)
{
	// Replace content
	draft.content = shortcutRun(p_strShortcutName, p_strShortcutInput);
	return draft.update();
}

// Run a shortcut and replace the draft body with the result
function shortcutReplaceBody(p_strShortcutName, p_strShortcutInput)
{
	// Append output to the title and replace content
	draft.content = draft.title + "\n" + shortcutRun(p_strShortcutName, p_strShortcutInput);
	return draft.update();
}

Standard Inputs from Drafts

Another thing you might find you want to do often is to use parts of a draft as input. Typically the entire content of the draft, the body (all but the title), or the selected text. We can add some further convenience functions relatively easily using our previous functions.

Draft Content as Input

To denote the input as being the draft content, we’ll name the functions as the previous ones appended with _Draft.

// Run a shortcut with the draft content as input and return the result
function shortcutRun_Draft(p_strShortcutName)
{
	return shortcutAppend(shortcutRun_Draft, draft.processTemplate('[[draft]]'));
}

// Run a shortcut with the draft content as input and append the result to the end of the draft
function shortcutAppend_Draft(p_strShortcutName)
{
	return shortcutAppend(p_strShortcutName, draft.processTemplate('[[draft]]'));
}

// Run a shortcut with the draft content as input and insert the result at the current cursor position
function shortcutInsert_Draft(p_strShortcutName)
{
	return shortcutInsert(p_strShortcutName, draft.processTemplate('[[draft]]'));
}


// Run a shortcut with the draft content as input and and replace the draft content with the result
function shortcutReplaceDraft_Draft(p_strShortcutName)
{
	return shortcutReplaceDraft(p_strShortcutName, draft.processTemplate('[[draft]]'));
}

// Run a shortcut with the draft content as input and and replace the draft body with the result
function shortcutReplaceBody_Draft(p_strShortcutName)
{
	return shortcutReplaceBody(p_strShortcutName, draft.processTemplate('[[draft]]'));
}

Draft Body as Input

Keeping with the convention above we will denote the input as being the draft body by appending with _Body.

// Run a shortcut with the draft body as input and return the result
function shortcutRun_Body(p_strShortcutName)
{
	return shortcutAppend(shortcutRun_Draft, draft.processTemplate('[[body]]'));
}

// Run a shortcut with the draft body as input and append the result to the end of the draft
function shortcutAppend_Body(p_strShortcutName)
{
	return shortcutAppend(p_strShortcutName, draft.processTemplate('[[body]]'));
}

// Run a shortcut with the draft body as input and insert the result at the current cursor position
function shortcutInsert_Body(p_strShortcutName)
{
	return shortcutInsert(p_strShortcutName, draft.processTemplate('[[body]]'));
}


// Run a shortcut with the draft body as input and and replace the draft content with the result
function shortcutReplaceDraft_Body(p_strShortcutName)
{
	return shortcutReplaceDraft(p_strShortcutName, draft.processTemplate('[[body]]'));
}

// Run a shortcut with the draft body as input and and replace the draft body with the result
function shortcutReplaceBody_Body(p_strShortcutName)
{
	return shortcutReplaceBody(p_strShortcutName, draft.processTemplate('[[body]]'));
}

Selected Text as Input

Once again, retaining the convention above, we will denote the input as being the selected by appending with _Selected.

// Run a shortcut with the selected text as input and return the result
function shortcutRun_Selected(p_strShortcutName)
{
	return shortcutAppend(shortcutRun_Draft, draft.processTemplate('[[selection_only]]'));
}

// Run a shortcut with the selected text as input and append the result to the end of the draft
function shortcutAppend_Selected(p_strShortcutName)
{
	return shortcutAppend(p_strShortcutName, draft.processTemplate('[[selection_only]]'));
}

// Run a shortcut with the selected text as input and insert the result at the current cursor position
function shortcutInsert_Selected(p_strShortcutName)
{
	return shortcutInsert(p_strShortcutName, draft.processTemplate('[[selection_only]]'));
}


// Run a shortcut with the selected text as input and and replace the draft content with the result
function shortcutReplaceDraft_Selected(p_strShortcutName)
{
	return shortcutReplaceDraft(p_strShortcutName, draft.processTemplate('[[selection_only]]'));
}

// Run a shortcut with the selected text as input and and replace the draft body with the result
function shortcutReplaceBody_Selected(p_strShortcutName)
{
	return shortcutReplaceBody(p_strShortcutName, draft.processTemplate('[[selection_only]]'));
}

Test Background Execution

To this point, we haven’t actually demonstrated that the execution takes place in the background versus foreground for the usual Shortcuts call. To that end I have created some links to an Apple Shortcuts shortcut and two Drafts actions.

The shortcut takes text as input and converts it to alternating upper and lower case characters prefixed by a time stamp. But, it only does this after a deliberate one second pause. The pause just being there to ensure you can clearly see a switch out to Shortcuts on a fast Mac.

The replace action at the end just removes any final newline the text transformation adds.

The first action calls this shortcut using the standard Drafts action step Run Shortcut to call the shortcut. It passes in the selected text and then uses an Insert Text action step to replace the selected text with the result from the shortcut.

The second action calls this shortcut via the shortcutInsert_Selected() function specified above. The action has two script steps. The first contains all of the functions set out above. The second step just contains the shortcutInsert_Selected() call.

Running the first action, you will see Shortcuts come to the fore. Running the second action, Drafts should remain the foreground app.

Summary

If you want to remain in the flow, without having Drafts switch to and from Shortcuts when it is calling a shortcut, hopefully these functions will help. You can duplicate or modify the Scripted Shortcut Run action, or copy the functions direct from the gist below.


Select the marker to expand this section and display the gist


It takes a little more effort to use these functions, but I find it generally preferable when I’m running and action for it to stay in Drafts - but at the end of the day, it doesn’t affect the result, just the journey taken to get there.

To ensure you have compatibility on iOS and iPadOS, you can consider combining the standard and scripted methods within the same action, and using the platform check boxes for the action steps to enable the desired action steps on the required platforms.

  1. The format of the AppleScript would be like this:

    tell application "Shortcuts Events" to run the shortcut named "Name of the shortcut" with input "text to pass to the shortcut"
    

Author: Stephen Millard
Tags: | shortcuts | drafts |

Buy me a coffeeBuy me a coffee



Related posts that you may also like to read