Running local commands
Most widgets never need this. A widget that must start a program (a launcher, claude -p "Hello", a script runner) declares each command in widget.json, and the page can only name a declared id. This is the one place a widget runs code outside the sandbox, so it is tightly constrained.
Declare it
Section titled “Declare it”"permissions": { "commands": [ { "id": "wake", "run": ["claude", "-p", "Hello"], "description": "Starts the Claude usage window" }, { "id": "ask", "run": ["claude", "-p", "${prompt}", "--model", "${model}"], "params": { "prompt": { "maxLength": 300 }, "model": { "choices": ["haiku", "sonnet"] } }, "timeoutSeconds": 90 } ]}Run it
Section titled “Run it”const r = await umbra.run("wake"); // { exitCode, stdout, stderr, timedOut, truncated, durationMs }await umbra.run("ask", { prompt: "Say hi", model: "haiku" });The rules
Section titled “The rules”These are checked when the widget loads, by scripts/catalog.mjs, and on install. A violation stops the widget from loading.
runis[executable, ...arguments], never a shell line. The executable is a plain program name found onPATH(claude,python) or an absolute local path (C:\Tools\app.exe); no relative paths, no UNC. The page can’t change it.- Each argument is a literal, or exactly
"${name}"for a declared parameter. A parameter can’t be part of a longer string ("--model=${m}"is rejected), and the executable can’t be a parameter. - A parameter is a string, always supplied, at most
maxLengthcharacters (default 200, max 1000), without control characters, and must not start with-or/unless it’s one of the fixedchoices. - If the executable is an interpreter (
cmd,powershell,pwsh,wscript,cscript,mshta,node,python,py,bash,sh,wsl,rundll32,regsvr32,msiexec, or a.ps1/.bat/.cmd/.vbs/.jsfile), every parameter needs fixedchoices; free text is refused when the widget loads. A parameter reaches the program as one single argument, so spaces and quotes in it can’t add arguments. .cmd/.batfiles only start for commands without parameters.timeoutSecondsis 1 to 120 (default 60).
How it runs
Section titled “How it runs”The program runs as you, with no window, in %APPDATA%\Umbra\data\run\<widget id>, with a scrubbed environment (system variables and PATH only: no tokens, no UMBRA_*). It and its descendants run in a job object and are killed on timeout or when Umbra exits. At most 1 MB of stdout and of stderr is kept (truncated tells you). Calls fail while the widget is disabled, and a running command stops when the widget closes.
The user approves every command
Section titled “The user approves every command”The first run shows an Umbra prompt with the exact command line. The approval is saved for this widget and this command, and is void as soon as the command changes or the resolved program file (its path or SHA-256) differs.
- A prompt only appears right after the user clicked the widget (so a button works; a background timer doesn’t), at most every 20 seconds, and not after three refusals. It closes as a refusal after 2 minutes.
- Without an approval, a call rejects with a message you should show (“Approve it in Settings > My widgets”).
- Users approve and revoke each command in Settings → My widgets, and Browse flags widgets that declare commands.
- Automation can’t approve.
umbractl approvalsand the MCPlist_command_approvalsare read-only, and backups never contain approvals.
widgets/script-runner is a worked example.
The user’s own scripts
Section titled “The user’s own scripts”A widget that runs whatever the user wants sets "userScripts": true in permissions. Its card in Settings > My widgets then gets a Your scripts section where the user adds, edits and deletes scripts (PowerShell, Python, Bash or a program; a file, or code typed right there). The widget lists them with umbra.commands(), re-reads them in umbra.onCommandsChange, and runs them by id with umbra.run.
- Umbra builds the command (
powershell ... -File <script>,python <script>,bash <script>,<program>), never a shell line, and the row in Settings shows it. - Saving in Settings is the approval. Only Settings can add, change, delete or approve a script; no
umbractlor MCP tool can, and the widget’s page only sees ids and names. - Scripts are kept outside the widget folder, so updating the widget keeps them. The same timeout, output cap and environment rules apply.
