External commands (window.Electron.externalCommands)
Runs an allowlisted external OS executable — ffmpeg, git, a bundled CLI tool — and captures its stdout/stderr. Source: src/main/external-commands.ts, channel prefix externalCommands:*. Opt-in per alias. The renderer only ever names a config alias, never a real path or command string — see Processes configuration for the processes.externalCommands.<alias> shape and the externalCommands vs utilityProcesses mental model. With no aliases configured, every call rejects NOT_CONFIGURED, so this module is safe to leave enabled by default.
Commands run via spawn(..., { shell: false }) — a direct process, never through a shell string, so there's no && rm -rf-style shell-injection surface even from a hostile argument.
Methods
| Method | Returns | Notes |
|---|---|---|
run(alias: string, options?: ExternalCommandRunOptions) |
Promise<ExternalCommandResult> |
Runs the command and resolves once it exits. A failed spawn is reported via result.error, not a rejection. |
start(alias: string, options?: ExternalCommandRunOptions) |
Promise<{ id: string; pid: number | null }> |
Starts the command and streams output/exit via events instead of awaiting completion. |
kill(id: string) |
Promise<boolean> |
false when id is unknown. |
onOutput(callback: (event: { id, stream: 'stdout' | 'stderr', text: string }) => void) |
() => void (unsubscribe) |
Only fires for commands started with start(). |
onExit(callback: (event: { id: string; result: ExternalCommandResult }) => void) |
() => void (unsubscribe) |
ExternalCommandRunOptions
| Field | Type | Notes |
|---|---|---|
args |
string[] |
Passed as a real argv array, never a shell string. Checked against the config's allowedArgs allowlist when set. Max 256 args, 8192 chars each. |
stdin |
string | number[] | ArrayBuffer | Uint8Array |
Optional stdin payload — use a byte array/Uint8Array for binary data. |
stdinBase64 |
string |
Alternative binary stdin, base64-encoded. Ignored when stdin is also given. |
timeoutMs |
number |
Overrides the config's timeoutMs for this call. 0 disables the timeout. |
ExternalCommandResult
{ id, pid, exitCode, signal, stdout, stderr, stdoutTruncated, stderrTruncated, timedOut, error? }
A timed-out command receives SIGTERM, then SIGKILL after a 2-second grace period if it's still alive; timedOut is set to true on the result either way.
Example
const result = await window.Electron.externalCommands.run('ffmpeg', {
args: ['-i', 'input.mp4', 'output.webm'],
});
if (result.exitCode !== 0) console.error(result.stderr);
// Streamed variant
const { id } = await window.Electron.externalCommands.start('ffmpeg', { args: [...] });
const unsubOut = window.Electron.externalCommands.onOutput((e) => {
if (e.id === id) console.log(e.stream, e.text);
});
const unsubExit = window.Electron.externalCommands.onExit((e) => {
if (e.id === id) console.log('exit code', e.result.exitCode);
});
.safe
Partial. run, start, kill are wrapped — window.Electron.safe.externalCommands.* resolves { ok: true, data } / { ok: false, error } instead of throwing. onOutput()/onExit() are the same functions on both surfaces (event subscriptions, never throw).