unsandbox.com

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

Docs 📚 View Pricing →
Lua
UN CLI
un.lua 19.6 KB · 589 lines
Download ⬇️

Usage

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

Source Code 📄

#!/usr/bin/env lua
-- 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.lua - Unsandbox CLI Client (Lua Implementation)
--
-- Full-featured CLI matching un.c capabilities:
-- - Execute code with env vars, input files, artifacts
-- - Interactive sessions with shell/REPL support
-- - Persistent services with domains and ports
--
-- Usage:
--   un.lua [options] <source_file>
--   un.lua session [options]
--   un.lua service [options]
--
-- Requires: UNSANDBOX_API_KEY environment variable
-- Note: Uses curl for HTTP requests (requires curl to be installed)

local json = require("cjson")

local API_BASE = "https://api.unsandbox.com"
local BLUE = "\27[34m"
local RED = "\27[31m"
local GREEN = "\27[32m"
local YELLOW = "\27[33m"
local RESET = "\27[0m"

local EXT_MAP = {
    [".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"
}

local function get_api_key(args_key)
    local key = args_key or os.getenv("UNSANDBOX_API_KEY")
    if not key then
        io.stderr:write(RED .. "Error: UNSANDBOX_API_KEY not set" .. RESET .. "\n")
        os.exit(1)
    end
    return key
end

local function detect_language(filename)
    local ext = filename:match("%.([^.]+)$")
    if ext then
        local lang = EXT_MAP["." .. ext:lower()]
        if lang then return lang end
    end

    local file = io.open(filename, "r")
    if file then
        local first_line = file:read("*line")
        file:close()
        if first_line and first_line:match("^#!") then
            if first_line:match("python") then return "python" end
            if first_line:match("node") then return "javascript" end
            if first_line:match("ruby") then return "ruby" end
            if first_line:match("perl") then return "perl" end
            if first_line:match("bash") or first_line:match("/sh") then return "bash" end
            if first_line:match("lua") then return "lua" end
            if first_line:match("php") then return "php" end
        end
    end

    io.stderr:write(RED .. "Error: Cannot detect language for " .. filename .. RESET .. "\n")
    os.exit(1)
end

local function shell_escape(str)
    return "'" .. str:gsub("'", "'\\''") .. "'"
end

local function api_request(endpoint, method, data, api_key)
    method = method or "GET"
    local url = API_BASE .. endpoint
    local tmpfile = os.tmpname()

    local cmd = "curl -s -X " .. method .. " " .. shell_escape(url) ..
                " -H 'Authorization: Bearer " .. api_key .. "'" ..
                " -H 'Content-Type: application/json'"

    if data then
        local payload = json.encode(data)
        local data_file = os.tmpname()
        local f = io.open(data_file, "w")
        f:write(payload)
        f:close()
        cmd = cmd .. " -d @" .. shell_escape(data_file)
    end

    cmd = cmd .. " -w '\\n%{http_code}' -o " .. shell_escape(tmpfile)

    local handle = io.popen(cmd)
    local http_code = handle:read("*a"):match("(%d+)$")
    handle:close()

    local file = io.open(tmpfile, "r")
    local response = file:read("*all")
    file:close()
    os.remove(tmpfile)

    if data then
        os.remove(data_file)
    end

    if not http_code or tonumber(http_code) < 200 or tonumber(http_code) >= 300 then
        io.stderr:write(RED .. "Error: HTTP " .. (http_code or "000") .. " - " .. response .. RESET .. "\n")
        os.exit(1)
    end

    return json.decode(response)
end

local function read_file(filename)
    local file, err = io.open(filename, "rb")
    if not file then
        io.stderr:write(RED .. "Error: File not found: " .. filename .. RESET .. "\n")
        os.exit(1)
    end
    local content = file:read("*all")
    file:close()
    return content
end

local function base64_encode(data)
    local b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    return ((data:gsub('.', function(x)
        local r,b='',x:byte()
        for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
        return r;
    end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
        if (#x < 6) then return '' end
        local c=0
        for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
        return b64:sub(c+1,c+1)
    end)..({ '', '==', '=' })[#data%3+1])
end

local function base64_decode(data)
    local b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    data = string.gsub(data, '[^'..b64..'=]', '')
    return (data:gsub('.', function(x)
        if (x == '=') then return '' end
        local r,f='',(b64:find(x)-1)
        for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
        return r;
    end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
        if (#x ~= 8) then return '' end
        local c=0
        for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
        return string.char(c)
    end))
end

local function cmd_execute(options)
    local api_key = get_api_key(options.api_key)
    local code = read_file(options.source_file)
    local language = detect_language(options.source_file)

    local payload = { language = language, code = code }

    if options.env and #options.env > 0 then
        local env_vars = {}
        for _, e in ipairs(options.env) do
            local k, v = e:match("^([^=]+)=(.*)$")
            if k and v then
                env_vars[k] = v
            end
        end
        if next(env_vars) then
            payload.env = env_vars
        end
    end

    if options.files and #options.files > 0 then
        local input_files = {}
        for _, filepath in ipairs(options.files) do
            local content = read_file(filepath)
            table.insert(input_files, {
                filename = filepath:match("([^/]+)$"),
                content_base64 = base64_encode(content)
            })
        end
        payload.input_files = input_files
    end

    if options.artifacts then payload.return_artifacts = true end
    if options.network then payload.network = options.network end
    if options.vcpu then payload.vcpu = options.vcpu end

    local result = api_request("/execute", "POST", payload, api_key)

    if result.stdout then
        io.write(BLUE .. result.stdout .. RESET)
    end
    if result.stderr then
        io.stderr:write(RED .. result.stderr .. RESET)
    end

    if options.artifacts and result.artifacts then
        local out_dir = options.output_dir or "."
        os.execute("mkdir -p " .. shell_escape(out_dir))
        for _, artifact in ipairs(result.artifacts) do
            local filename = artifact.filename or "artifact"
            local content = base64_decode(artifact.content_base64)
            local filepath = out_dir .. "/" .. filename
            local file = io.open(filepath, "wb")
            file:write(content)
            file:close()
            os.execute("chmod 755 " .. shell_escape(filepath))
            io.stderr:write(GREEN .. "Saved: " .. filepath .. RESET .. "\n")
        end
    end

    os.exit(result.exit_code or 0)
end

local function cmd_session(options)
    local api_key = get_api_key(options.api_key)

    if options.list then
        local result = api_request("/sessions", "GET", nil, api_key)
        local sessions = result.sessions or {}
        if #sessions == 0 then
            print("No active sessions")
        else
            print(string.format("%-40s %-10s %-10s %s", "ID", "Shell", "Status", "Created"))
            for _, s in ipairs(sessions) do
                print(string.format("%-40s %-10s %-10s %s",
                    s.id or "N/A", s.shell or "N/A",
                    s.status or "N/A", s.created_at or "N/A"))
            end
        end
        return
    end

    if options.kill then
        api_request("/sessions/" .. options.kill, "DELETE", nil, api_key)
        print(GREEN .. "Session terminated: " .. options.kill .. RESET)
        return
    end

    if options.attach then
        print(YELLOW .. "Attaching to session " .. options.attach .. "..." .. RESET)
        print(YELLOW .. "(Interactive sessions require WebSocket - use un2 for full support)" .. RESET)
        return
    end

    local payload = { shell = options.shell or "bash" }
    if options.network then payload.network = options.network end
    if options.vcpu then payload.vcpu = options.vcpu end
    if options.tmux then payload.persistence = "tmux" end
    if options.screen then payload.persistence = "screen" end
    if options.audit then payload.audit = true end

    print(YELLOW .. "Creating session..." .. RESET)
    local result = api_request("/sessions", "POST", payload, api_key)
    print(GREEN .. "Session created: " .. (result.id or "N/A") .. RESET)
    print(YELLOW .. "(Interactive sessions require WebSocket - use un2 for full support)" .. RESET)
end

local function cmd_service(options)
    local api_key = get_api_key(options.api_key)

    if options.list then
        local result = api_request("/services", "GET", nil, api_key)
        local services = result.services or {}
        if #services == 0 then
            print("No services")
        else
            print(string.format("%-20s %-15s %-10s %-15s %s", "ID", "Name", "Status", "Ports", "Domains"))
            for _, s in ipairs(services) do
                local ports = table.concat(s.ports or {}, ",")
                local domains = table.concat(s.domains or {}, ",")
                print(string.format("%-20s %-15s %-10s %-15s %s",
                    s.id or "N/A", s.name or "N/A",
                    s.status or "N/A", ports, domains))
            end
        end
        return
    end

    if options.info then
        local result = api_request("/services/" .. options.info, "GET", nil, api_key)
        print(json.encode(result))
        return
    end

    if options.logs then
        local result = api_request("/services/" .. options.logs .. "/logs", "GET", nil, api_key)
        print(result.logs or "")
        return
    end

    if options.tail then
        local result = api_request("/services/" .. options.tail .. "/logs?lines=9000", "GET", nil, api_key)
        print(result.logs or "")
        return
    end

    if options.sleep then
        api_request("/services/" .. options.sleep .. "/sleep", "POST", nil, api_key)
        print(GREEN .. "Service sleeping: " .. options.sleep .. RESET)
        return
    end

    if options.wake then
        api_request("/services/" .. options.wake .. "/wake", "POST", nil, api_key)
        print(GREEN .. "Service waking: " .. options.wake .. RESET)
        return
    end

    if options.destroy then
        api_request("/services/" .. options.destroy, "DELETE", nil, api_key)
        print(GREEN .. "Service destroyed: " .. options.destroy .. RESET)
        return
    end

    if options.execute then
        local payload = { command = options.command }
        local result = api_request("/services/" .. options.execute .. "/execute", "POST", payload, api_key)
        if result.stdout then io.write(BLUE .. result.stdout .. RESET) end
        if result.stderr then io.stderr:write(RED .. result.stderr .. RESET) end
        return
    end

    if options.name then
        local payload = { name = options.name }
        if options.ports then
            local ports = {}
            for p in options.ports:gmatch("[^,]+") do
                table.insert(ports, tonumber(p))
            end
            payload.ports = ports
        end
        if options.domains then
            local domains = {}
            for d in options.domains:gmatch("[^,]+") do
                table.insert(domains, d)
            end
            payload.domains = domains
        end
        if options.type then
            payload.service_type = options.type
        end
        if options.bootstrap then
            local file = io.open(options.bootstrap, "r")
            if file then
                payload.bootstrap = file:read("*all")
                file:close()
            else
                payload.bootstrap = options.bootstrap
            end
        end
        if options.network then payload.network = options.network end
        if options.vcpu then payload.vcpu = options.vcpu end

        local result = api_request("/services", "POST", payload, api_key)
        print(GREEN .. "Service created: " .. (result.id or "N/A") .. RESET)
        print("Name: " .. (result.name or "N/A"))
        if result.url then print("URL: " .. result.url) end
        return
    end

    io.stderr:write(RED .. "Error: Specify --name to create a service, or use --list, --info, etc." .. RESET .. "\n")
    os.exit(1)
end

local function main()
    local options = {
        command = nil,
        source_file = nil,
        env = {},
        files = {},
        artifacts = false,
        output_dir = nil,
        network = nil,
        vcpu = nil,
        api_key = nil,
        shell = nil,
        list = false,
        attach = nil,
        kill = nil,
        audit = false,
        tmux = false,
        screen = false,
        name = nil,
        ports = nil,
        domains = nil,
        type = nil,
        bootstrap = nil,
        info = nil,
        logs = nil,
        tail = nil,
        sleep = nil,
        wake = nil,
        destroy = nil,
        execute = nil,
        command = nil
    }

    local i = 1
    while i <= #arg do
        local a = arg[i]

        if a == "session" or a == "service" then
            options.command = a
        elseif a == "-e" then
            i = i + 1
            table.insert(options.env, arg[i])
        elseif a == "-f" then
            i = i + 1
            table.insert(options.files, arg[i])
        elseif a == "-a" then
            options.artifacts = true
        elseif a == "-o" then
            i = i + 1
            options.output_dir = arg[i]
        elseif a == "-n" then
            i = i + 1
            options.network = arg[i]
        elseif a == "-v" then
            i = i + 1
            options.vcpu = tonumber(arg[i])
        elseif a == "-k" then
            i = i + 1
            options.api_key = arg[i]
        elseif a == "-s" or a == "--shell" then
            i = i + 1
            options.shell = arg[i]
        elseif a == "-l" or a == "--list" then
            options.list = true
        elseif a == "--attach" then
            i = i + 1
            options.attach = arg[i]
        elseif a == "--kill" then
            i = i + 1
            options.kill = arg[i]
        elseif a == "--audit" then
            options.audit = true
        elseif a == "--tmux" then
            options.tmux = true
        elseif a == "--screen" then
            options.screen = true
        elseif a == "--name" then
            i = i + 1
            options.name = arg[i]
        elseif a == "--ports" then
            i = i + 1
            options.ports = arg[i]
        elseif a == "--domains" then
            i = i + 1
            options.domains = arg[i]
        elseif a == "--type" then
            i = i + 1
            options.type = arg[i]
        elseif a == "--bootstrap" then
            i = i + 1
            options.bootstrap = arg[i]
        elseif a == "--info" then
            i = i + 1
            options.info = arg[i]
        elseif a == "--logs" then
            i = i + 1
            options.logs = arg[i]
        elseif a == "--tail" then
            i = i + 1
            options.tail = arg[i]
        elseif a == "--sleep" then
            i = i + 1
            options.sleep = arg[i]
        elseif a == "--wake" then
            i = i + 1
            options.wake = arg[i]
        elseif a == "--destroy" then
            i = i + 1
            options.destroy = arg[i]
        elseif a == "--execute" then
            i = i + 1
            options.execute = arg[i]
        elseif a == "--command" then
            i = i + 1
            options.command = arg[i]
        elseif not a:match("^%-") then
            options.source_file = a
        end

        i = i + 1
    end

    if options.command == "session" then
        cmd_session(options)
    elseif options.command == "service" then
        cmd_service(options)
    elseif options.source_file then
        cmd_execute(options)
    else
        print([[
Unsandbox CLI - Execute code in secure sandboxes

Usage:
  ]] .. arg[0] .. [[ [options] <source_file>
  ]] .. arg[0] .. [[ session [options]
  ]] .. arg[0] .. [[ service [options]

Execute options:
  -e KEY=VALUE      Environment variable (multiple allowed)
  -f FILE          Input file (multiple allowed)
  -a               Return artifacts
  -o DIR           Output directory for artifacts
  -n MODE          Network mode (zerotrust|semitrusted)
  -v N             vCPU count (1-8)
  -k KEY           API key

Session options:
  -s, --shell NAME  Shell/REPL (default: bash)
  -l, --list       List sessions
  --attach ID      Attach to session
  --kill ID        Terminate session
  --audit          Record session
  --tmux           Enable tmux persistence
  --screen         Enable screen persistence

Service options:
  --name NAME      Service name
  --ports PORTS    Comma-separated ports
  --domains DOMAINS Custom domains
  --type TYPE      Service type (minecraft|mumble|teamspeak|source|tcp|udp)
  --bootstrap CMD  Bootstrap command/file
  -l, --list       List services
  --info ID        Get service details
  --logs ID        Get all logs
  --tail ID        Get last 9000 lines
  --sleep ID       Freeze service
  --wake ID        Unfreeze service
  --destroy ID     Destroy service
  --execute ID     Execute command in service
  --command CMD    Command to execute (with --execute)
]])
        os.exit(1)
    end
end

main()

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