I made a proposed overhaul of the basic-cli Cmd API.
The old output! function required people to write substantial scaffolding code just to see human readable stderr of a command in case it failed. With the new exec_output!, all the info you typically want is included in the error. This works well with just ?, for convenient scripting.
output! used to return:
Output : { status : Result I32 InternalIOErr.IOErr, stdout : List U8, stderr : List U8}
exec_output! now returns Ok({stdout_utf8 : Str, stderr_utf8_lossy : Str}) if the exit code (=status) is 0, if not, the exit code is included in the error.
exec_output_bytes! was added to get stdout and stderr in bytes, similar to the old output!, for fine grained control or max perf.
exec! got nicer errors.
exec_cmd! for when you want to execute your command with specific env vars and forward stdout, stderr, and stdin directly to your terminal.
status! was renamed to exec_exit_code! and given nicer errors as well.
What do you think?
Comparing to the old way it looks better! I had not used the exec yet, so it's hard for me to evaluate this change pragmatically.
I'll make a before and after example to make it easier to evaluate
Cmd.exec!("date", ["-X"])
date: invalid option -- 'X'
Try 'date --help' for more information.
Program exited with error:
❌ CmdStatusErr (Other "Non-zero exit code: 1")
date: invalid option -- 'X'
Try 'date --help' for more information.
Program exited with error:
❌ ExecFailed {command: "date -X", exit_code: 1}
output = run_cmd_w_output!("date", ["-X"])?
# You need this whole function to get usable output on error
run_cmd_w_output! : Str, List Str => Result Str [BadCmdOutput(Str)]_
run_cmd_w_output! = |cmd_str, args|
cmd_out =
Cmd.new(cmd_str)
|> Cmd.args(args)
|> Cmd.output!()
stdout_utf8 = Str.from_utf8_lossy(cmd_out.stdout)
when cmd_out.status is
Ok(0) ->
Ok(stdout_utf8)
_ ->
stderr_utf8 = Str.from_utf8_lossy(cmd_out.stderr)
err_data =
"""
Cmd `${cmd_str} ${Str.join_with(args, " ")}` failed:
- status: ${Inspect.to_str(cmd_out.status)}
- stdout: ${stdout_utf8}
- stderr: ${stderr_utf8}
"""
Err(BadCmdOutput(err_data))
Program exited with error:
❌ BadCmdOutput "Cmd `date -X` failed:
- status: (Err (Other "Non-zero exit code: 1"))
- stdout:
- stderr: date: invalid option -- 'X'
Try 'date --help' for more information.
"
output =
Cmd.new("date")
|> Cmd.arg("-X")
|> Cmd.exec_output!()?
Program exited with error:
❌ NonZeroExitCode {command: "{ cmd: date, args: -X }", exit_code: 1, stderr_utf8_lossy: "date: invalid option -- 'X'
Try 'date --help' for more information.
", stdout_utf8_lossy: ""}
exit_code =
Cmd.new("dat")
|> Cmd.arg("-X")
|> Cmd.status!()?
Program exited with error:
❌ CmdStatusErr NotFound
exit_code =
Cmd.new("dat")
|> Cmd.arg("-X")
|> Cmd.exec_exit_code!()?
Program exited with error:
❌ FailedToGetExitCode {command: "{ cmd: dat, args: -X }", err: NotFound}
The Before/After look like a nice improvement to me. :smiley:
Last updated: Jun 16 2026 at 16:19 UTC