# 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 #!/usr/bin/env raku # unsandbox CLI - Raku implementation # Full-featured CLI matching un.c/un.py capabilities use JSON::Fast; use Digest::SHA; constant $API_BASE = "https://api.unsandbox.com"; constant $PORTAL_BASE = "https://unsandbox.com"; constant $BLUE = "\e[34m"; constant $RED = "\e[31m"; constant $GREEN = "\e[32m"; constant $YELLOW = "\e[33m"; constant $RESET = "\e[0m"; my %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 => 'vlang', dart => 'dart', groovy => 'groovy', scala => 'scala', f90 => 'fortran', f95 => 'fortran', cob => 'cobol', pro => 'prolog', forth => 'forth', '4th' => 'forth', tcl => 'tcl', raku => 'raku', pl6 => 'raku', p6 => 'raku', m => 'objc' ); sub get-api-keys() { my $public-key = %*ENV // ''; my $secret-key = %*ENV // ''; # Fallback to old UNSANDBOX_API_KEY for backwards compat if !$public-key && %*ENV { $public-key = %*ENV; $secret-key = ''; } unless $public-key { note "{$RED}Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY not set{$RESET}"; exit 1; } return ($public-key, $secret-key); } sub detect-language(Str $filename --> Str) { my $ext = $filename.IO.extension; return %EXT_MAP{$ext} if %EXT_MAP{$ext}:exists; # Try reading shebang if $filename.IO.e { my $first-line = $filename.IO.lines.head; if $first-line.starts-with('#!') { return 'python' if $first-line.contains('python'); return 'javascript' if $first-line.contains('node'); return 'ruby' if $first-line.contains('ruby'); return 'perl' if $first-line.contains('perl'); return 'bash' if $first-line.contains('bash') || $first-line.contains('/sh'); return 'lua' if $first-line.contains('lua'); return 'php' if $first-line.contains('php'); } } note "{$RED}Error: Cannot detect language for $filename{$RESET}"; exit 1; } sub api-request(Str $endpoint, Str $method, %data?, Str :$public-key!, Str :$secret-key!) { my $url = $API_BASE ~ $endpoint; my @args = 'curl', '-s'; my $body = ''; if $method eq 'GET' { @args.append: '-X', 'GET'; } elsif $method eq 'DELETE' { @args.append: '-X', 'DELETE'; } elsif $method eq 'POST' { @args.append: '-X', 'POST'; @args.append: '-H', 'Content-Type: application/json'; if %data { $body = to-json(%data); @args.append: '-d', $body; } } @args.append: '-H', "Authorization: Bearer $public-key"; # Add HMAC signature if secret-key is present if $secret-key { my $timestamp = now.Int; my $sig-input = "{$timestamp}:{$method}:{$endpoint}:{$body}"; my $signature = hmac-hex($sig-input, $secret-key, &sha256); @args.append: '-H', "X-Timestamp: $timestamp"; @args.append: '-H', "X-Signature: $signature"; } @args.append: $url; my $proc = run |@args, :out, :err; my $body = $proc.out.slurp; my $err = $proc.err.slurp; if $proc.exitcode != 0 { note "{$RED}Error: API request failed{$RESET}"; note $err if $err; exit 1; } return from-json($body); } sub cmd-execute(@args) { my ($public-key, $secret-key) = get-api-keys(); my $source-file = ''; my %env-vars; my @input-files; my $artifacts = False; my $output-dir = '.'; my $network = ''; my $vcpu = 0; # Parse arguments my $i = 0; while $i < @args.elems { given @args[$i] { when '-e' { $i++; my ($key, $value) = @args[$i].split('=', 2); %env-vars{$key} = $value; } when '-f' { $i++; @input-files.push(@args[$i]); } when '-a' { $artifacts = True; } when '-o' { $i++; $output-dir = @args[$i]; } when '-n' { $i++; $network = @args[$i]; } when '-v' { $i++; $vcpu = @args[$i].Int; } default { $source-file = @args[$i]; } } $i++; } unless $source-file { note "Usage: un.raku [options] "; exit 1; } unless $source-file.IO.e { note "{$RED}Error: File not found: $source-file{$RESET}"; exit 1; } # Read source file my $code = $source-file.IO.slurp; my $language = detect-language($source-file); # Build request payload my %payload = language => $language, code => $code; # Add environment variables %payload = %env-vars if %env-vars; # Add input files if @input-files { my @files; for @input-files -> $filepath { unless $filepath.IO.e { note "{$RED}Error: Input file not found: $filepath{$RESET}"; exit 1; } my $content = $filepath.IO.slurp(:bin); @files.push({ filename => $filepath.IO.basename, content_base64 => $content.encode('latin1').decode('latin1').encode.base64 }); } %payload = @files; } # Add options %payload = True if $artifacts; %payload = $network if $network; %payload = $vcpu if $vcpu > 0; # Execute my %result = api-request('/execute', 'POST', %payload, :$public-key, :$secret-key); # Print output if %result { print "{$BLUE}{%result}{$RESET}"; } if %result { note "{$RED}{%result}{$RESET}"; } # Save artifacts if $artifacts && %result { mkdir $output-dir unless $output-dir.IO.d; for %result.list -> %artifact { my $filename = %artifact; my $content = %artifact.decode('base64'); my $path = "$output-dir/$filename"; $path.IO.spurt($content, :bin); run 'chmod', '755', $path; note "{$GREEN}Saved: $path{$RESET}"; } } exit %result // 0; } sub cmd-session(@args) { my ($public-key, $secret-key) = get-api-keys(); my $list-mode = False; my $kill-id = ''; my $shell = ''; my $network = ''; my $vcpu = 0; # Parse arguments my $i = 0; while $i < @args.elems { given @args[$i] { when '--list' { $list-mode = True; } when '--kill' { $i++; $kill-id = @args[$i]; } when '--shell' { $i++; $shell = @args[$i]; } when '-n' { $i++; $network = @args[$i]; } when '-v' { $i++; $vcpu = @args[$i].Int; } } $i++; } if $list-mode { my %result = api-request('/sessions', 'GET', :$public-key, :$secret-key); my @sessions = %result.list; unless @sessions { say "No active sessions"; return; } say sprintf("%-40s %-10s %-10s %s", 'ID', 'Shell', 'Status', 'Created'); for @sessions -> %s { say sprintf("%-40s %-10s %-10s %s", %s, %s, %s, %s); } return; } if $kill-id { api-request("/sessions/$kill-id", 'DELETE', :$public-key, :$secret-key); say "{$GREEN}Session terminated: $kill-id{$RESET}"; return; } # Create new session my %payload = shell => ($shell || 'bash'); %payload = $network if $network; %payload = $vcpu if $vcpu > 0; say "{$YELLOW}Creating session...{$RESET}"; my %result = api-request('/sessions', 'POST', %payload, :$public-key, :$secret-key); say "{$GREEN}Session created: {%result}{$RESET}"; say "{$YELLOW}(Interactive sessions require WebSocket - use un2 for full support){$RESET}"; } sub cmd-service(@args) { my ($public-key, $secret-key) = get-api-keys(); my $list-mode = False; my $info-id = ''; my $logs-id = ''; my $sleep-id = ''; my $wake-id = ''; my $destroy-id = ''; my $dump-bootstrap-id = ''; my $dump-file = ''; my $name = ''; my $ports = ''; my $type = ''; my $bootstrap = ''; my $network = ''; my $vcpu = 0; # Parse arguments my $i = 0; while $i < @args.elems { given @args[$i] { when '--list' { $list-mode = True; } when '--info' { $i++; $info-id = @args[$i]; } when '--logs' { $i++; $logs-id = @args[$i]; } when '--freeze' { $i++; $sleep-id = @args[$i]; } when '--unfreeze' { $i++; $wake-id = @args[$i]; } when '--destroy' { $i++; $destroy-id = @args[$i]; } when '--dump-bootstrap' { $i++; $dump-bootstrap-id = @args[$i]; } when '--dump-file' { $i++; $dump-file = @args[$i]; } when '--name' { $i++; $name = @args[$i]; } when '--ports' { $i++; $ports = @args[$i]; } when '--type' { $i++; $type = @args[$i]; } when '--bootstrap' { $i++; $bootstrap = @args[$i]; } when '-n' { $i++; $network = @args[$i]; } when '-v' { $i++; $vcpu = @args[$i].Int; } } $i++; } if $list-mode { my %result = api-request('/services', 'GET', :$public-key, :$secret-key); my @services = %result.list; unless @services { say "No services"; return; } say sprintf("%-20s %-15s %-10s %-15s %s", 'ID', 'Name', 'Status', 'Ports', 'Domains'); for @services -> %s { my $port-str = %s.join(','); my $domain-str = %s.join(','); say sprintf("%-20s %-15s %-10s %-15s %s", %s, %s, %s, $port-str, $domain-str); } return; } if $info-id { my %result = api-request("/services/$info-id", 'GET', :$public-key, :$secret-key); say to-json(%result, :pretty); return; } if $logs-id { my %result = api-request("/services/$logs-id/logs", 'GET', :$public-key, :$secret-key); say %result; return; } if $sleep-id { api-request("/services/$sleep-id/sleep", 'POST', :$public-key, :$secret-key); say "{$GREEN}Service sleeping: $sleep-id{$RESET}"; return; } if $wake-id { api-request("/services/$wake-id/wake", 'POST', :$public-key, :$secret-key); say "{$GREEN}Service waking: $wake-id{$RESET}"; return; } if $destroy-id { api-request("/services/$destroy-id", 'DELETE', :$public-key, :$secret-key); say "{$GREEN}Service destroyed: $destroy-id{$RESET}"; return; } if $dump-bootstrap-id { note "Fetching bootstrap script from $dump-bootstrap-id..."; my %payload = command => "cat /tmp/bootstrap.sh"; my %result = api-request("/services/$dump-bootstrap-id/execute", 'POST', %payload, :$public-key, :$secret-key); if %result && %result ne '' { my $bootstrap = %result; if $dump-file { # Write to file $dump-file.IO.spurt($bootstrap); run 'chmod', '755', $dump-file; say "Bootstrap saved to $dump-file"; } else { # Print to stdout print $bootstrap; } } else { note "{$RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file){$RESET}"; exit 1; } return; } # Create new service if $name { my %payload = name => $name; if $ports { %payload = $ports.split(',')>>.Int; } if $type { %payload = $type; } if $bootstrap { # Check if bootstrap is a file if $bootstrap.IO.e && $bootstrap.IO.f { %payload = $bootstrap.IO.slurp; } else { %payload = $bootstrap; } } %payload = $network if $network; %payload = $vcpu if $vcpu > 0; my %result = api-request('/services', 'POST', %payload, :$public-key, :$secret-key); say "{$GREEN}Service created: {%result}{$RESET}"; say "Name: {%result}"; say "URL: {%result}" if %result; return; } note "{$RED}Error: Specify --name to create a service, or use --list, --info, etc.{$RESET}"; exit 1; } sub validate-key(Bool $extend) { my ($public-key, $secret-key) = get-api-keys(); # Build curl command my @args = 'curl', '-s', '-X', 'POST'; @args.append: "$PORTAL_BASE/keys/validate"; @args.append: '-H', 'Content-Type: application/json'; @args.append: '-H', "Authorization: Bearer $public-key"; # Add HMAC signature if secret-key is present if $secret-key { my $timestamp = now.Int; my $sig-input = "{$timestamp}:POST:/keys/validate:"; my $signature = hmac-hex($sig-input, $secret-key, &sha256); @args.append: '-H', "X-Timestamp: $timestamp"; @args.append: '-H', "X-Signature: $signature"; } my $proc = run |@args, :out, :err; my $body = $proc.out.slurp; my $err-msg = $proc.err.slurp; if $proc.exitcode != 0 { say "{$RED}Invalid{$RESET}"; note "Reason: $err-msg" if $err-msg; exit 1; } my %result = from-json($body); # Handle --extend flag if $extend { my $public-key = %result; if $public-key { say "{$BLUE}Opening browser to extend key...{$RESET}"; run 'xdg-open', "$PORTAL_BASE/keys/extend?pk=$public-key"; return; } else { note "{$RED}Error: Could not retrieve public key{$RESET}"; exit 1; } } # Check if key is expired if %result { say "{$RED}Expired{$RESET}"; say "Public Key: {%result // 'N/A'}"; say "Tier: {%result // 'N/A'}"; say "Expired: {%result // 'N/A'}"; say "{$YELLOW}To renew: Visit https://unsandbox.com/keys/extend{$RESET}"; exit 1; } # Valid key say "{$GREEN}Valid{$RESET}"; say "Public Key: {%result // 'N/A'}"; say "Tier: {%result // 'N/A'}"; say "Status: {%result // 'N/A'}"; say "Expires: {%result // 'N/A'}"; say "Time Remaining: {%result // 'N/A'}"; say "Rate Limit: {%result // 'N/A'}"; say "Burst: {%result // 'N/A'}"; say "Concurrency: {%result // 'N/A'}"; } sub cmd-key(@args) { my $extend = False; # Parse arguments for @args -> $arg { if $arg eq '--extend' { $extend = True; } } validate-key($extend); } sub MAIN(*@args) { unless @args { note "Usage: un.raku [options] "; note " un.raku session [options]"; note " un.raku service [options]"; note " un.raku key [options]"; exit 1; } given @args[0] { when 'session' { cmd-session(@args[1..*]); } when 'service' { cmd-service(@args[1..*]); } when 'key' { cmd-key(@args[1..*]); } default { cmd-execute(@args); } } }