unsandbox.com

Anonymous remote code, compile, & execution API for humans & machine learning agents.

Docs 📚 View Pricing →
F#
UN CLI
un.fs 20.3 KB · 493 lines
Download ⬇️

Usage

# Run this implementation to execute a Python script
dotnet fsi cli/inception/un.fs test/fib.py test/fib.py

Source Code 📄

// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
//
// This is free public domain software for the public good of a permacomputer hosted
// at permacomputer.com - an always-on computer by the people, for the people. One
// which is durable, easy to repair, and distributed like tap water for machine
// learning intelligence.
//
// The permacomputer is community-owned infrastructure optimized around four values:
//
//   TRUTH    - First principles, math & science, open source code freely distributed
//   FREEDOM  - Voluntary partnerships, freedom from tyranny & corporate control
//   HARMONY  - Minimal waste, self-renewing systems with diverse thriving connections
//   LOVE     - Be yourself without hurting others, cooperation through natural law
//
// This software contributes to that vision by enabling code execution across 42+
// programming languages through a unified interface, accessible to all. Code is
// seeds to sprout on any abandoned technology.
//
// Learn more: https://www.permacomputer.com
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
// software, either in source code form or as a compiled binary, for any purpose,
// commercial or non-commercial, and by any means.
//
// NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
//
// That said, our permacomputer's digital membrane stratum continuously runs unit,
// integration, and functional tests on all of it's own software - with our
// permacomputer monitoring itself, repairing itself, with minimal human in the
// loop guidance. Our agents do their best.
//
// Copyright 2025 TimeHexOn & foxhop & russell@unturf
// https://www.timehexon.com
// https://www.foxhop.net
// https://www.unturf.com/software


// un.fs - Unsandbox CLI Client (F# Implementation)
// Compile: fsharpc un.fs
// Run: mono un.exe [options] <source_file>
// Requires: UNSANDBOX_API_KEY environment variable

open System
open System.IO
open System.Net
open System.Text

let apiBase = "https://api.unsandbox.com"
let blue = "\x1B[34m"
let red = "\x1B[31m"
let green = "\x1B[32m"
let yellow = "\x1B[33m"
let reset = "\x1B[0m"

let extMap =
    Map.ofList [
        (".py", "python"); (".js", "javascript"); (".ts", "typescript")
        (".rb", "ruby"); (".php", "php"); (".pl", "perl"); (".lua", "lua")
        (".sh", "bash"); (".go", "go"); (".rs", "rust"); (".c", "c")
        (".cpp", "cpp"); (".cc", "cpp"); (".cxx", "cpp")
        (".java", "java"); (".kt", "kotlin"); (".cs", "csharp"); (".fs", "fsharp")
        (".hs", "haskell"); (".ml", "ocaml"); (".clj", "clojure"); (".scm", "scheme")
        (".lisp", "commonlisp"); (".erl", "erlang"); (".ex", "elixir"); (".exs", "elixir")
        (".jl", "julia"); (".r", "r"); (".R", "r"); (".cr", "crystal")
        (".d", "d"); (".nim", "nim"); (".zig", "zig"); (".v", "v")
        (".dart", "dart"); (".groovy", "groovy"); (".scala", "scala")
        (".f90", "fortran"); (".f95", "fortran"); (".cob", "cobol")
        (".pro", "prolog"); (".forth", "forth"); (".4th", "forth")
        (".tcl", "tcl"); (".raku", "raku"); (".m", "objc")
    ]

type Args = {
    mutable Command: string option
    mutable SourceFile: string option
    mutable ApiKey: string option
    mutable Network: string option
    mutable Vcpu: int
    Env: ResizeArray<string>
    Files: ResizeArray<string>
    mutable Artifacts: bool
    mutable OutputDir: string option
    mutable SessionList: bool
    mutable SessionShell: string option
    mutable SessionKill: string option
    mutable ServiceList: bool
    mutable ServiceName: string option
    mutable ServicePorts: string option
    mutable ServiceType: string option
    mutable ServiceBootstrap: string option
    mutable ServiceInfo: string option
    mutable ServiceLogs: string option
    mutable ServiceTail: string option
    mutable ServiceSleep: string option
    mutable ServiceWake: string option
    mutable ServiceDestroy: string option
}

let getApiKey (argsKey: string option) =
    let key = match argsKey with | Some k -> k | None -> Environment.GetEnvironmentVariable("UNSANDBOX_API_KEY")
    if String.IsNullOrEmpty(key) then
        eprintfn "%sError: UNSANDBOX_API_KEY not set%s" red reset
        exit 1
    key

let detectLanguage (filename: string) =
    let dotIndex = filename.LastIndexOf('.')
    if dotIndex = -1 then
        failwith "Cannot detect language: no file extension"
    let ext = filename.Substring(dotIndex).ToLower()
    match Map.tryFind ext extMap with
    | Some lang -> lang
    | None -> failwithf "Unsupported file extension: %s" ext

let jsonEscape (s: string) =
    let sb = StringBuilder("\"")
    for c in s do
        match c with
        | '"' -> sb.Append("\\\"") |> ignore
        | '\\' -> sb.Append("\\\\") |> ignore
        | '\n' -> sb.Append("\\n") |> ignore
        | '\r' -> sb.Append("\\r") |> ignore
        | '\t' -> sb.Append("\\t") |> ignore
        | _ -> sb.Append(c) |> ignore
    sb.Append("\"") |> ignore
    sb.ToString()

let rec toJson (obj: obj) =
    match obj with
    | null -> "null"
    | :? string as s -> jsonEscape s
    | :? int as i -> i.ToString()
    | :? float as f -> f.ToString()
    | :? bool as b -> b.ToString().ToLower()
    | :? Map<string, obj> as m ->
        let entries = m |> Map.toSeq |> Seq.map (fun (k, v) -> sprintf "\"%s\":%s" k (toJson v)) |> String.concat ","
        sprintf "{%s}" entries
    | :? ResizeArray<obj> as lst ->
        let items = lst |> Seq.map toJson |> String.concat ","
        sprintf "[%s]" items
    | :? (string * obj) list as lst ->
        let entries = lst |> List.map (fun (k, v) -> sprintf "\"%s\":%s" k (toJson v)) |> String.concat ","
        sprintf "{%s}" entries
    | _ -> jsonEscape (obj.ToString())

let extractJsonValue (json: string) (key: string) =
    let pattern = sprintf "\"%s\":" key
    let startIndex = json.IndexOf(pattern)
    if startIndex = -1 then None
    else
        let mutable idx = startIndex + pattern.Length
        while idx < json.Length && Char.IsWhiteSpace(json.[idx]) do
            idx <- idx + 1

        if json.[idx] = '"' then
            idx <- idx + 1
            let sb = StringBuilder()
            let mutable escaped = false
            let mutable found = false
            let mutable i = idx
            while i < json.Length && not found do
                let c = json.[i]
                if escaped then
                    match c with
                    | 'n' -> sb.Append('\n') |> ignore
                    | 'r' -> sb.Append('\r') |> ignore
                    | 't' -> sb.Append('\t') |> ignore
                    | '"' -> sb.Append('"') |> ignore
                    | '\\' -> sb.Append('\\') |> ignore
                    | _ -> sb.Append(c) |> ignore
                    escaped <- false
                else if c = '\\' then
                    escaped <- true
                else if c = '"' then
                    found <- true
                else
                    sb.Append(c) |> ignore
                i <- i + 1
            Some (sb.ToString())
        else
            let sb = StringBuilder()
            let mutable i = idx
            while i < json.Length && (Char.IsDigit(json.[i]) || json.[i] = '-') do
                sb.Append(json.[i]) |> ignore
                i <- i + 1
            Some (sb.ToString())

let parseJson (json: string) =
    let trimmed = json.Trim()
    if not (trimmed.StartsWith("{")) then Map.empty
    else
        let result = ResizeArray<string * obj>()
        let mutable i = 1

        while i < trimmed.Length do
            while i < trimmed.Length && Char.IsWhiteSpace(trimmed.[i]) do i <- i + 1
            if trimmed.[i] = '}' then i <- trimmed.Length
            elif trimmed.[i] = '"' then
                let keyStart = i + 1
                i <- i + 1
                while i < trimmed.Length && trimmed.[i] <> '"' do
                    if trimmed.[i] = '\\' then i <- i + 1
                    i <- i + 1
                let key = trimmed.Substring(keyStart, i - keyStart).Replace("\\\"", "\"").Replace("\\\\", "\\")
                i <- i + 1

                while i < trimmed.Length && (Char.IsWhiteSpace(trimmed.[i]) || trimmed.[i] = ':') do i <- i + 1

                let value = extractJsonValue trimmed key
                match value with
                | Some v -> result.Add((key, box v))
                | None -> ()

                while i < trimmed.Length && (Char.IsWhiteSpace(trimmed.[i]) || trimmed.[i] = ',' || trimmed.[i] = '"' || Char.IsLetterOrDigit(trimmed.[i]) || trimmed.[i] = '\\') do i <- i + 1
            else
                i <- i + 1

        result |> Seq.map (fun (k, v) -> k, v) |> Map.ofSeq

let apiRequest (endpoint: string) (method: string) (data: (string * obj) list option) (apiKey: string) =
    ServicePointManager.SecurityProtocol <- SecurityProtocolType.Tls12 ||| SecurityProtocolType.Tls11 ||| SecurityProtocolType.Tls

    let request = WebRequest.Create(apiBase + endpoint) :?> HttpWebRequest
    request.Method <- method
    request.ContentType <- "application/json"
    request.Headers.Add("Authorization", sprintf "Bearer %s" apiKey)
    request.Timeout <- 300000

    match data with
    | Some d ->
        let json = toJson (box d)
        let bytes = Encoding.UTF8.GetBytes(json)
        request.ContentLength <- int64 bytes.Length
        use stream = request.GetRequestStream()
        stream.Write(bytes, 0, bytes.Length)
    | None -> ()

    try
        use response = request.GetResponse() :?> HttpWebResponse
        if response.StatusCode <> HttpStatusCode.OK then
            failwithf "HTTP %A" response.StatusCode
        use reader = new StreamReader(response.GetResponseStream())
        let responseText = reader.ReadToEnd()
        parseJson responseText
    with
    | :? WebException as ex ->
        let errorMsg =
            if ex.Response <> null then
                use reader = new StreamReader(ex.Response.GetResponseStream())
                reader.ReadToEnd()
            else
                ex.Message
        failwithf "HTTP error - %s" errorMsg

let cmdExecute (args: Args) =
    let apiKey = getApiKey args.ApiKey
    let code = File.ReadAllText(args.SourceFile.Value)
    let language = detectLanguage args.SourceFile.Value

    let mutable payload = [("language", box language); ("code", box code)]

    if args.Env.Count > 0 then
        let envVars = args.Env |> Seq.choose (fun e ->
            let parts = e.Split([|'='|], 2)
            if parts.Length = 2 then Some (parts.[0], box parts.[1]) else None
        ) |> Seq.toList
        if not (List.isEmpty envVars) then
            payload <- payload @ [("env", box envVars)]

    if args.Files.Count > 0 then
        let inputFiles = args.Files |> Seq.map (fun filepath ->
            let content = File.ReadAllBytes(filepath)
            [("filename", box (Path.GetFileName(filepath))); ("content_base64", box (Convert.ToBase64String(content)))]
        ) |> Seq.toList
        payload <- payload @ [("input_files", box inputFiles)]

    if args.Artifacts then
        payload <- payload @ [("return_artifacts", box true)]
    if args.Network.IsSome then
        payload <- payload @ [("network", box args.Network.Value)]
    if args.Vcpu > 0 then
        payload <- payload @ [("vcpu", box args.Vcpu)]

    let result = apiRequest "/execute" "POST" (Some payload) apiKey

    match result.TryFind "stdout" with
    | Some stdout when not (String.IsNullOrEmpty(stdout.ToString())) ->
        printf "%s%s%s" blue (stdout.ToString()) reset
    | _ -> ()

    match result.TryFind "stderr" with
    | Some stderr when not (String.IsNullOrEmpty(stderr.ToString())) ->
        eprintf "%s%s%s" red (stderr.ToString()) reset
    | _ -> ()

    if args.Artifacts && result.ContainsKey("artifacts") then
        let outDir = match args.OutputDir with | Some d -> d | None -> "."
        Directory.CreateDirectory(outDir) |> ignore
        eprintfn "%sSaved artifacts to %s%s" green outDir reset

    let exitCode = match result.TryFind "exit_code" with | Some ec -> int (ec.ToString()) | None -> 0
    exit exitCode

let cmdSession (args: Args) =
    let apiKey = getApiKey args.ApiKey

    if args.SessionList then
        let result = apiRequest "/sessions" "GET" None apiKey
        printfn "%-40s %-10s %-10s %s" "ID" "Shell" "Status" "Created"
        printfn "No sessions (list parsing not implemented)"
    elif args.SessionKill.IsSome then
        let result = apiRequest (sprintf "/sessions/%s" args.SessionKill.Value) "DELETE" None apiKey
        printfn "%sSession terminated: %s%s" green args.SessionKill.Value reset
    else
        let mutable payload = [("shell", box (match args.SessionShell with | Some s -> s | None -> "bash"))]
        if args.Network.IsSome then
            payload <- payload @ [("network", box args.Network.Value)]
        if args.Vcpu > 0 then
            payload <- payload @ [("vcpu", box args.Vcpu)]

        printfn "%sCreating session...%s" yellow reset
        let result = apiRequest "/sessions" "POST" (Some payload) apiKey
        match result.TryFind "id" with
        | Some id -> printfn "%sSession created: %s%s" green (id.ToString()) reset
        | None -> printfn "%sSession created%s" green reset
        printfn "%s(Interactive sessions require WebSocket - use un2 for full support)%s" yellow reset

let cmdService (args: Args) =
    let apiKey = getApiKey args.ApiKey

    if args.ServiceList then
        let result = apiRequest "/services" "GET" None apiKey
        printfn "%-20s %-15s %-10s %-15s %s" "ID" "Name" "Status" "Ports" "Domains"
        printfn "No services (list parsing not implemented)"
    elif args.ServiceInfo.IsSome then
        let result = apiRequest (sprintf "/services/%s" args.ServiceInfo.Value) "GET" None apiKey
        printfn "%s" (toJson (box result))
    elif args.ServiceLogs.IsSome then
        let result = apiRequest (sprintf "/services/%s/logs" args.ServiceLogs.Value) "GET" None apiKey
        match result.TryFind "logs" with
        | Some logs -> printfn "%s" (logs.ToString())
        | None -> ()
    elif args.ServiceTail.IsSome then
        let result = apiRequest (sprintf "/services/%s/logs?lines=9000" args.ServiceTail.Value) "GET" None apiKey
        match result.TryFind "logs" with
        | Some logs -> printfn "%s" (logs.ToString())
        | None -> ()
    elif args.ServiceSleep.IsSome then
        let result = apiRequest (sprintf "/services/%s/sleep" args.ServiceSleep.Value) "POST" None apiKey
        printfn "%sService sleeping: %s%s" green args.ServiceSleep.Value reset
    elif args.ServiceWake.IsSome then
        let result = apiRequest (sprintf "/services/%s/wake" args.ServiceWake.Value) "POST" None apiKey
        printfn "%sService waking: %s%s" green args.ServiceWake.Value reset
    elif args.ServiceDestroy.IsSome then
        let result = apiRequest (sprintf "/services/%s" args.ServiceDestroy.Value) "DELETE" None apiKey
        printfn "%sService destroyed: %s%s" green args.ServiceDestroy.Value reset
    elif args.ServiceName.IsSome then
        let mutable payload = [("name", box args.ServiceName.Value)]
        if args.ServicePorts.IsSome then
            let ports = args.ServicePorts.Value.Split(',') |> Array.map (fun p -> box (int (p.Trim())))
            payload <- payload @ [("ports", box ports)]
        if args.ServiceType.IsSome then
            payload <- payload @ [("service_type", box args.ServiceType.Value)]
        if args.ServiceBootstrap.IsSome then
            payload <- payload @ [("bootstrap", box args.ServiceBootstrap.Value)]
        if args.Network.IsSome then
            payload <- payload @ [("network", box args.Network.Value)]
        if args.Vcpu > 0 then
            payload <- payload @ [("vcpu", box args.Vcpu)]

        let result = apiRequest "/services" "POST" (Some payload) apiKey
        match result.TryFind "id" with
        | Some id -> printfn "%sService created: %s%s" green (id.ToString()) reset
        | None -> printfn "%sService created%s" green reset
        match result.TryFind "name" with
        | Some name -> printfn "Name: %s" (name.ToString())
        | None -> ()
        match result.TryFind "url" with
        | Some url -> printfn "URL: %s" (url.ToString())
        | None -> ()
    else
        eprintfn "%sError: Specify --name to create a service, or use --list, --info, etc.%s" red reset
        exit 1

let parseArgs (argv: string[]) =
    let args = {
        Command = None
        SourceFile = None
        ApiKey = None
        Network = None
        Vcpu = 0
        Env = ResizeArray<string>()
        Files = ResizeArray<string>()
        Artifacts = false
        OutputDir = None
        SessionList = false
        SessionShell = None
        SessionKill = None
        ServiceList = false
        ServiceName = None
        ServicePorts = None
        ServiceType = None
        ServiceBootstrap = None
        ServiceInfo = None
        ServiceLogs = None
        ServiceTail = None
        ServiceSleep = None
        ServiceWake = None
        ServiceDestroy = None
    }

    let mutable i = 0
    while i < argv.Length do
        match argv.[i] with
        | "session" -> args.Command <- Some "session"
        | "service" -> args.Command <- Some "service"
        | "-k" | "--api-key" -> i <- i + 1; args.ApiKey <- Some argv.[i]
        | "-n" | "--network" -> i <- i + 1; args.Network <- Some argv.[i]
        | "-v" | "--vcpu" -> i <- i + 1; args.Vcpu <- int argv.[i]
        | "-e" | "--env" -> i <- i + 1; args.Env.Add(argv.[i])
        | "-f" | "--files" -> i <- i + 1; args.Files.Add(argv.[i])
        | "-a" | "--artifacts" -> args.Artifacts <- true
        | "-o" | "--output-dir" -> i <- i + 1; args.OutputDir <- Some argv.[i]
        | "-l" | "--list" ->
            match args.Command with
            | Some "session" -> args.SessionList <- true
            | Some "service" -> args.ServiceList <- true
            | _ -> ()
        | "-s" | "--shell" -> i <- i + 1; args.SessionShell <- Some argv.[i]
        | "--kill" -> i <- i + 1; args.SessionKill <- Some argv.[i]
        | "--name" -> i <- i + 1; args.ServiceName <- Some argv.[i]
        | "--ports" -> i <- i + 1; args.ServicePorts <- Some argv.[i]
        | "--type" -> i <- i + 1; args.ServiceType <- Some argv.[i]
        | "--bootstrap" -> i <- i + 1; args.ServiceBootstrap <- Some argv.[i]
        | "--info" -> i <- i + 1; args.ServiceInfo <- Some argv.[i]
        | "--logs" -> i <- i + 1; args.ServiceLogs <- Some argv.[i]
        | "--tail" -> i <- i + 1; args.ServiceTail <- Some argv.[i]
        | "--sleep" -> i <- i + 1; args.ServiceSleep <- Some argv.[i]
        | "--wake" -> i <- i + 1; args.ServiceWake <- Some argv.[i]
        | "--destroy" -> i <- i + 1; args.ServiceDestroy <- Some argv.[i]
        | arg when not (arg.StartsWith("-")) -> args.SourceFile <- Some arg
        | _ -> ()
        i <- i + 1

    args

let printHelp () =
    printfn "Usage: un [options] <source_file>"
    printfn "       un session [options]"
    printfn "       un service [options]"
    printfn ""
    printfn "Execute options:"
    printfn "  -e KEY=VALUE      Set environment variable"
    printfn "  -f FILE           Add input file"
    printfn "  -a                Return artifacts"
    printfn "  -o DIR            Output directory for artifacts"
    printfn "  -n MODE           Network mode (zerotrust/semitrusted)"
    printfn "  -v N              vCPU count (1-8)"
    printfn "  -k KEY            API key"
    printfn ""
    printfn "Session options:"
    printfn "  --list            List active sessions"
    printfn "  --shell NAME      Shell/REPL to use"
    printfn "  --kill ID         Terminate session"
    printfn ""
    printfn "Service options:"
    printfn "  --list            List services"
    printfn "  --name NAME       Service name"
    printfn "  --ports PORTS     Comma-separated ports"
    printfn "  --type TYPE       Service type (minecraft/mumble/teamspeak/source/tcp/udp)"
    printfn "  --bootstrap CMD   Bootstrap command"
    printfn "  --info ID         Get service details"
    printfn "  --logs ID         Get all logs"
    printfn "  --tail ID         Get last 9000 lines"
    printfn "  --sleep ID        Freeze service"
    printfn "  --wake ID         Unfreeze service"
    printfn "  --destroy ID      Destroy service"

[<EntryPoint>]
let main argv =
    try
        let args = parseArgs argv

        match args.Command with
        | Some "session" -> cmdSession args; 0
        | Some "service" -> cmdService args; 0
        | _ ->
            match args.SourceFile with
            | Some _ -> cmdExecute args; 0
            | None -> printHelp(); 1
    with ex ->
        eprintfn "%sError: %s%s" red ex.Message reset
        1

License

PUBLIC DOMAIN - NO LICENSE, NO WARRANTY

This is free public domain software for the public good of a permacomputer hosted
at permacomputer.com - an always-on computer by the people, for the people. One
that is durable, easy to repair, and distributed like tap water for machine
learning intelligence.

The permacomputer is community-owned infrastructure optimized around four values:

  TRUTH    - First principles, math & science, open source code freely distributed
  FREEDOM  - Voluntary partnerships, freedom from tyranny & corporate control
  HARMONY  - Minimal waste, self-renewing systems with diverse thriving connections
  LOVE     - Be yourself without hurting others, cooperation through natural law

This software contributes to that vision by enabling code execution across all 42
programming languages through a unified interface, accessible to everyone. Code is
seeds to sprout on any abandoned technology.

Learn more: https://www.permacomputer.com

Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.

NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.

That said, our permacomputer's digital membrane stratum continuously runs unit,
integration, and functional tests on all its own software - with our permacomputer
monitoring itself, repairing itself, with minimal human guidance in the loop.
Our agents do their best.

Copyright 2025 TimeHexOn & foxhop & russell@unturf
https://www.timehexon.com
https://www.foxhop.net
https://www.unturf.com/software