Display result of simple shell script in HUD Overlay; async JavaScript function?

I want to run a single line shell script, and display the text result in a HUD Overlay. Seems simple enough, but I can't figure out the simplest way to get the shell output to the HUD Overlay step...

You need to lookup variables. The script needs to set a BTT variable as its returned output and then yoh can show the variable inside a HUD.

I couldn't find any documentation on setting a variable from the Execute Shell Script step, only via AppleScript, or using JavaScript... I guess that's what I need to do, wrap the script in a JS per examples in the docs?

You can run AppleScript code from within shell scripts using

osascript -e 'AppleScript here'

OR

You can run shell scripts inside an AppleScript using

do shell script 'shell commands here'

I'm using example #2 in the doc JavaScript examples. It's working now, but the HUD always shows the result from the previous time it was invoked, so it's always wrong. Here's my script...

(async () => {

// put the shell script into a string (single backticks are great for multiline strings)
let shellScript = `/Users/x/bin/SwitchAudioSource -n`;


let shellScriptWrapper = {
    script: shellScript, // mandatory
    launchPath: '/bin/bash', //optional - default is /bin/bash
    parameters: '-c', // optional - default is -c
    environmentVariables: '' //optional e.g. VAR1=/test/;VAR2=/test2/;
};

// this will execute the Apple Script and store the result in the result variable.
let result = await runShellScript(shellScriptWrapper);

// do whatever you want with the result
await callBTT('set_string_variable', {variable_name: 'result', to: result});

// at the end you always need to call returnToBTT to exit the script / return the value to BTT.
returnToBTT(result);

// it is important that this function self-executes ()
})();

I've tried calling the HUD directly in the Javascipt, to even worse effect: it's totally blank and is displayed for less than 1 second. It also causes BTT to hang half the time, needing a force quit. I'm pretty frustrated this is so tricky.

(async () => {

	// put the shell script into a string (single backticks are great for multiline strings)
	let shellScript = `/Users/x/bin/SwitchAudioSource -n`;


	let shellScriptWrapper = {
		script: shellScript, // mandatory
		launchPath: '/bin/bash', //optional - default is /bin/bash
		parameters: '-c', // optional - default is -c
		environmentVariables: '' //optional e.g. VAR1=/test/;VAR2=/test2/;
	};

	// this will execute the Apple Script and store the result in the result variable.
	let result = await runShellScript(shellScriptWrapper);

	// do whatever you want with the result
	let actionDefinition = {
		"BTTPredefinedActionType" : 254,
		"BTTPredefinedActionName" : "Show HUD Overlay",
		"BTTHUDActionConfiguration" : "{\"BTTActionHUDDetail\":\"" + result + "\",\"BTTActionHUDTitle\":\"sound\",\"BTTActionHUDDuration\":1.8999999761581421,\"BTTActionHUDBackground\":\"38.719535, 38.720697, 38.720067, 213.662109\",\"BTTActionHUDSlideDirection\":0}",
	}
	callBTT('trigger_action', {json: JSON.stringify(actionDefinition)});

	// at the end you always need to call returnToBTT to exit the script / return the value to BTT.
	returnToBTT(result);

// it is important that this function self-executes ()
})();

Again, very frustrating this is so hard. I wasn't planning on learning how to use Promises in JS today, and it still doesn't work as expected. If I settle for a notification instead of HUD, Automator takes care of this in three very simple steps. :frowning:

Sorry the BTT hud is not really designed for being called externally.

Your last example breaks because result contains unescaped quotes. This should work (but it will look kind of ugly with longer text):

(async () => {


let shellScript = `/usr/local/bin/SwitchAudioSource -n`;


	let shellScriptWrapper = {
		script: shellScript, // mandatory
		launchPath: '/bin/bash', //optional - default is /bin/bash
		parameters: '-c', // optional - default is -c
		environmentVariables: '' //optional e.g. VAR1=/test/;VAR2=/test2/;
	};

	let result = await runShellScript(shellScriptWrapper);


	// do whatever you want with the result
let actionDefinition = {
		"BTTPredefinedActionType" : 254,
		"BTTPredefinedActionName" : "Show HUD Overlay",
		"BTTHUDActionConfiguration" : "{\"BTTActionHUDDetail\":\"" + result.replace(/[\""]/g, '\\"') + "\",\"BTTActionHUDTitle\":\"sound\",\"BTTActionHUDDuration\":1.8999999761581421,\"BTTActionHUDBackground\":\"38.719535, 38.720697, 38.720067, 213.662109\",\"BTTActionHUDSlideDirection\":0}",
	}
	
	
	callBTT('trigger_action', {json: JSON.stringify(actionDefinition)});

	// at the end you always need to call returnToBTT to exit the script / return the value to BTT.
	returnToBTT(result);

// it is important that this function self-executes ()
})();

The doc mentions always needing to call returntoBTT. Perhaps, @Andreas_Hegenberg can help clarify how we can access this returned value in BTT itself so there would be no need of a variable in between. This could also solve some of my issues as I’ve been able to use values returned from scripts to show in TouchBar but not elsewhere like HUDs or perhaps passing it to the next action.

I'm working on ways to pass data directly to the next action in a sequence, but this is still a work in progress. Will hopefully become much easier soon :slight_smile:

1 Like

@Andreas_Hegenberg thanks for the code and comments. Your last code worked, and I was able to tweak it adding an emoji title on the HUD too ("\uD83D\uDD0A\"). It's a good snippet to reuse for other shell scripts. But as you say, it would be great if I could configure the HUD in the BTT interface using either a set variable (which was always showing the last run result), or passing data to the next in a sequence (otherwise, what's the point of returning the result anyway?!)...

Yes that's the plan :slight_smile:
Currently the result can be used in things like the floating web view or Touch Bar widgets, but in the future I want to make any action result easily available to the next action.

1 Like

If I may chime in with another suggestion for HUDs is to please allow it to be shown in full-screen apps. Right now it doesn't show most times and so I've fallen back to using LaunchBar to show text in largetype as my way of quick notification.

Currently the result can be used in things like the floating web view or Touch Bar widgets, but in the future I want to make any action result easily available to the next action.

@Andreas_Hegenberg Has this been released? Would be very useful

Instead of hand-escaping the string, I think it would be a lot easier/cleaner to just let JSON.stringify() handle that as well:

// do the shell script stuff and get the result. 
// Then…

const hudConfig = {
  BTTActionHUDTitle: 'sound',
  BTTActionHUDDetail: result,  
  BTTActionHUDDuration : 1.8999999761581421,
  BTTActionHUDBackground: '38.719535, 38.720697, 38.720067, 213.662109',
  BTTActionHUDSlideDirection: 0
};
const actionDefinition = {
  BTTPredefinedActionType : 254,
  BTTPredefinedActionName: 'Show HUD Overlay',
  BTTHUDActionConfiguration: hudConfig,
};

callBTT('trigger_action', {json: JSON.stringify(actionDefinition)});

returnToBTT(result);