Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Console
open Axial.FileSystem
open Axial.PlatformService
open Axial.Process

Open Axial.Process.DSL for command-line-shaped authoring:

Show page setup
// Setup for the checked examples on this page.
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Console
open Axial.FileSystem
open Axial.PlatformService
open Axial.Process
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
    if actual <> expected then failwithf "Expected %A but got %A" expected actual
Verified
// The examples on this page start real processes with the live service.
let host : ProcessHostEnvironment =
    { Process = Process.live Clock.live FileSystem.live Console.live
      Console = Console.live }

let stdoutOf (workflow: Flow<ProcessHostEnvironment, ProcessError, ProcessResult>) : string =
    match Flow.run host workflow with
    | Exit.Success result -> result.StdOut
    | Exit.Failure cause -> failwithf "%A" cause
Verified
open Axial.Process.DSL

let connect (deviceId: string) (workspace: string) =
    cmd $"printf %%s {deviceId}"
    |> cwd workspace
    |> env "DEVICE_MODE" "service"
    |> timeout (TimeSpan.FromSeconds 30.0)
    |> capture
Verified
stdoutOf (connect "sensor 7; rm -rf /" (IO.Path.GetTempPath())) |> shouldEqual "sensor 7; rm -rf /"

Interpolation holes remain individual native arguments. They are not concatenated into shell source. Mark sensitive values explicitly:

Verified
let authenticate (token: string) =
    cmd $"printf %%s {secret token}"
Verified
authenticate "s3cret" |> Process.render |> shouldEqual "printf %s ***"
stdoutOf (authenticate "s3cret" |> capture) |> shouldEqual "s3cret"

The real value reaches the process; renders, plans, and errors show ***.

Use bash, sh, or pwsh when shell syntax is required. Interpolated values are passed out of band as positional arguments:

Verified
let shout (value: string) =
    bash $"printf '%%s' {value} | tr '[:lower:]' '[:upper:]'"
    |> capture
Verified
stdoutOf (shout "it's $HOME") |> shouldEqual "IT'S $HOME"

capture, console, and stream are lazy: they turn the specification into a Flow or FlowStream. Capture selects complete stdout and stderr capture. Console forwards both channels while retaining structured completion data. Stream yields ProcessEvent values and must be consumed by a Flow before a host can run it.

run is the host execution verb: it starts a process Flow with live services and returns a command-line exit code. This keeps it distinct from Flow.run, which runs an application Flow with its explicit environment.

Verified
cmd $"dotnet --version"
|> console
|> run
|> shouldEqual 0

Use runWith when an application already owns its environment. The environment must provide IConsole for reporting a typed process failure; it can carry any additional application services. The Flow's own type states whether it also needs IProcess.

Verified
type AppEnvironment =
    { Process: IProcess
      Console: IConsole
      Region: string }
    interface IHasProcess with member this.Process = this.Process
    interface IHasConsole with member this.Console = this.Console

let appEnvironment =
    { Process = Process.live Clock.live FileSystem.live Console.live
      Console = Console.live
      Region = "eu" }
Verified
let exit =
    cmd $"dotnet --version"
    |> console
    |> runWith appEnvironment

exit |> shouldEqual 0

For application composition, use Process.toFlow (or the DSL's capture or console) and start the enclosing workflow with Flow.run.