Initial commit: 42 UN CLI implementations
Unsandbox CLI implementations in 42 programming languages for the permacomputer project. Public domain software for code execution across all ecosystems. Languages: Python, JavaScript, Ruby, Go, Rust, C, C++, Java, Kotlin, C#, F#, Haskell, OCaml, Clojure, Scheme, Common Lisp, Erlang, Elixir, D, Nim, Zig, V, Dart, Groovy, Scala, Julia, R, Crystal, Fortran, COBOL, Prolog, Forth, Tcl, Raku, Lua, PHP, Perl, Bash, TypeScript, Objective-C, PowerShell, AWK Includes test suites and service lifecycle tests.
This commit is contained in:
commit
2b35819393
143 changed files with 30022 additions and 0 deletions
355
un.cr
Normal file
355
un.cr
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
# 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 - Source code must be open source & freely distributed
|
||||
# FREEDOM - Voluntary participation without corporate control
|
||||
# HARMONY - Systems operating with minimal waste that self-renew
|
||||
# LOVE - Individual rights protected while fostering cooperation
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
#!/usr/bin/env crystal
|
||||
|
||||
require "http/client"
|
||||
require "json"
|
||||
require "base64"
|
||||
require "option_parser"
|
||||
|
||||
# Extension to language mapping
|
||||
EXT_MAP = {
|
||||
".jl" => "julia", ".r" => "r", ".cr" => "crystal",
|
||||
".f90" => "fortran", ".cob" => "cobol", ".pro" => "prolog",
|
||||
".forth" => "forth", ".4th" => "forth", ".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", ".d" => "d", ".nim" => "nim", ".zig" => "zig",
|
||||
".v" => "v", ".dart" => "dart", ".groovy" => "groovy",
|
||||
".scala" => "scala", ".tcl" => "tcl", ".raku" => "raku", ".m" => "objc"
|
||||
}
|
||||
|
||||
# ANSI color codes
|
||||
BLUE = "\033[34m"
|
||||
RED = "\033[31m"
|
||||
GREEN = "\033[32m"
|
||||
YELLOW = "\033[33m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
API_BASE = "https://api.unsandbox.com"
|
||||
|
||||
def detect_language(filename : String) : String
|
||||
ext = File.extname(filename).downcase
|
||||
EXT_MAP.fetch(ext, "unknown")
|
||||
end
|
||||
|
||||
def get_api_key(args_key : String?) : String
|
||||
key = args_key || ENV["UNSANDBOX_API_KEY"]?
|
||||
if key.nil? || key.empty?
|
||||
STDERR.puts "#{RED}Error: UNSANDBOX_API_KEY not set#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
key
|
||||
end
|
||||
|
||||
def api_request(endpoint : String, api_key : String, method = "GET", data : JSON::Any? = nil)
|
||||
url = URI.parse(API_BASE + endpoint)
|
||||
headers = HTTP::Headers{
|
||||
"Content-Type" => "application/json",
|
||||
"Authorization" => "Bearer #{api_key}"
|
||||
}
|
||||
|
||||
begin
|
||||
response = case method
|
||||
when "GET"
|
||||
HTTP::Client.get(url, headers: headers)
|
||||
when "POST"
|
||||
body = data ? data.to_json : ""
|
||||
HTTP::Client.post(url, headers: headers, body: body)
|
||||
when "DELETE"
|
||||
HTTP::Client.delete(url, headers: headers)
|
||||
else
|
||||
STDERR.puts "#{RED}Error: Unsupported method: #{method}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
JSON.parse(response.body)
|
||||
rescue ex
|
||||
STDERR.puts "#{RED}Error: Request failed: #{ex.message}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_execute(args)
|
||||
api_key = get_api_key(args[:api_key]?)
|
||||
|
||||
filename = args[:source_file].as(String)
|
||||
unless File.exists?(filename)
|
||||
STDERR.puts "#{RED}Error: File not found: #{filename}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
language = detect_language(filename)
|
||||
if language == "unknown"
|
||||
STDERR.puts "#{RED}Error: Cannot detect language for #{filename}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
code = File.read(filename)
|
||||
|
||||
# Build request payload
|
||||
payload = JSON.parse({language: language, code: code}.to_json)
|
||||
|
||||
# Add environment variables
|
||||
if env_vars = args[:env]?.as?(Array(String))
|
||||
env_hash = {} of String => String
|
||||
env_vars.each do |e|
|
||||
if e.includes?('=')
|
||||
k, v = e.split('=', 2)
|
||||
env_hash[k] = v
|
||||
end
|
||||
end
|
||||
unless env_hash.empty?
|
||||
payload.as_h["env"] = JSON.parse(env_hash.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
# Add input files
|
||||
if files = args[:files]?.as?(Array(String))
|
||||
input_files = [] of JSON::Any
|
||||
files.each do |filepath|
|
||||
unless File.exists?(filepath)
|
||||
STDERR.puts "#{RED}Error: Input file not found: #{filepath}#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
content = Base64.strict_encode(File.read(filepath))
|
||||
input_files << JSON.parse({
|
||||
filename: File.basename(filepath),
|
||||
content_base64: content
|
||||
}.to_json)
|
||||
end
|
||||
unless input_files.empty?
|
||||
payload.as_h["input_files"] = JSON.parse(input_files.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
# Add options
|
||||
if args[:artifacts]?.as?(Bool)
|
||||
payload.as_h["return_artifacts"] = JSON::Any.new(true)
|
||||
end
|
||||
if network = args[:network]?.as?(String)
|
||||
payload.as_h["network"] = JSON::Any.new(network)
|
||||
end
|
||||
|
||||
# Execute
|
||||
result = api_request("/execute", api_key, method: "POST", data: payload)
|
||||
|
||||
# Print output
|
||||
if stdout = result["stdout"]?.try(&.as_s?)
|
||||
print BLUE, stdout, RESET
|
||||
end
|
||||
if stderr = result["stderr"]?.try(&.as_s?)
|
||||
print RED, stderr, RESET
|
||||
end
|
||||
|
||||
# Save artifacts
|
||||
if args[:artifacts]?.as?(Bool) && (artifacts = result["artifacts"]?.try(&.as_a?))
|
||||
out_dir = args[:output_dir]?.as?(String) || "."
|
||||
Dir.mkdir_p(out_dir)
|
||||
artifacts.each do |artifact|
|
||||
filename = artifact["filename"]?.try(&.as_s?) || "artifact"
|
||||
content = Base64.decode(artifact["content_base64"].as_s)
|
||||
path = File.join(out_dir, filename)
|
||||
File.write(path, content)
|
||||
File.chmod(path, 0o755)
|
||||
STDERR.puts "#{GREEN}Saved: #{path}#{RESET}"
|
||||
end
|
||||
end
|
||||
|
||||
exit_code = result["exit_code"]?.try(&.as_i?) || 0
|
||||
exit exit_code
|
||||
end
|
||||
|
||||
def cmd_session(args)
|
||||
api_key = get_api_key(args[:api_key]?)
|
||||
|
||||
if args[:list]?.as?(Bool)
|
||||
result = api_request("/sessions", api_key)
|
||||
sessions = result["sessions"]?.try(&.as_a?) || [] of JSON::Any
|
||||
if sessions.empty?
|
||||
puts "No active sessions"
|
||||
else
|
||||
printf "%-40s %-10s %-10s %s\n", "ID", "Shell", "Status", "Created"
|
||||
sessions.each do |s|
|
||||
printf "%-40s %-10s %-10s %s\n",
|
||||
s["id"]?.try(&.as_s?) || "N/A",
|
||||
s["shell"]?.try(&.as_s?) || "N/A",
|
||||
s["status"]?.try(&.as_s?) || "N/A",
|
||||
s["created_at"]?.try(&.as_s?) || "N/A"
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if kill_id = args[:kill]?.as?(String)
|
||||
api_request("/sessions/#{kill_id}", api_key, method: "DELETE")
|
||||
puts "#{GREEN}Session terminated: #{kill_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
STDERR.puts "#{RED}Error: Use --list or --kill#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def cmd_service(args)
|
||||
api_key = get_api_key(args[:api_key]?)
|
||||
|
||||
if args[:list]?.as?(Bool)
|
||||
result = api_request("/services", api_key)
|
||||
services = result["services"]?.try(&.as_a?) || [] of JSON::Any
|
||||
if services.empty?
|
||||
puts "No services"
|
||||
else
|
||||
printf "%-20s %-15s %-10s %-15s %s\n", "ID", "Name", "Status", "Ports", "Domains"
|
||||
services.each do |s|
|
||||
ports = s["ports"]?.try(&.as_a?.map(&.as_i).join(',')) || ""
|
||||
domains = s["domains"]?.try(&.as_a?.map(&.as_s).join(',')) || ""
|
||||
printf "%-20s %-15s %-10s %-15s %s\n",
|
||||
s["id"]?.try(&.as_s?) || "N/A",
|
||||
s["name"]?.try(&.as_s?) || "N/A",
|
||||
s["status"]?.try(&.as_s?) || "N/A",
|
||||
ports, domains
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if info_id = args[:info]?.as?(String)
|
||||
result = api_request("/services/#{info_id}", api_key)
|
||||
puts result.to_pretty_json
|
||||
return
|
||||
end
|
||||
|
||||
if logs_id = args[:logs]?.as?(String)
|
||||
result = api_request("/services/#{logs_id}/logs", api_key)
|
||||
puts result["logs"]?.try(&.as_s?) || ""
|
||||
return
|
||||
end
|
||||
|
||||
if sleep_id = args[:sleep]?.as?(String)
|
||||
api_request("/services/#{sleep_id}/sleep", api_key, method: "POST")
|
||||
puts "#{GREEN}Service sleeping: #{sleep_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if wake_id = args[:wake]?.as?(String)
|
||||
api_request("/services/#{wake_id}/wake", api_key, method: "POST")
|
||||
puts "#{GREEN}Service waking: #{wake_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
if destroy_id = args[:destroy]?.as?(String)
|
||||
api_request("/services/#{destroy_id}", api_key, method: "DELETE")
|
||||
puts "#{GREEN}Service destroyed: #{destroy_id}#{RESET}"
|
||||
return
|
||||
end
|
||||
|
||||
STDERR.puts "#{RED}Error: Use --list, --info, --logs, --sleep, --wake, or --destroy#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def main
|
||||
args = {
|
||||
source_file: nil,
|
||||
api_key: nil,
|
||||
network: nil,
|
||||
env: [] of String,
|
||||
files: [] of String,
|
||||
artifacts: false,
|
||||
output_dir: nil,
|
||||
command: nil,
|
||||
list: false,
|
||||
kill: nil,
|
||||
info: nil,
|
||||
logs: nil,
|
||||
sleep: nil,
|
||||
wake: nil,
|
||||
destroy: nil
|
||||
} of Symbol => (String | Array(String) | Bool | Nil)
|
||||
|
||||
parser = OptionParser.new do |opts|
|
||||
opts.banner = "Usage: un.cr [options] <source_file>\n un.cr session [options]\n un.cr service [options]"
|
||||
|
||||
opts.on("-k API_KEY", "--api-key=API_KEY", "API key") { |k| args[:api_key] = k }
|
||||
opts.on("-n NETWORK", "--network=NETWORK", "Network mode") { |n| args[:network] = n }
|
||||
opts.on("-e ENV", "--env=ENV", "Environment variable (KEY=VALUE)") { |e| args[:env].as(Array(String)) << e }
|
||||
opts.on("-f FILE", "--files=FILE", "Input file") { |f| args[:files].as(Array(String)) << f }
|
||||
opts.on("-a", "--artifacts", "Return artifacts") { args[:artifacts] = true }
|
||||
opts.on("-o DIR", "--output-dir=DIR", "Output directory") { |d| args[:output_dir] = d }
|
||||
opts.on("-l", "--list", "List items") { args[:list] = true }
|
||||
opts.on("--kill=ID", "Kill session") { |id| args[:kill] = id }
|
||||
opts.on("--info=ID", "Get service info") { |id| args[:info] = id }
|
||||
opts.on("--logs=ID", "Get service logs") { |id| args[:logs] = id }
|
||||
opts.on("--sleep=ID", "Sleep service") { |id| args[:sleep] = id }
|
||||
opts.on("--wake=ID", "Wake service") { |id| args[:wake] = id }
|
||||
opts.on("--destroy=ID", "Destroy service") { |id| args[:destroy] = id }
|
||||
|
||||
opts.unknown_args do |before, after|
|
||||
if before.size > 0
|
||||
case before[0]
|
||||
when "session"
|
||||
args[:command] = "session"
|
||||
when "service"
|
||||
args[:command] = "service"
|
||||
else
|
||||
args[:source_file] = before[0]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parser.parse
|
||||
|
||||
if args[:command] == "session"
|
||||
cmd_session(args)
|
||||
elsif args[:command] == "service"
|
||||
cmd_service(args)
|
||||
elsif args[:source_file]
|
||||
cmd_execute(args)
|
||||
else
|
||||
STDERR.puts parser
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
main
|
||||
Loading…
Add table
Add a link
Reference in a new issue