This started as a fun thought experiment around plugin trust: build a tiny extension for two everyday apps and see how easily that code can run an OS command. I used an OBS Lua script and a custom Obsidian plugin to pop Calculator. OBS ran the command when I triggered the scriptโs hotkey; Obsidian ran it as soon as the plugin loaded.
OBS: Lua to shell
Save this as run-command.lua, then load it through Tools โ Scripts โ +:
local obs = obslua
local hotkey_id
local COMMAND = 'start "" "C:\\Windows\\System32\\calc.exe"'
local function run_command(pressed)
if pressed then
os.execute(COMMAND)
end
end
function script_description()
return "Runs a configured OS command through an OBS hotkey."
end
function script_load(settings)
hotkey_id = obs.obs_hotkey_register_frontend(
"local_poc_launch_calculator",
"Local test: launch Calculator",
run_command
)
end
function script_unload()
if hotkey_id then obs.obs_hotkey_unregister(hotkey_id) end
end
os.execute() invokes the shell. start "" supplies an empty window title before the quoted executable path. Replace COMMAND with another locally chosen Windows command, such as whoami, to change what executes.
OBS WebSocket v5 can trigger the registered hotkey. After connecting and completing authentication, send:
{
"op": 6,
"d": {
"requestType": "TriggerHotkeyByName",
"requestId": "calculator-test",
"requestData": {
"hotkeyName": "local_poc_launch_calculator"
}
}
}
The message selects an existing action. It does not upload Lua or supply a new command. The previously loaded script provides the execution capability. I verified the WebSocket request succeeded and a Calculator window appeared.
Obsidian: plugin load to shell
Create two files in the vault:
.obsidian/plugins/calculator-launch-demo/
โโโ manifest.json
โโโ main.js
manifest.json:
{
"id": "calculator-launch-demo",
"name": "Calculator Launch Demo",
"version": "1.0.0",
"minAppVersion": "1.4.0",
"description": "Runs a local command when enabled.",
"author": "Local demo",
"isDesktopOnly": true
}
main.js:
const { Plugin } = require("obsidian");
const { exec } = require("node:child_process");
module.exports = class CalculatorLaunchDemo extends Plugin {
onload() {
if (process.platform !== "win32") return;
const command = '"C:\\Windows\\System32\\calc.exe"';
exec(command, { windowsHide: true }, (error) => {
if (error) console.error("Command failed:", error);
});
}
};
Allow community plugins, reload the installed-plugin list, and enable Calculator Launch Demo. This is CommonJS JavaScript; no npm build or separate Shell commands plugin is required.
onload() runs when Obsidian loads the plugin. Nodeโs exec() passes the command to the Windows shell, under the permissions available to Obsidian. Changing the command string changes the OS command that runs. windowsHide hides the intermediary console, not Calculator.
I enabled the plugin and verified a new Calculator window appeared. Leaving it enabled repeats the launch when Obsidian starts; disabling and re-enabling it repeats the test.
Where the trust lands
OBS: loaded Lua โ registered hotkey โ os.execute()
Obsidian: enabled plugin โ onload() โ child_process.exec()
Neither API is restricted to Calculator. Trusting the extension admits code that can start local processes. OBS adds a remote trigger for an action already installed; the Obsidian example executes directly at plugin load.
References: OBS scripting ยท OBS WebSocket hotkey request ยท Obsidian plugin lifecycle ยท Node child processes.