Terminal
One-shot commands as argv, interactive ptys, and why a non-zero exit code is a success.
Nothing here is gated on the input lease. A shell is a session of its own, and two people holding separate terminals is normal.
exec
const result = await machine.terminal.exec(["uname", "-sr"]);
result.stdout;
result.stderr;
result.exit_code;result = machine.terminal.exec(["uname", "-sr"])
result["stdout"]
result["stderr"]
result["exit_code"]exec takes argv, not a string. There is no shell and no tokenizing, so an argument
containing a space, a quote or a semicolon is one argument:
await machine.terminal.exec(["git", "commit", "-m", "a message; with a semicolon"]);machine.terminal.exec(["git", "commit", "-m", "a message; with a semicolon"])That is the whole defence against command injection, and it works only because the API refuses to split a string for you.
timeout?number
TypeScript `timeoutMs` (milliseconds) / Python `timeout` (seconds). How long the command may run.
numberuser?"agent" | "root"
The two users the image ships. `agent` owns the desktop.
"agent" | "root""agent"cwd?string
Working directory.
stringstdin?string
Fed to the process.
stringA non-zero exit code is a successful call
Only a failure to run the command at all raises. A command that runs and returns 1 comes back
normally with exit_code: 1 — the code is data, not a fault.
const found = await machine.terminal.exec(["grep", "-q", "needle", "/tmp/haystack"]);
if (found.exit_code === 1) {
// not an error: grep says "no match"
}found = machine.terminal.exec(["grep", "-q", "needle", "/tmp/haystack"])
if found["exit_code"] == 1:
# not an error: grep says "no match"
...Worth holding onto in an agent loop. A model that sees an exception on every failed grep learns to avoid grep; a model that sees an exit code learns to read it.
Asking for a shell by name
exec deliberately does not tokenize, so pipes, globs and redirects need a shell, spelled out:
await machine.terminal.execShell("cd /tmp/site && python3 -m http.server 3000 &");
// exactly equivalent to:
await machine.terminal.exec(["/bin/sh", "-lc", "cd /tmp/site && python3 -m http.server 3000 &"]);machine.terminal.exec_shell("cd /tmp/site && python3 -m http.server 3000 &")
# exactly equivalent to:
machine.terminal.exec(["/bin/sh", "-lc", "cd /tmp/site && python3 -m http.server 3000 &"])The point is that a reader can see you asked for it. Guessing where a command ends is how injection happens.
Interactive terminals
const term = await machine.terminal.open({ cols: 120, rows: 32 });
const open = await machine.terminal.list();
await machine.terminal.kill(term.id);term = machine.terminal.open(cols=120, rows=32)
open_terms = machine.terminal.list()
machine.terminal.kill(term["id"])open takes command, cwd, user, cols and rows. To attach and exchange bytes, see
streams.
Killing a pty is also what closing an attached connection does.