CLI
Cliente de linha de comando rápido para execução de código e sessões interativas. Mais de 42 linguagens, mais de 30 shells/REPLs.
Documentação Oficial OpenAPI Swagger ↗Início Rápido — Prolog
# Download + setup
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/pro/sync/src/un.pro && chmod +x un.pro && ln -sf un.pro un
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"
# Run code
./un script.prolog
Baixar
Guia de Instalação →Características:
- 42+ languages - Python, JS, Go, Rust, C++, Java...
- Sessions - 30+ shells/REPLs, tmux persistence
- Files - Upload files, collect artifacts
- Services - Persistent containers with domains
- Snapshots - Point-in-time backups
- Images - Publish, share, transfer
Início Rápido de Integração ⚡
Adicione superpoderes unsandbox ao seu app Prolog existente:
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/pro/sync/src/un.pro
# Option A: Environment variables
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"
# Option B: Config file (persistent)
mkdir -p ~/.unsandbox
echo "unsb-pk-xxxx-xxxx-xxxx-xxxx,unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx" > ~/.unsandbox/accounts.csv
%% In your Prolog app:
:- use_module(un).
main :-
execute_code("prolog", "write('Hello from Prolog running on unsandbox!'), nl.", Result),
get_dict(stdout, Result, Stdout),
write(Stdout). % Hello from Prolog running on unsandbox!
swipl -g main -t halt myapp.pl
d59c9df2c72720e338a58abf8c3d2964
SHA256: f8a9dce64c58518d7bafd81af30c230f3864c188f7aea470c20a518f6965f041
% 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 swipl
:- initialization(main, main).
% Initialize global account index to -1 (not set)
:- nb_setval(account_index, -1).
% Constants
portal_base('https://unsandbox.com').
languages_cache_ttl(3600). % 1 hour cache TTL
% Get languages cache file path
languages_cache_file(Path) :-
getenv('HOME', Home),
atomic_list_concat([Home, '/.unsandbox/languages.json'], Path).
% Extension to language mapping
ext_lang('.jl', 'julia').
ext_lang('.r', 'r').
ext_lang('.cr', 'crystal').
ext_lang('.f90', 'fortran').
ext_lang('.cob', 'cobol').
ext_lang('.pro', 'prolog').
ext_lang('.forth', 'forth').
ext_lang('.4th', 'forth').
ext_lang('.py', 'python').
ext_lang('.js', 'javascript').
ext_lang('.ts', 'typescript').
ext_lang('.rb', 'ruby').
ext_lang('.php', 'php').
ext_lang('.pl', 'perl').
ext_lang('.lua', 'lua').
ext_lang('.sh', 'bash').
ext_lang('.go', 'go').
ext_lang('.rs', 'rust').
ext_lang('.c', 'c').
ext_lang('.cpp', 'cpp').
ext_lang('.java', 'java').
% Detect language from filename
detect_language(Filename, Language) :-
file_name_extension(_, Ext, Filename),
downcase_atom(Ext, ExtLower),
atomic_list_concat(['.', ExtLower], ExtWithDot),
ext_lang(ExtWithDot, Language), !.
detect_language(_, 'unknown').
% Read entire file into string
read_file_content(Filename, Content) :-
open(Filename, read, Stream),
read_string(Stream, _, Content),
close(Stream).
% Load credentials from accounts.csv at 0-indexed row
% Tries ~/.unsandbox/accounts.csv first, then ./accounts.csv
load_accounts_csv(Index, PK, SK) :-
getenv('HOME', Home),
atomic_list_concat([Home, '/.unsandbox/accounts.csv'], Path1),
( exists_file(Path1)
-> load_accounts_csv_file(Path1, Index, PK, SK)
; exists_file('accounts.csv')
-> load_accounts_csv_file('accounts.csv', Index, PK, SK)
; fail
).
load_accounts_csv_file(Path, Index, PK, SK) :-
setup_call_cleanup(
open(Path, read, Stream),
load_accounts_csv_stream(Stream, 0, Index, PK, SK),
close(Stream)
).
load_accounts_csv_stream(Stream, Count, Index, PK, SK) :-
read_line_to_string(Stream, Line),
Line \= end_of_file,
!,
% Skip blank lines and comments
( (Line = "" ; string_code(1, Line, 35)) % 35 = '#'
-> load_accounts_csv_stream(Stream, Count, Index, PK, SK)
; Count =:= Index
-> split_string(Line, ",", " \t", [PKStr, SKStr|_]),
atom_string(PK, PKStr),
atom_string(SK, SKStr)
; Next is Count + 1,
load_accounts_csv_stream(Stream, Next, Index, PK, SK)
).
% Get API keys from environment (HMAC or legacy), respecting --account N
get_public_key(PublicKey) :-
( nb_getval(account_index, Idx), Idx >= 0
-> ( load_accounts_csv(Idx, PublicKey, _)
-> true
; format(user_error, 'Error: Account index ~w not found in accounts.csv~n', [Idx]),
halt(1)
)
; ( getenv('UNSANDBOX_PUBLIC_KEY', PublicKey),
PublicKey \= ''
-> true
; getenv('UNSANDBOX_API_KEY', PublicKey),
PublicKey \= ''
-> true
; % Try accounts.csv with UNSANDBOX_ACCOUNT or row 0
( getenv('UNSANDBOX_ACCOUNT', IdxStr),
atom_number(IdxStr, DefaultIdx)
-> true
; DefaultIdx = 0
),
( load_accounts_csv(DefaultIdx, PublicKey, _)
-> true
; write(user_error, 'Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY environment variable not set\n'),
halt(1)
)
)
).
get_secret_key(SecretKey) :-
( nb_getval(account_index, Idx), Idx >= 0
-> ( load_accounts_csv(Idx, _, SecretKey)
-> true
; SecretKey = ''
)
; ( getenv('UNSANDBOX_SECRET_KEY', SecretKey),
SecretKey \= ''
-> true
; getenv('UNSANDBOX_API_KEY', SecretKey),
SecretKey \= ''
-> true
; % Try accounts.csv with UNSANDBOX_ACCOUNT or row 0
( getenv('UNSANDBOX_ACCOUNT', IdxStr),
atom_number(IdxStr, DefaultIdx)
-> true
; DefaultIdx = 0
),
( load_accounts_csv(DefaultIdx, _, SecretKey)
-> true
; SecretKey = ''
)
)
).
% Get API key (legacy compatibility)
get_api_key(ApiKey) :-
get_public_key(ApiKey).
% Execute command using curl
execute_file(Filename) :-
% Check file exists
( exists_file(Filename)
-> true
; format(user_error, 'Error: File not found: ~w~n', [Filename]),
halt(1)
),
% Detect language
detect_language(Filename, Language),
( Language \= 'unknown'
-> true
; format(user_error, 'Error: Unknown language for file: ~w~n', [Filename]),
halt(1)
),
% Get API keys
get_public_key(PublicKey),
get_secret_key(SecretKey),
% Build and execute curl command with HMAC
format(atom(Cmd),
'BODY=$(jq -Rs \'\'\''{language: "~w", code: .}\'\'\'\' < "~w"); TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/execute:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'\'\'s/.*= //\'\'\'\'); curl -s -X POST https://api.unsandbox.com/execute -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" -o /tmp/unsandbox_resp.json; RESP=$(cat /tmp/unsandbox_resp.json); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; rm -f /tmp/unsandbox_resp.json; exit 1; fi; jq -r ".stdout // empty" /tmp/unsandbox_resp.json | sed "s/^/\\x1b[34m/" | sed "s/$/\\x1b[0m/"; jq -r ".stderr // empty" /tmp/unsandbox_resp.json | sed "s/^/\\x1b[31m/" | sed "s/$/\\x1b[0m/" >&2; rm -f /tmp/unsandbox_resp.json',
[Language, Filename, SecretKey, PublicKey]),
shell(Cmd, 0).
% Session list
session_list :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/sessions:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X GET https://api.unsandbox.com/sessions -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; exit 1; fi; echo "$RESP" | jq -r \'.sessions[] | "\\(.id) \\(.shell) \\(.status) \\(.created_at)"\' 2>/dev/null || echo "No active sessions"',
[SecretKey, PublicKey]),
shell(Cmd, 0).
% Session kill
session_kill(SessionId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:DELETE:/sessions/~w:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X DELETE https://api.unsandbox.com/sessions/~w -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" >/dev/null && echo -e "\\x1b[32mSession terminated: ~w\\x1b[0m"',
[SessionId, SecretKey, SessionId, PublicKey, SessionId]),
shell(Cmd, 0).
% Session create with optional input files
session_create(Shell, InputFiles) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
( Shell \= ''
-> ShellVal = Shell
; ShellVal = 'bash'
),
% Build file arguments for bash script
build_file_args(InputFiles, FileArgs),
format(atom(Cmd),
'echo -e "\\x1b[33mCreating session...\\x1b[0m"; SHELL_VAL="~w"; INPUT_FILES=""; ~w if [ -n "$INPUT_FILES" ]; then BODY="{\\\"shell\\\":\\\"$SHELL_VAL\\\",\\\"input_files\\\":[$INPUT_FILES]}"; else BODY="{\\\"shell\\\":\\\"$SHELL_VAL\\\"}"; fi; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/sessions:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/sessions -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" | jq .',
[ShellVal, FileArgs, SecretKey, PublicKey]),
shell(Cmd, 0).
% Build bash commands to base64 encode files
build_file_args([], '').
build_file_args(Files, Args) :-
Files \= [],
maplist(build_single_file_arg, Files, ArgList),
atomic_list_concat(ArgList, ' ', Args).
build_single_file_arg(FilePath, Arg) :-
file_base_name(FilePath, Basename),
format(atom(Arg), 'CONTENT=$(base64 -w0 "~w"); if [ -z "$INPUT_FILES" ]; then INPUT_FILES="{\\\"filename\\\":\\\"~w\\\",\\\"content\\\":\\\"$CONTENT\\\"}"; else INPUT_FILES="$INPUT_FILES,{\\\"filename\\\":\\\"~w\\\",\\\"content\\\":\\\"$CONTENT\\\"}"; fi;', [FilePath, Basename, Basename]).
% Service list
service_list :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/services:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X GET https://api.unsandbox.com/services -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; exit 1; fi; echo "$RESP" | jq -r \'.services[] | "\\(.id) \\(.name) \\(.status)"\' 2>/dev/null || echo "No services"',
[SecretKey, PublicKey]),
shell(Cmd, 0).
% Service info
service_info(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/services/~w:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X GET https://api.unsandbox.com/services/~w -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq .',
[ServiceId, SecretKey, ServiceId, PublicKey]),
shell(Cmd, 0).
% Service logs
service_logs(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/services/~w/logs:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X GET https://api.unsandbox.com/services/~w/logs -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq -r ".logs"',
[ServiceId, SecretKey, ServiceId, PublicKey]),
shell(Cmd, 0).
% Service sleep
service_sleep(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/services/~w/freeze:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/services/~w/freeze -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" >/dev/null && echo -e "\\x1b[32mService frozen: ~w\\x1b[0m"',
[ServiceId, SecretKey, ServiceId, PublicKey, ServiceId]),
shell(Cmd, 0).
% Service wake
service_wake(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/services/~w/unfreeze:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/services/~w/unfreeze -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" >/dev/null && echo -e "\\x1b[32mService unfreezing: ~w\\x1b[0m"',
[ServiceId, SecretKey, ServiceId, PublicKey, ServiceId]),
shell(Cmd, 0).
% Service destroy
service_destroy(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:DELETE:/services/~w:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -w \'\'\\n%{http_code}\'\' -X DELETE https://api.unsandbox.com/services/~w -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); HTTP_CODE=$(echo "$RESP" | tail -n1); BODY=$(echo "$RESP" | sed \'\'$d\'\'); if [ "$HTTP_CODE" = "428" ]; then CHALLENGE_ID=$(echo "$BODY" | jq -r \'\'.challenge_id // empty\'\'); echo -e "\\x1b[33mConfirmation required. Check your email for a one-time code.\\x1b[0m" >&2; echo -n "Enter OTP: " >&2; read OTP; if [ -z "$OTP" ]; then echo -e "\\x1b[31mError: Operation cancelled\\x1b[0m" >&2; exit 1; fi; TS2=$(date +%s); MSG2="$TS2:DELETE:/services/~w:"; SIG2=$(echo -n "$MSG2" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP2=$(curl -s -w \'\'\\n%{http_code}\'\' -X DELETE https://api.unsandbox.com/services/~w -H "Authorization: Bearer ~w" -H "X-Timestamp: $TS2" -H "X-Signature: $SIG2" -H "X-Sudo-OTP: $OTP" -H "X-Sudo-Challenge: $CHALLENGE_ID"); HTTP2=$(echo "$RESP2" | tail -n1); if [ "$HTTP2" = "200" ] || [ "$HTTP2" = "204" ]; then echo -e "\\x1b[32mService destroyed: ~w\\x1b[0m"; else echo "$RESP2" | sed \'\'$d\'\' | jq . 2>/dev/null || echo "$RESP2" | sed \'\'$d\'\'; exit 1; fi; elif [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then echo -e "\\x1b[32mService destroyed: ~w\\x1b[0m"; else echo "$BODY" | jq . 2>/dev/null || echo "$BODY"; exit 1; fi',
[ServiceId, SecretKey, ServiceId, PublicKey, ServiceId, SecretKey, ServiceId, PublicKey, ServiceId, ServiceId]),
shell(Cmd, 0).
% Service resize
service_resize(ServiceId, Vcpu) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
Ram is Vcpu * 2,
format(atom(Cmd),
'BODY=\'\'{\"vcpu\":~w}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:PATCH:/services/~w:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X PATCH https://api.unsandbox.com/services/~w -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" >/dev/null && echo -e "\\x1b[32mService resized to ~w vCPU, ~w GB RAM\\x1b[0m"',
[Vcpu, ServiceId, SecretKey, ServiceId, PublicKey, Vcpu, Ram]),
shell(Cmd, 0).
% Service set unfreeze on demand
service_set_unfreeze_on_demand(ServiceId, Enabled) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
( Enabled = true
-> EnabledStr = 'true', Msg = 'enabled'
; EnabledStr = 'false', Msg = 'disabled'
),
format(atom(Cmd),
'BODY=\'\'{\"unfreeze_on_demand\":~w}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:PATCH:/services/~w:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X PATCH https://api.unsandbox.com/services/~w -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" >/dev/null && echo -e "\\x1b[32mUnfreeze-on-demand ~w for service: ~w\\x1b[0m"',
[EnabledStr, ServiceId, SecretKey, ServiceId, PublicKey, Msg, ServiceId]),
shell(Cmd, 0).
% Service env status
service_env_status(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/services/~w/env:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X GET "https://api.unsandbox.com/services/~w/env" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq .',
[ServiceId, SecretKey, ServiceId, PublicKey]),
shell(Cmd, 0).
% Service env set
service_env_set(ServiceId, Envs, EnvFile) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'ENV_CONTENT=""; ENV_LINES="~w"; if [ -n "$ENV_LINES" ]; then ENV_CONTENT="$ENV_LINES"; fi; ENV_FILE="~w"; if [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then while IFS= read -r line || [ -n "$line" ]; do case "$line" in "#"*|"") continue ;; esac; if [ -n "$ENV_CONTENT" ]; then ENV_CONTENT="$ENV_CONTENT\\n"; fi; ENV_CONTENT="$ENV_CONTENT$line"; done < "$ENV_FILE"; fi; if [ -z "$ENV_CONTENT" ]; then echo -e "\\x1b[31mError: No environment variables to set\\x1b[0m" >&2; exit 1; fi; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:PUT:/services/~w/env:$ENV_CONTENT"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X PUT "https://api.unsandbox.com/services/~w/env" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -H "Content-Type: text/plain" --data-binary "$ENV_CONTENT" | jq .',
[Envs, EnvFile, ServiceId, SecretKey, ServiceId, PublicKey]),
shell(Cmd, 0).
% Service env export
service_env_export(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/services/~w/env/export:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST "https://api.unsandbox.com/services/~w/env/export" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq -r ".content // empty"',
[ServiceId, SecretKey, ServiceId, PublicKey]),
shell(Cmd, 0).
% Service env delete
service_env_delete(ServiceId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:DELETE:/services/~w/env:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X DELETE "https://api.unsandbox.com/services/~w/env" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" >/dev/null && echo -e "\\x1b[32mVault deleted for: ~w\\x1b[0m"',
[ServiceId, SecretKey, ServiceId, PublicKey, ServiceId]),
shell(Cmd, 0).
% Service dump bootstrap
service_dump_bootstrap(ServiceId, DumpFile) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
( DumpFile = ''
-> % No file specified, print to stdout
format(atom(Cmd),
'echo "Fetching bootstrap script from ~w..." >&2; BODY=\'\'\'\'{"command":"cat /tmp/bootstrap.sh"}\'\'\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/services/~w/execute:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'\'\'s/.*= //\'\'\'\'); RESP=$(curl -s -X POST https://api.unsandbox.com/services/~w/execute -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY"); STDOUT=$(echo "$RESP" | jq -r ".stdout // empty"); if [ -n "$STDOUT" ]; then echo "$STDOUT"; else echo -e "\\x1b[31mError: Failed to fetch bootstrap\\x1b[0m" >&2; exit 1; fi',
[ServiceId, ServiceId, SecretKey, ServiceId, PublicKey])
; % File specified, save to file
format(atom(Cmd),
'echo "Fetching bootstrap script from ~w..." >&2; BODY=\'\'\'\'{"command":"cat /tmp/bootstrap.sh"}\'\'\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/services/~w/execute:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'\'\'s/.*= //\'\'\'\'); RESP=$(curl -s -X POST https://api.unsandbox.com/services/~w/execute -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY"); STDOUT=$(echo "$RESP" | jq -r ".stdout // empty"); if [ -n "$STDOUT" ]; then echo "$STDOUT" > "~w" && chmod 755 "~w" && echo "Bootstrap saved to ~w"; else echo -e "\\x1b[31mError: Failed to fetch bootstrap\\x1b[0m" >&2; exit 1; fi',
[ServiceId, ServiceId, SecretKey, ServiceId, PublicKey, DumpFile, DumpFile, DumpFile])
),
shell(Cmd, 0).
% Service create with optional input files
service_create(Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
% Build JSON payload
( Ports \= ''
-> format(atom(PortsJson), ',"ports":[~w]', [Ports])
; PortsJson = ''
),
( Bootstrap \= ''
-> format(atom(BootstrapJson), ',"bootstrap":"~w"', [Bootstrap])
; BootstrapJson = ''
),
( BootstrapFile \= ''
-> ( exists_file(BootstrapFile)
-> read_file_content(BootstrapFile, BootstrapContent),
format(atom(BootstrapContentJson), ',"bootstrap_content":"~w"', [BootstrapContent])
; format(user_error, 'Error: Bootstrap file not found: ~w~n', [BootstrapFile]),
halt(1)
)
; BootstrapContentJson = ''
),
( ServiceType \= ''
-> format(atom(ServiceTypeJson), ',"service_type":"~w"', [ServiceType])
; ServiceTypeJson = ''
),
% Build file arguments for bash script
build_file_args(InputFiles, FileArgs),
format(atom(Cmd),
'echo -e "\\x1b[33mCreating service...\\x1b[0m"; INPUT_FILES=""; ~w if [ -n "$INPUT_FILES" ]; then INPUT_FILES_JSON=",\\\"input_files\\\":[$INPUT_FILES]"; else INPUT_FILES_JSON=""; fi; BODY="{\\\"name\\\":\\\"~w\\\"~w~w~w~w$INPUT_FILES_JSON}"; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/services:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/services -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" | jq . && echo -e "\\x1b[32mService created\\x1b[0m"',
[FileArgs, Name, PortsJson, BootstrapJson, BootstrapContentJson, ServiceTypeJson, SecretKey, PublicKey]),
shell(Cmd, 0).
% Key validate
validate_key(Extend) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
portal_base(PortalBase),
( Extend = true
-> % Build command for --extend mode
format(atom(Cmd),
'BODY=\'\'{}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/keys/validate:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X POST ~w/keys/validate -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY"); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; exit 1; fi; PUBLIC_KEY=$(echo "$RESP" | jq -r ".public_key // \\"N/A\\""); xdg-open "~w/keys/extend?pk=$PUBLIC_KEY" 2>/dev/null',
[SecretKey, PortalBase, PublicKey, PortalBase])
; % Build command for normal validation
format(atom(Cmd),
'BODY=\'\'{}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/keys/validate:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST ~w/keys/validate -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" -o /tmp/unsandbox_key_resp.json; STATUS=$?; if [ $STATUS -ne 0 ]; then echo -e "\\x1b[31mInvalid\\x1b[0m"; exit 1; fi; RESP=$(cat /tmp/unsandbox_key_resp.json); if echo "$RESP" | grep -qi "timestamp" && echo "$RESP" | grep -Eqi "(401|expired|invalid)"; then echo -e "\\x1b[31mError: Request timestamp expired (must be within 5 minutes of server time)\\x1b[0m" >&2; echo -e "\\x1b[33mYour computer'\''s clock may have drifted.\\x1b[0m" >&2; echo "Check your system time and sync with NTP if needed:" >&2; echo " Linux: sudo ntpdate -s time.nist.gov" >&2; echo " macOS: sudo sntp -sS time.apple.com" >&2; echo " Windows: w32tm /resync\\x1b[0m" >&2; rm -f /tmp/unsandbox_key_resp.json; exit 1; fi; EXPIRED=$(jq -r ".expired // false" /tmp/unsandbox_key_resp.json); if [ "$EXPIRED" = "true" ]; then echo -e "\\x1b[31mExpired\\x1b[0m"; echo "Public Key: $(jq -r ".public_key // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Tier: $(jq -r ".tier // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Expired: $(jq -r ".expires_at // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo -e "\\x1b[33mTo renew: Visit https://unsandbox.com/keys/extend\\x1b[0m"; rm -f /tmp/unsandbox_key_resp.json; exit 1; else echo -e "\\x1b[32mValid\\x1b[0m"; echo "Public Key: $(jq -r ".public_key // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Tier: $(jq -r ".tier // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Status: $(jq -r ".status // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Expires: $(jq -r ".expires_at // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Time Remaining: $(jq -r ".time_remaining // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Rate Limit: $(jq -r ".rate_limit // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Burst: $(jq -r ".burst // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; echo "Concurrency: $(jq -r ".concurrency // \\"N/A\\"" /tmp/unsandbox_key_resp.json)"; fi; rm -f /tmp/unsandbox_key_resp.json',
[SecretKey, PortalBase, PublicKey])
),
shell(Cmd, 0).
% Read languages cache
read_languages_cache(Languages) :-
languages_cache_file(CacheFile),
exists_file(CacheFile),
languages_cache_ttl(TTL),
% Read cache file and check timestamp
format(atom(Cmd),
'CACHE=$(cat "~w" 2>/dev/null); if [ -n "$CACHE" ]; then TIMESTAMP=$(echo "$CACHE" | jq -r ".timestamp // 0"); CURRENT=$(date +%s); AGE=$((CURRENT - TIMESTAMP)); if [ $AGE -lt ~w ]; then echo "$CACHE" | jq -c ".languages // []"; fi; fi',
[CacheFile, TTL]),
setup_call_cleanup(
process_create(path(sh), ['-c', Cmd], [stdout(pipe(Out))]),
read_string(Out, _, Output),
close(Out)
),
Output \= '',
Output \= '\n',
Languages = Output.
% Write languages cache
write_languages_cache(LanguagesJson) :-
languages_cache_file(CacheFile),
getenv('HOME', Home),
atomic_list_concat([Home, '/.unsandbox'], CacheDir),
format(atom(MkdirCmd), 'mkdir -p "~w"', [CacheDir]),
shell(MkdirCmd, 0),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); echo \'\'\'\'{"languages":~w,"timestamp":\'\'\'\'$TIMESTAMP\'\'\'\'}\'\'\'\'> "~w"',
[LanguagesJson, CacheFile]),
shell(Cmd, 0).
% Languages command with caching
languages_command(JsonOutput) :-
% Try to read from cache first
( read_languages_cache(CachedLangs),
CachedLangs \= ''
-> % Use cached data
( JsonOutput = true
-> format('~w~n', [CachedLangs])
; % Parse and print one per line
format(atom(Cmd), 'echo \'\'~w\'\' | jq -r ".[]"', [CachedLangs]),
shell(Cmd, 0)
)
; % No valid cache, fetch from API
get_public_key(PublicKey),
get_secret_key(SecretKey),
( JsonOutput = true
-> % Output as JSON array and cache
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/languages:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X GET https://api.unsandbox.com/languages -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); LANGS=$(echo "$RESP" | jq -c ".languages // []"); echo "$LANGS"; CACHE_DIR="$HOME/.unsandbox"; mkdir -p "$CACHE_DIR"; echo "{\\\"languages\\\":$LANGS,\\\"timestamp\\\":$(date +%s)}" > "$CACHE_DIR/languages.json"',
[SecretKey, PublicKey])
; % Output one language per line and cache
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/languages:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X GET https://api.unsandbox.com/languages -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); LANGS=$(echo "$RESP" | jq -c ".languages // []"); echo "$RESP" | jq -r ".languages[]"; CACHE_DIR="$HOME/.unsandbox"; mkdir -p "$CACHE_DIR"; echo "{\\\"languages\\\":$LANGS,\\\"timestamp\\\":$(date +%s)}" > "$CACHE_DIR/languages.json"',
[SecretKey, PublicKey])
),
shell(Cmd, 0)
).
% Handle languages subcommand
handle_languages(['--json'|_]) :- languages_command(true).
handle_languages(_) :- languages_command(false).
% Handle key subcommand
handle_key(['--extend'|_]) :- validate_key(true).
handle_key(_) :- validate_key(false).
% Image list
image_list :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/images:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X GET https://api.unsandbox.com/images -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq .',
[SecretKey, PublicKey]),
shell(Cmd, 0).
% Image info
image_info(ImageId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:GET:/images/~w:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X GET https://api.unsandbox.com/images/~w -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" | jq .',
[ImageId, SecretKey, ImageId, PublicKey]),
shell(Cmd, 0).
% Image delete
image_delete(ImageId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:DELETE:/images/~w:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -w \'\'\\n%{http_code}\'\' -X DELETE https://api.unsandbox.com/images/~w -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); HTTP_CODE=$(echo "$RESP" | tail -n1); BODY=$(echo "$RESP" | sed \'\'$d\'\'); if [ "$HTTP_CODE" = "428" ]; then CHALLENGE_ID=$(echo "$BODY" | jq -r \'\'.challenge_id // empty\'\'); echo -e "\\x1b[33mConfirmation required. Check your email for a one-time code.\\x1b[0m" >&2; echo -n "Enter OTP: " >&2; read OTP; if [ -z "$OTP" ]; then echo -e "\\x1b[31mError: Operation cancelled\\x1b[0m" >&2; exit 1; fi; TS2=$(date +%s); MSG2="$TS2:DELETE:/images/~w:"; SIG2=$(echo -n "$MSG2" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP2=$(curl -s -w \'\'\\n%{http_code}\'\' -X DELETE https://api.unsandbox.com/images/~w -H "Authorization: Bearer ~w" -H "X-Timestamp: $TS2" -H "X-Signature: $SIG2" -H "X-Sudo-OTP: $OTP" -H "X-Sudo-Challenge: $CHALLENGE_ID"); HTTP2=$(echo "$RESP2" | tail -n1); if [ "$HTTP2" = "200" ] || [ "$HTTP2" = "204" ]; then echo -e "\\x1b[32mImage deleted: ~w\\x1b[0m"; else echo "$RESP2" | sed \'\'$d\'\' | jq . 2>/dev/null || echo "$RESP2" | sed \'\'$d\'\'; exit 1; fi; elif [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then echo -e "\\x1b[32mImage deleted: ~w\\x1b[0m"; else echo "$BODY" | jq . 2>/dev/null || echo "$BODY"; exit 1; fi',
[ImageId, SecretKey, ImageId, PublicKey, ImageId, SecretKey, ImageId, PublicKey, ImageId, ImageId]),
shell(Cmd, 0).
% Image lock
image_lock(ImageId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/images/~w/lock:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/images/~w/lock -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" >/dev/null && echo -e "\\x1b[32mImage locked: ~w\\x1b[0m"',
[ImageId, SecretKey, ImageId, PublicKey, ImageId]),
shell(Cmd, 0).
% Image unlock
image_unlock(ImageId) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/images/~w/unlock:"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -w \'\'\\n%{http_code}\'\' -X POST https://api.unsandbox.com/images/~w/unlock -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE"); HTTP_CODE=$(echo "$RESP" | tail -n1); BODY=$(echo "$RESP" | sed \'\'$d\'\'); if [ "$HTTP_CODE" = "428" ]; then CHALLENGE_ID=$(echo "$BODY" | jq -r \'\'.challenge_id // empty\'\'); echo -e "\\x1b[33mConfirmation required. Check your email for a one-time code.\\x1b[0m" >&2; echo -n "Enter OTP: " >&2; read OTP; if [ -z "$OTP" ]; then echo -e "\\x1b[31mError: Operation cancelled\\x1b[0m" >&2; exit 1; fi; TS2=$(date +%s); MSG2="$TS2:POST:/images/~w/unlock:"; SIG2=$(echo -n "$MSG2" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP2=$(curl -s -w \'\'\\n%{http_code}\'\' -X POST https://api.unsandbox.com/images/~w/unlock -H "Authorization: Bearer ~w" -H "X-Timestamp: $TS2" -H "X-Signature: $SIG2" -H "X-Sudo-OTP: $OTP" -H "X-Sudo-Challenge: $CHALLENGE_ID"); HTTP2=$(echo "$RESP2" | tail -n1); if [ "$HTTP2" = "200" ] || [ "$HTTP2" = "204" ]; then echo -e "\\x1b[32mImage unlocked: ~w\\x1b[0m"; else echo "$RESP2" | sed \'\'$d\'\' | jq . 2>/dev/null || echo "$RESP2" | sed \'\'$d\'\'; exit 1; fi; elif [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then echo -e "\\x1b[32mImage unlocked: ~w\\x1b[0m"; else echo "$BODY" | jq . 2>/dev/null || echo "$BODY"; exit 1; fi',
[ImageId, SecretKey, ImageId, PublicKey, ImageId, SecretKey, ImageId, PublicKey, ImageId, ImageId]),
shell(Cmd, 0).
% Image publish
image_publish(SourceId, SourceType, Name) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
( Name \= ''
-> format(atom(NameJson), ',\\\"name\\\":\\\"~w\\\"', [Name])
; NameJson = ''
),
format(atom(Cmd),
'BODY="{\\\"source_type\\\":\\\"~w\\\",\\\"source_id\\\":\\\"~w\\\"~w}"; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/images/publish:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/images/publish -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" | jq . && echo -e "\\x1b[32mImage published\\x1b[0m"',
[SourceType, SourceId, NameJson, SecretKey, PublicKey]),
shell(Cmd, 0).
% Image visibility
image_visibility(ImageId, Mode) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
format(atom(Cmd),
'BODY="{\\\"visibility\\\":\\\"~w\\\"}"; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/images/~w/visibility:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/images/~w/visibility -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" >/dev/null && echo -e "\\x1b[32mImage visibility set to ~w: ~w\\x1b[0m"',
[Mode, ImageId, SecretKey, ImageId, PublicKey, Mode, ImageId]),
shell(Cmd, 0).
% Image spawn
image_spawn(ImageId, Name, Ports) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
( Name \= ''
-> format(atom(NameJson), '\\\"name\\\":\\\"~w\\\"', [Name])
; NameJson = ''
),
( Ports \= ''
-> ( Name \= ''
-> format(atom(PortsJson), ',\\\"ports\\\":[~w]', [Ports])
; format(atom(PortsJson), '\\\"ports\\\":[~w]', [Ports])
)
; PortsJson = ''
),
format(atom(Cmd),
'BODY="{~w~w}"; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/images/~w/spawn:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/images/~w/spawn -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" | jq . && echo -e "\\x1b[32mService spawned from image\\x1b[0m"',
[NameJson, PortsJson, ImageId, SecretKey, ImageId, PublicKey]),
shell(Cmd, 0).
% Image clone
image_clone(ImageId, Name) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
( Name \= ''
-> format(atom(NameJson), '\\\"name\\\":\\\"~w\\\"', [Name])
; NameJson = ''
),
format(atom(Cmd),
'BODY="{~w}"; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/images/~w/clone:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X POST https://api.unsandbox.com/images/~w/clone -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" | jq . && echo -e "\\x1b[32mImage cloned\\x1b[0m"',
[NameJson, ImageId, SecretKey, ImageId, PublicKey]),
shell(Cmd, 0).
% Handle image subcommand
handle_image(['--list'|_]) :- !, image_list.
handle_image(['-l'|_]) :- !, image_list.
handle_image(['--info', ImageId|_]) :- !, image_info(ImageId).
handle_image(['--delete', ImageId|_]) :- !, image_delete(ImageId).
handle_image(['--lock', ImageId|_]) :- !, image_lock(ImageId).
handle_image(['--unlock', ImageId|_]) :- !, image_unlock(ImageId).
handle_image(['--publish', SourceId, '--source-type', SourceType|Rest]) :- !,
parse_image_name(Rest, '', Name),
image_publish(SourceId, SourceType, Name).
handle_image(['--publish', _|_]) :- !,
write(user_error, '\x1b[31mError: --publish requires --source-type (service or snapshot)\x1b[0m\n'),
halt(1).
handle_image(['--visibility', ImageId, Mode|_]) :- !, image_visibility(ImageId, Mode).
handle_image(['--spawn', ImageId|Rest]) :- !,
parse_spawn_args(Rest, '', '', Name, Ports),
image_spawn(ImageId, Name, Ports).
handle_image(['--clone', ImageId|Rest]) :- !,
parse_image_name(Rest, '', Name),
image_clone(ImageId, Name).
handle_image(_) :-
write(user_error, '\x1b[31mError: Use --list, --info, --delete, --lock, --unlock, --publish, --visibility, --spawn, or --clone\x1b[0m\n'),
halt(1).
% Parse --name from arguments
parse_image_name([], Name, Name).
parse_image_name(['--name', N|_], _, N) :- !.
parse_image_name([_|Rest], Name, NameOut) :- parse_image_name(Rest, Name, NameOut).
% Parse --name and --ports for spawn
parse_spawn_args([], Name, Ports, Name, Ports).
parse_spawn_args(['--name', N|Rest], _, Ports, NameOut, PortsOut) :- !,
parse_spawn_args(Rest, N, Ports, NameOut, PortsOut).
parse_spawn_args(['--ports', P|Rest], Name, _, NameOut, PortsOut) :- !,
parse_spawn_args(Rest, Name, P, NameOut, PortsOut).
parse_spawn_args([_|Rest], Name, Ports, NameOut, PortsOut) :-
parse_spawn_args(Rest, Name, Ports, NameOut, PortsOut).
% Handle session subcommand
handle_session(['--list'|_]) :- session_list.
handle_session(['-l'|_]) :- session_list.
handle_session(['--kill', SessionId|_]) :- session_kill(SessionId).
handle_session(Args) :-
parse_session_args(Args, '', [], Shell, InputFiles),
session_create(Shell, InputFiles).
% Parse session arguments for -f and --shell
parse_session_args([], Shell, Files, Shell, Files).
parse_session_args(['--shell', ShellVal|Rest], _, Files, Shell, InputFiles) :-
parse_session_args(Rest, ShellVal, Files, Shell, InputFiles).
parse_session_args(['-s', ShellVal|Rest], _, Files, Shell, InputFiles) :-
parse_session_args(Rest, ShellVal, Files, Shell, InputFiles).
parse_session_args(['-f', FilePath|Rest], Shell, Files, ShellOut, InputFiles) :-
( exists_file(FilePath)
-> append(Files, [FilePath], NewFiles),
parse_session_args(Rest, Shell, NewFiles, ShellOut, InputFiles)
; format(user_error, 'Error: File not found: ~w~n', [FilePath]),
halt(1)
).
parse_session_args([Arg|Rest], Shell, Files, ShellOut, InputFiles) :-
( atom_chars(Arg, ['-'|_])
-> format(user_error, 'Unknown option: ~w~n', [Arg]),
format(user_error, 'Usage: un.pro session [options]~n', []),
halt(1)
; parse_session_args(Rest, Shell, Files, ShellOut, InputFiles)
).
% Handle service subcommand
handle_service(['env', Action, ServiceId|Rest]) :-
!,
parse_env_args(Rest, '', '', Envs, EnvFile),
handle_env_action(Action, ServiceId, Envs, EnvFile).
handle_service(Args) :-
parse_service_args(Args, '', '', '', '', '', [], '', '', Action, InputFiles),
execute_service_action(Action, InputFiles).
% Handle env action
handle_env_action('status', ServiceId, _, _) :- service_env_status(ServiceId).
handle_env_action('set', ServiceId, Envs, EnvFile) :- service_env_set(ServiceId, Envs, EnvFile).
handle_env_action('export', ServiceId, _, _) :- service_env_export(ServiceId).
handle_env_action('delete', ServiceId, _, _) :- service_env_delete(ServiceId).
handle_env_action(Action, _, _, _) :-
format(user_error, 'Error: Unknown env action: ~w~n', [Action]),
write(user_error, 'Usage: un.pro service env <status|set|export|delete> <service_id>\n'),
halt(1).
% Parse env arguments for -e and --env-file
parse_env_args([], Envs, EnvFile, Envs, EnvFile).
parse_env_args(['-e', EnvVal|Rest], Envs, EnvFile, EnvsOut, EnvFileOut) :-
( Envs \= ''
-> format(atom(NewEnvs), '~w\\n~w', [Envs, EnvVal])
; NewEnvs = EnvVal
),
parse_env_args(Rest, NewEnvs, EnvFile, EnvsOut, EnvFileOut).
parse_env_args(['--env-file', EnvFileVal|Rest], Envs, _, EnvsOut, EnvFileOut) :-
parse_env_args(Rest, Envs, EnvFileVal, EnvsOut, EnvFileOut).
parse_env_args([_|Rest], Envs, EnvFile, EnvsOut, EnvFileOut) :-
parse_env_args(Rest, Envs, EnvFile, EnvsOut, EnvFileOut).
% Parse service arguments
parse_service_args([], Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, create, InputFiles) :-
( Name \= ''
-> service_create_with_vault(Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile)
; write(user_error, 'Error: --name required for service creation\n'),
halt(1)
).
parse_service_args([], _, _, _, _, _, InputFiles, _, _, Action, InputFiles) :-
( Action = list
-> service_list
; write(user_error, 'Error: Use --list, --info, --logs, --freeze, --unfreeze, --destroy, --name, or env\n'),
halt(1)
).
parse_service_args(['--list'|_], _, _, _, _, _, _, _, _, _, _) :- service_list.
parse_service_args(['-l'|_], _, _, _, _, _, _, _, _, _, _) :- service_list.
parse_service_args(['--info', ServiceId|_], _, _, _, _, _, _, _, _, _, _) :- service_info(ServiceId).
parse_service_args(['--logs', ServiceId|_], _, _, _, _, _, _, _, _, _, _) :- service_logs(ServiceId).
parse_service_args(['--freeze', ServiceId|_], _, _, _, _, _, _, _, _, _, _) :- service_sleep(ServiceId).
parse_service_args(['--unfreeze', ServiceId|_], _, _, _, _, _, _, _, _, _, _) :- service_wake(ServiceId).
parse_service_args(['--destroy', ServiceId|_], _, _, _, _, _, _, _, _, _, _) :- service_destroy(ServiceId).
parse_service_args(['--resize', ServiceId, '--vcpu', VcpuAtom|_], _, _, _, _, _, _, _, _, _, _) :-
atom_number(VcpuAtom, Vcpu),
( Vcpu >= 1, Vcpu =< 8
-> service_resize(ServiceId, Vcpu)
; write(user_error, '\x1b[31mError: vCPU must be between 1 and 8\x1b[0m\n'),
halt(1)
).
parse_service_args(['--resize', ServiceId, '-v', VcpuAtom|_], _, _, _, _, _, _, _, _, _, _) :-
atom_number(VcpuAtom, Vcpu),
( Vcpu >= 1, Vcpu =< 8
-> service_resize(ServiceId, Vcpu)
; write(user_error, '\x1b[31mError: vCPU must be between 1 and 8\x1b[0m\n'),
halt(1)
).
parse_service_args(['--resize', _|_], _, _, _, _, _, _, _, _, _, _) :-
write(user_error, '\x1b[31mError: --resize requires --vcpu or -v\x1b[0m\n'),
halt(1).
parse_service_args(['--set-unfreeze-on-demand', ServiceId, EnabledAtom|_], _, _, _, _, _, _, _, _, _, _) :-
( (EnabledAtom = 'true' ; EnabledAtom = '1')
-> service_set_unfreeze_on_demand(ServiceId, true)
; (EnabledAtom = 'false' ; EnabledAtom = '0')
-> service_set_unfreeze_on_demand(ServiceId, false)
; write(user_error, '\x1b[31mError: --set-unfreeze-on-demand requires true/false or 1/0\x1b[0m\n'),
halt(1)
).
parse_service_args(['--dump-bootstrap', ServiceId|Rest], _, _, _, _, _, _, _, _, _, _) :-
( Rest = ['--dump-file', DumpFile|_]
-> service_dump_bootstrap(ServiceId, DumpFile)
; service_dump_bootstrap(ServiceId, '')
).
parse_service_args(['--name', Name|Rest], _, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, _, InputFilesOut) :-
parse_service_args(Rest, Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, create, InputFilesOut).
parse_service_args(['--ports', PortsList|Rest], Name, _, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut) :-
parse_service_args(Rest, Name, PortsList, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut).
parse_service_args(['--bootstrap', BootstrapVal|Rest], Name, Ports, _, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut) :-
parse_service_args(Rest, Name, Ports, BootstrapVal, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut).
parse_service_args(['--bootstrap-file', BootstrapFileVal|Rest], Name, Ports, Bootstrap, _, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut) :-
parse_service_args(Rest, Name, Ports, Bootstrap, BootstrapFileVal, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut).
parse_service_args(['--type', Type|Rest], Name, Ports, Bootstrap, BootstrapFile, _, InputFiles, Envs, EnvFile, Action, InputFilesOut) :-
parse_service_args(Rest, Name, Ports, Bootstrap, BootstrapFile, Type, InputFiles, Envs, EnvFile, Action, InputFilesOut).
parse_service_args(['-e', EnvVal|Rest], Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut) :-
( Envs \= ''
-> format(atom(NewEnvs), '~w\\n~w', [Envs, EnvVal])
; NewEnvs = EnvVal
),
parse_service_args(Rest, Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, NewEnvs, EnvFile, Action, InputFilesOut).
parse_service_args(['--env-file', EnvFileVal|Rest], Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, _, Action, InputFilesOut) :-
parse_service_args(Rest, Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFileVal, Action, InputFilesOut).
parse_service_args(['-f', FilePath|Rest], Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut) :-
( exists_file(FilePath)
-> append(InputFiles, [FilePath], NewInputFiles),
parse_service_args(Rest, Name, Ports, Bootstrap, BootstrapFile, ServiceType, NewInputFiles, Envs, EnvFile, Action, InputFilesOut)
; format(user_error, 'Error: File not found: ~w~n', [FilePath]),
halt(1)
).
parse_service_args([_|Rest], Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut) :-
parse_service_args(Rest, Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile, Action, InputFilesOut).
% Execute service action (not used, but kept for structure)
execute_service_action(_, _).
% Service create with auto-vault
service_create_with_vault(Name, Ports, Bootstrap, BootstrapFile, ServiceType, InputFiles, Envs, EnvFile) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
% Build JSON payload
( Ports \= ''
-> format(atom(PortsJson), ',"ports":[~w]', [Ports])
; PortsJson = ''
),
( Bootstrap \= ''
-> format(atom(BootstrapJson), ',"bootstrap":"~w"', [Bootstrap])
; BootstrapJson = ''
),
( BootstrapFile \= ''
-> ( exists_file(BootstrapFile)
-> read_file_content(BootstrapFile, BootstrapContent),
format(atom(BootstrapContentJson), ',"bootstrap_content":"~w"', [BootstrapContent])
; format(user_error, 'Error: Bootstrap file not found: ~w~n', [BootstrapFile]),
halt(1)
)
; BootstrapContentJson = ''
),
( ServiceType \= ''
-> format(atom(ServiceTypeJson), ',"service_type":"~w"', [ServiceType])
; ServiceTypeJson = ''
),
% Build file arguments for bash script
build_file_args(InputFiles, FileArgs),
format(atom(Cmd),
'echo -e "\\x1b[33mCreating service...\\x1b[0m"; INPUT_FILES=""; ~w if [ -n "$INPUT_FILES" ]; then INPUT_FILES_JSON=",\\\"input_files\\\":[$INPUT_FILES]"; else INPUT_FILES_JSON=""; fi; BODY="{\\\"name\\\":\\\"~w\\\"~w~w~w~w$INPUT_FILES_JSON}"; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:POST:/services:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); RESP=$(curl -s -X POST https://api.unsandbox.com/services -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY"); SVC_ID=$(echo "$RESP" | jq -r ".id // empty"); if [ -n "$SVC_ID" ]; then echo -e "\\x1b[32m$SVC_ID created\\x1b[0m"; ENV_CONTENT=""; ENV_LINES="~w"; if [ -n "$ENV_LINES" ]; then ENV_CONTENT="$ENV_LINES"; fi; ENV_FILE="~w"; if [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then while IFS= read -r line || [ -n "$line" ]; do case "$line" in "#"*|"") continue ;; esac; if [ -n "$ENV_CONTENT" ]; then ENV_CONTENT="$ENV_CONTENT\\n"; fi; ENV_CONTENT="$ENV_CONTENT$line"; done < "$ENV_FILE"; fi; if [ -n "$ENV_CONTENT" ]; then TS2=$(date +%s); SIG2=$(echo -n "$TS2:PUT:/services/$SVC_ID/env:$ENV_CONTENT" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X PUT "https://api.unsandbox.com/services/$SVC_ID/env" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TS2" -H "X-Signature: $SIG2" -H "Content-Type: text/plain" --data-binary "$ENV_CONTENT" >/dev/null && echo -e "\\x1b[32mVault configured\\x1b[0m"; fi; else echo "$RESP" | jq .; fi',
[FileArgs, Name, PortsJson, BootstrapJson, BootstrapContentJson, ServiceTypeJson, SecretKey, PublicKey, Envs, EnvFile, SecretKey, PublicKey]),
shell(Cmd, 0).
% Main program
main(Argv) :-
% Initialize account_index global to -1 (not set)
nb_setval(account_index, -1),
% Parse --account N global flag if present
( Argv = ['--account', NStr|Rest]
-> ( atom_number(NStr, N), integer(N)
-> nb_setval(account_index, N),
ActualArgv = Rest
; write(user_error, 'Error: --account requires an integer argument\n'),
halt(1)
)
; ActualArgv = Argv
),
% Check arguments
( ActualArgv = []
-> write(user_error, 'Usage: un.pro [options] <source_file>\n'),
write(user_error, ' un.pro session [options]\n'),
write(user_error, ' un.pro service [options]\n'),
write(user_error, ' un.pro snapshot [options]\n'),
write(user_error, ' un.pro image [options]\n'),
write(user_error, ' un.pro languages [--json]\n'),
write(user_error, ' un.pro key [options]\n'),
write(user_error, '\n'),
write(user_error, 'Snapshot options:\n'),
write(user_error, ' --list, -l List all snapshots\n'),
write(user_error, ' --info ID Get snapshot details\n'),
write(user_error, ' --delete ID Delete a snapshot\n'),
write(user_error, ' --lock ID Lock snapshot\n'),
write(user_error, ' --unlock ID Unlock snapshot\n'),
write(user_error, ' --restore ID Restore from snapshot\n'),
write(user_error, ' --clone ID Clone snapshot (--type required)\n'),
write(user_error, ' --type TYPE Clone type: session or service\n'),
write(user_error, ' --name NAME Name for cloned resource\n'),
write(user_error, ' --shell SHELL Shell for cloned session\n'),
write(user_error, ' --ports PORTS Ports for cloned service\n'),
write(user_error, '\n'),
write(user_error, 'Image options:\n'),
write(user_error, ' --list, -l List all images\n'),
write(user_error, ' --info ID Get image details\n'),
write(user_error, ' --delete ID Delete an image\n'),
write(user_error, ' --lock ID Lock image\n'),
write(user_error, ' --unlock ID Unlock image\n'),
write(user_error, ' --publish ID --source-type TYPE Publish image\n'),
write(user_error, ' --visibility ID MODE Set visibility\n'),
write(user_error, ' --spawn ID Spawn service from image\n'),
write(user_error, ' --clone ID Clone an image\n'),
write(user_error, ' --name NAME Name for spawned/cloned\n'),
write(user_error, ' --ports PORTS Ports for spawned service\n'),
halt(1)
; true
),
% Parse subcommands
( ActualArgv = ['session'|Rest]
-> handle_session(Rest)
; ActualArgv = ['service'|Rest]
-> handle_service(Rest)
; ActualArgv = ['snapshot'|Rest]
-> handle_snapshot(Rest)
; ActualArgv = ['image'|Rest]
-> handle_image(Rest)
; ActualArgv = ['languages'|Rest]
-> handle_languages(Rest)
; ActualArgv = ['key'|Rest]
-> handle_key(Rest)
; ActualArgv = [Filename|_]
-> execute_file(Filename)
; write(user_error, 'Error: Invalid arguments\n'),
halt(1)
).
% =============================================================================
% Snapshot handlers
% =============================================================================
handle_snapshot(Args) :-
get_public_key(PublicKey),
get_secret_key(SecretKey),
handle_snapshot_cmd(Args, PublicKey, SecretKey).
handle_snapshot_cmd(['--list'|_], PublicKey, SecretKey) :- !,
atomic_list_concat([
'TS=$(date +%s); ',
'SIG=$(echo -n "$TS:GET:/snapshots:" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'curl -s -X GET "https://api.unsandbox.com/snapshots" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS" ',
'-H "X-Signature: $SIG" | jq .'
], Cmd),
shell(Cmd).
handle_snapshot_cmd(['-l'|_], PublicKey, SecretKey) :- !,
handle_snapshot_cmd(['--list'], PublicKey, SecretKey).
handle_snapshot_cmd(['--info', Id|_], PublicKey, SecretKey) :- !,
atomic_list_concat([
'TS=$(date +%s); ',
'SIG=$(echo -n "$TS:GET:/snapshots/', Id, ':" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'curl -s -X GET "https://api.unsandbox.com/snapshots/', Id, '" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS" ',
'-H "X-Signature: $SIG" | jq .'
], Cmd),
shell(Cmd).
handle_snapshot_cmd(['--delete', Id|_], PublicKey, SecretKey) :- !,
atomic_list_concat([
'TS=$(date +%s); ',
'SIG=$(echo -n "$TS:DELETE:/snapshots/', Id, ':" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'RESP=$(curl -s -w "\\n%{http_code}" -X DELETE "https://api.unsandbox.com/snapshots/', Id, '" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS" ',
'-H "X-Signature: $SIG"); ',
'HTTP_CODE=$(echo "$RESP" | tail -1); ',
'BODY=$(echo "$RESP" | head -n -1); ',
'if [ "$HTTP_CODE" = "428" ]; then ',
'OTP=$(echo "$BODY" | jq -r ".otp // empty"); ',
'if [ -n "$OTP" ]; then ',
'TS2=$(date +%s); ',
'SIG2=$(echo -n "$TS2:DELETE:/snapshots/', Id, ':" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'curl -s -X DELETE "https://api.unsandbox.com/snapshots/', Id, '" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS2" ',
'-H "X-Signature: $SIG2" ',
'-H "X-Sudo-OTP: $OTP" | jq .; ',
'echo -e "\\x1b[32mSnapshot deleted\\x1b[0m"; fi; ',
'else echo "$BODY" | jq .; fi'
], Cmd),
shell(Cmd).
handle_snapshot_cmd(['--lock', Id|_], PublicKey, SecretKey) :- !,
atomic_list_concat([
'TS=$(date +%s); ',
'SIG=$(echo -n "$TS:POST:/snapshots/', Id, '/lock:" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'curl -s -X POST "https://api.unsandbox.com/snapshots/', Id, '/lock" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS" ',
'-H "X-Signature: $SIG" | jq . && ',
'echo -e "\\x1b[32mSnapshot locked\\x1b[0m"'
], Cmd),
shell(Cmd).
handle_snapshot_cmd(['--unlock', Id|_], PublicKey, SecretKey) :- !,
atomic_list_concat([
'TS=$(date +%s); ',
'BODY="{}"; ',
'SIG=$(echo -n "$TS:POST:/snapshots/', Id, '/unlock:$BODY" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'RESP=$(curl -s -w "\\n%{http_code}" -X POST "https://api.unsandbox.com/snapshots/', Id, '/unlock" ',
'-H "Content-Type: application/json" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS" ',
'-H "X-Signature: $SIG" ',
'-d "$BODY"); ',
'HTTP_CODE=$(echo "$RESP" | tail -1); ',
'BODY_RESP=$(echo "$RESP" | head -n -1); ',
'if [ "$HTTP_CODE" = "428" ]; then ',
'OTP=$(echo "$BODY_RESP" | jq -r ".otp // empty"); ',
'if [ -n "$OTP" ]; then ',
'TS2=$(date +%s); ',
'SIG2=$(echo -n "$TS2:POST:/snapshots/', Id, '/unlock:$BODY" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'curl -s -X POST "https://api.unsandbox.com/snapshots/', Id, '/unlock" ',
'-H "Content-Type: application/json" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS2" ',
'-H "X-Signature: $SIG2" ',
'-H "X-Sudo-OTP: $OTP" ',
'-d "$BODY" | jq .; ',
'echo -e "\\x1b[32mSnapshot unlocked\\x1b[0m"; fi; ',
'else echo "$BODY_RESP" | jq .; fi'
], Cmd),
shell(Cmd).
handle_snapshot_cmd(['--restore', Id|_], PublicKey, SecretKey) :- !,
atomic_list_concat([
'TS=$(date +%s); ',
'BODY="{}"; ',
'SIG=$(echo -n "$TS:POST:/snapshots/', Id, '/restore:$BODY" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'curl -s -X POST "https://api.unsandbox.com/snapshots/', Id, '/restore" ',
'-H "Content-Type: application/json" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS" ',
'-H "X-Signature: $SIG" ',
'-d "$BODY" | jq . && ',
'echo -e "\\x1b[32mSnapshot restored\\x1b[0m"'
], Cmd),
shell(Cmd).
handle_snapshot_cmd(['--clone', Id|Rest], PublicKey, SecretKey) :- !,
parse_snapshot_clone_args(Rest, Type, Name, Ports, Shell),
( Type = ''
-> write(user_error, '\x1b[31mError: --type required for --clone (session or service)\x1b[0m\n'),
halt(1)
; true
),
build_snapshot_clone_body(Type, Name, Ports, Shell, Body),
atomic_list_concat([
'TS=$(date +%s); ',
'BODY=''', Body, '''; ',
'SIG=$(echo -n "$TS:POST:/snapshots/', Id, '/clone:$BODY" | openssl dgst -sha256 -hmac "', SecretKey, '" | cut -d" " -f2); ',
'curl -s -X POST "https://api.unsandbox.com/snapshots/', Id, '/clone" ',
'-H "Content-Type: application/json" ',
'-H "Authorization: Bearer ', PublicKey, '" ',
'-H "X-Timestamp: $TS" ',
'-H "X-Signature: $SIG" ',
'-d "$BODY" | jq . && ',
'echo -e "\\x1b[32mSnapshot cloned\\x1b[0m"'
], Cmd),
shell(Cmd).
handle_snapshot_cmd(_, _, _) :-
write(user_error, '\x1b[31mError: Use --list, --info, --delete, --lock, --unlock, --restore, or --clone\x1b[0m\n'),
halt(1).
parse_snapshot_clone_args([], '', '', '', '').
parse_snapshot_clone_args(['--type', Type|Rest], Type, Name, Ports, Shell) :- !,
parse_snapshot_clone_args(Rest, _, Name, Ports, Shell).
parse_snapshot_clone_args(['--name', Name|Rest], Type, Name, Ports, Shell) :- !,
parse_snapshot_clone_args(Rest, Type, _, Ports, Shell).
parse_snapshot_clone_args(['--ports', Ports|Rest], Type, Name, Ports, Shell) :- !,
parse_snapshot_clone_args(Rest, Type, Name, _, Shell).
parse_snapshot_clone_args(['--shell', Shell|Rest], Type, Name, Ports, Shell) :- !,
parse_snapshot_clone_args(Rest, Type, Name, Ports, _).
parse_snapshot_clone_args([_|Rest], Type, Name, Ports, Shell) :-
parse_snapshot_clone_args(Rest, Type, Name, Ports, Shell).
build_snapshot_clone_body(Type, Name, Ports, Shell, Body) :-
atomic_list_concat(['{"type":"', Type, '"'], Base),
( Name \= ''
-> atomic_list_concat([Base, ',"name":"', Name, '"'], Base2)
; Base2 = Base
),
( Ports \= ''
-> atomic_list_concat([Base2, ',"ports":[', Ports, ']'], Base3)
; Base3 = Base2
),
( Shell \= ''
-> atomic_list_concat([Base3, ',"shell":"', Shell, '"'], Base4)
; Base4 = Base3
),
atomic_list_concat([Base4, '}'], Body).
Esclarecimentos de documentação
Dependências
C Binary (un1) — requer libcurl e libwebsockets:
sudo apt install build-essential libcurl4-openssl-dev libwebsockets-dev
wget unsandbox.com/downloads/un.c && gcc -O2 -o un un.c -lcurl -lwebsockets
Implementações SDK — a maioria usa apenas stdlib (Ruby, JS, Go, etc). Alguns requerem dependências mínimas:
pip install requests # Python
Executar Código
Executar um script
./un hello.py
./un app.js
./un main.rs
Com variáveis de ambiente
./un -e DEBUG=1 -e NAME=World script.py
Com arquivos de entrada (teletransportar arquivos para sandbox)
./un -f data.csv -f config.json process.py
Obter binário compilado
./un -a -o ./bin main.c
Sessões interativas
Iniciar uma sessão de shell
# Default bash shell
./un session
# Choose your shell
./un session --shell zsh
./un session --shell fish
# Jump into a REPL
./un session --shell python3
./un session --shell node
./un session --shell julia
Sessão com acesso à rede
./un session -n semitrusted
Auditoria de sessão (gravação completa do terminal)
# Record everything (including vim, interactive programs)
./un session --audit -o ./logs
# Replay session later
zcat session.log*.gz | less -R
Coletar artefatos da sessão
# Files in /tmp/artifacts/ are collected on exit
./un session -a -o ./outputs
Persistência de sessão (tmux/screen)
# Default: session terminates on disconnect (clean exit)
./un session
# With tmux: session persists, can reconnect later
./un session --tmux
# Press Ctrl+b then d to detach
# With screen: alternative multiplexer
./un session --screen
# Press Ctrl+a then d to detach
Listar Trabalhos Ativos
./un session --list
# Output:
# Active sessions: 2
#
# SESSION ID CONTAINER SHELL TTL STATUS
# abc123... unsb-vm-12345 python3 45m30s active
# def456... unsb-vm-67890 bash 1h2m active
Reconectar à sessão existente
# Reconnect by container name (requires --tmux or --screen)
./un session --attach unsb-vm-12345
# Use exit to terminate session, or detach to keep it running
Encerrar uma sessão
./un session --kill unsb-vm-12345
Shells e REPLs disponíveis
Shells: bash, dash, sh, zsh, fish, ksh, tcsh, csh, elvish, xonsh, ash
REPLs: python3, bpython, ipython # Python
node # JavaScript
ruby, irb # Ruby
lua # Lua
php # PHP
perl # Perl
guile, scheme # Scheme
ghci # Haskell
erl, iex # Erlang/Elixir
sbcl, clisp # Common Lisp
r # R
julia # Julia
clojure # Clojure
Gerenciamento de Chave API
Verificar Status do Pagamento
# Check if your API key is valid
./un key
# Output:
# Valid: key expires in 30 days
Estender Chave Expirada
# Open the portal to extend an expired key
./un key --extend
# This opens the unsandbox.com portal where you can
# add more credits to extend your key's expiration
Autenticação
As credenciais são carregadas em ordem de prioridade (maior primeiro):
# 1. CLI flags (highest priority)
./un -p unsb-pk-xxxx -k unsb-sk-xxxxx script.py
# 2. Environment variables
export UNSANDBOX_PUBLIC_KEY=unsb-pk-xxxx-xxxx-xxxx-xxxx
export UNSANDBOX_SECRET_KEY=unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx
./un script.py
# 3. Config file (lowest priority)
# ~/.unsandbox/accounts.csv format: public_key,secret_key
mkdir -p ~/.unsandbox
echo "unsb-pk-xxxx-xxxx-xxxx-xxxx,unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx" > ~/.unsandbox/accounts.csv
./un script.py
As requisições são assinadas com HMAC-SHA256. O token bearer contém apenas a chave pública; a chave secreta calcula a assinatura (nunca é transmitida).
Escalonamento de Recursos
Definir Quantidade de vCPU
# Default: 1 vCPU, 2GB RAM
./un script.py
# Scale up: 4 vCPUs, 8GB RAM
./un -v 4 script.py
# Maximum: 8 vCPUs, 16GB RAM
./un --vcpu 8 heavy_compute.py
Reforço de Sessão Ao Vivo
# Boost a running session to 2 vCPU, 4GB RAM
./un session --boost sandbox-abc
# Boost to specific vCPU count (4 vCPU, 8GB RAM)
./un session --boost sandbox-abc --boost-vcpu 4
# Return to base resources (1 vCPU, 2GB RAM)
./un session --unboost sandbox-abc
Congelar/Descongelar Sessão
Congelar e Descongelar Sessões
# Freeze a session (stop billing, preserve state)
./un session --freeze sandbox-abc
# Unfreeze a frozen session
./un session --unfreeze sandbox-abc
# Note: Requires --tmux or --screen for persistence
Serviços Persistentes
Criar um Serviço
# Web server with ports
./un service --name web --ports 80,443 --bootstrap "python -m http.server 80"
# With custom domains
./un service --name blog --ports 8000 --domains blog.example.com
# Game server with SRV records
./un service --name mc --type minecraft --bootstrap ./setup.sh
# Deploy app tarball with bootstrap script
./un service --name app --ports 8000 -f app.tar.gz --bootstrap-file ./setup.sh
# setup.sh: cd /tmp && tar xzf app.tar.gz && ./app/start.sh
Gerenciar Serviços
# List all services
./un service --list
# Get service details
./un service --info abc123
# View bootstrap logs
./un service --logs abc123
./un service --tail abc123 # last 9000 lines
# Execute command in running service
./un service --execute abc123 'journalctl -u myapp -n 50'
# Dump bootstrap script (for migrations)
./un service --dump-bootstrap abc123
./un service --dump-bootstrap abc123 backup.sh
# Freeze/unfreeze service
./un service --freeze abc123
./un service --unfreeze abc123
# Service settings (auto-wake, freeze page display)
./un service --auto-unfreeze abc123 # enable auto-wake on HTTP
./un service --no-auto-unfreeze abc123 # disable auto-wake
./un service --show-freeze-page abc123 # show HTML payment page (default)
./un service --no-show-freeze-page abc123 # return JSON error instead
# Redeploy with new bootstrap
./un service --redeploy abc123 --bootstrap ./new-setup.sh
# Destroy service
./un service --destroy abc123
Snapshots
Listar Snapshots
./un snapshot --list
# Output:
# Snapshots: 3
#
# SNAPSHOT ID NAME SOURCE SIZE CREATED
# unsb-snapshot-a1b2-c3d4-e5f6-g7h8 before-upgrade session 512 MB 2h ago
# unsb-snapshot-i9j0-k1l2-m3n4-o5p6 stable-v1.0 service 1.2 GB 1d ago
Criar Snapshot da Sessão
# Snapshot with name
./un session --snapshot unsb-vm-12345 --name "before upgrade"
# Quick snapshot (auto-generated name)
./un session --snapshot unsb-vm-12345
Criar Snapshot do Serviço
# Standard snapshot (pauses container briefly)
./un service --snapshot unsb-service-abc123 --name "stable v1.0"
# Hot snapshot (no pause, may be inconsistent)
./un service --snapshot unsb-service-abc123 --hot
Restaurar a partir do Snapshot
# Restore session from snapshot
./un session --restore unsb-snapshot-a1b2-c3d4-e5f6-g7h8
# Restore service from snapshot
./un service --restore unsb-snapshot-i9j0-k1l2-m3n4-o5p6
Excluir Snapshot
./un snapshot --delete unsb-snapshot-a1b2-c3d4-e5f6-g7h8
Imagens
Imagens são imagens de container independentes e transferíveis que sobrevivem à exclusão do container. Diferente dos snapshots (que permanecem com seu container), imagens podem ser compartilhadas com outros usuários, transferidas entre chaves de API ou tornadas públicas no marketplace.
Listar Imagens
# List all images (owned + shared + public)
./un image --list
# List only your images
./un image --list owned
# List images shared with you
./un image --list shared
# List public marketplace images
./un image --list public
# Get image details
./un image --info unsb-image-xxxx-xxxx-xxxx-xxxx
Publicar Imagens
# Publish from a stopped or frozen service
./un image --publish-service unsb-service-abc123 \
--name "My App v1.0" --description "Production snapshot"
# Publish from a snapshot
./un image --publish-snapshot unsb-snapshot-xxxx-xxxx-xxxx-xxxx \
--name "Stable Release"
# Note: Cannot publish from running containers - stop or freeze first
Criar Serviços a partir de Imagens
# Spawn a new service from an image
./un image --spawn unsb-image-xxxx-xxxx-xxxx-xxxx \
--name new-service --ports 80,443
# Clone an image (creates a copy you own)
./un image --clone unsb-image-xxxx-xxxx-xxxx-xxxx
Proteção de Imagem
# Lock image to prevent accidental deletion
./un image --lock unsb-image-xxxx-xxxx-xxxx-xxxx
# Unlock image to allow deletion
./un image --unlock unsb-image-xxxx-xxxx-xxxx-xxxx
# Delete image (must be unlocked)
./un image --delete unsb-image-xxxx-xxxx-xxxx-xxxx
Visibilidade e Compartilhamento
# Set visibility level
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx private # owner only (default)
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx unlisted # can be shared
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx public # marketplace
# Share with specific user
./un image --grant unsb-image-xxxx-xxxx-xxxx-xxxx \
--key unsb-pk-friend-friend-friend-friend
# Revoke access
./un image --revoke unsb-image-xxxx-xxxx-xxxx-xxxx \
--key unsb-pk-friend-friend-friend-friend
# List who has access
./un image --trusted unsb-image-xxxx-xxxx-xxxx-xxxx
Transferir Propriedade
# Transfer image to another API key
./un image --transfer unsb-image-xxxx-xxxx-xxxx-xxxx \
--to unsb-pk-newowner-newowner-newowner-newowner
Referência de uso
Usage: ./un [options] <source_file>
./un session [options]
./un service [options]
./un snapshot [options]
./un image [options]
./un key
Commands:
(default) Execute source file in sandbox
session Open interactive shell/REPL session
service Manage persistent services
snapshot Manage container snapshots
image Manage container images (publish, share, transfer)
key Check API key validity and expiration
Options:
-e KEY=VALUE Set environment variable (can use multiple times)
-f FILE Add input file (can use multiple times)
-a Return and save artifacts from /tmp/artifacts/
-o DIR Output directory for artifacts (default: current dir)
-p KEY Public key (or set UNSANDBOX_PUBLIC_KEY env var)
-k KEY Secret key (or set UNSANDBOX_SECRET_KEY env var)
-n MODE Network mode: zerotrust (default) or semitrusted
-v N, --vcpu N vCPU count 1-8, each vCPU gets 2GB RAM (default: 1)
-y Skip confirmation for large uploads (>1GB)
-h Show this help
Authentication (priority order):
1. -p and -k flags (public and secret key)
2. UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars
3. ~/.unsandbox/accounts.csv (format: public_key,secret_key per line)
Session options:
-s, --shell SHELL Shell/REPL to use (default: bash)
-l, --list List active sessions
--attach ID Reconnect to existing session (ID or container name)
--kill ID Terminate a session (ID or container name)
--freeze ID Freeze a session (requires --tmux/--screen)
--unfreeze ID Unfreeze a frozen session
--boost ID Boost session resources (2 vCPU, 4GB RAM)
--boost-vcpu N Specify vCPU count for boost (1-8)
--unboost ID Return to base resources
--audit Record full session for auditing
--tmux Enable session persistence with tmux (allows reconnect)
--screen Enable session persistence with screen (allows reconnect)
Service options:
--name NAME Service name (creates new service)
--ports PORTS Comma-separated ports (e.g., 80,443)
--domains DOMAINS Custom domains (e.g., example.com,www.example.com)
--type TYPE Service type: minecraft, mumble, teamspeak, source, tcp, udp
--bootstrap CMD Bootstrap command/file/URL to run on startup
-f FILE Upload file to /tmp/ (can use multiple times)
-l, --list List all services
--info ID Get service details
--tail ID Get last 9000 lines of bootstrap logs
--logs ID Get all bootstrap logs
--freeze ID Freeze a service
--unfreeze ID Unfreeze a service
--auto-unfreeze ID Enable auto-wake on HTTP request
--no-auto-unfreeze ID Disable auto-wake on HTTP request
--show-freeze-page ID Show HTML payment page when frozen (default)
--no-show-freeze-page ID Return JSON error when frozen
--destroy ID Destroy a service
--redeploy ID Re-run bootstrap script (requires --bootstrap)
--execute ID CMD Run a command in a running service
--dump-bootstrap ID [FILE] Dump bootstrap script (for migrations)
--snapshot ID Create snapshot of session or service
--snapshot-name User-friendly name for snapshot
--hot Create snapshot without pausing (may be inconsistent)
--restore ID Restore session/service from snapshot ID
Snapshot options:
-l, --list List all snapshots
--info ID Get snapshot details
--delete ID Delete a snapshot permanently
Image options:
-l, --list [owned|shared|public] List images (all, owned, shared, or public)
--info ID Get image details
--publish-service ID Publish image from stopped/frozen service
--publish-snapshot ID Publish image from snapshot
--name NAME Name for published image
--description DESC Description for published image
--delete ID Delete image (must be unlocked)
--clone ID Clone image (creates copy you own)
--spawn ID Create service from image (requires --name)
--lock ID Lock image to prevent deletion
--unlock ID Unlock image to allow deletion
--visibility ID LEVEL Set visibility (private|unlisted|public)
--grant ID --key KEY Grant access to another API key
--revoke ID --key KEY Revoke access from API key
--transfer ID --to KEY Transfer ownership to API key
--trusted ID List API keys with access
Key options:
(no options) Check API key validity
--extend Open portal to extend an expired key
Examples:
./un script.py # execute Python script
./un -e DEBUG=1 script.py # with environment variable
./un -f data.csv process.py # with input file
./un -a -o ./bin main.c # save compiled artifacts
./un -v 4 heavy.py # with 4 vCPUs, 8GB RAM
./un session # interactive bash session
./un session --tmux # bash with reconnect support
./un session --list # list active sessions
./un session --attach unsb-vm-12345 # reconnect to session
./un session --kill unsb-vm-12345 # terminate a session
./un session --freeze unsb-vm-12345 # freeze session
./un session --unfreeze unsb-vm-12345 # unfreeze session
./un session --boost unsb-vm-12345 # boost resources
./un session --unboost unsb-vm-12345 # return to base
./un session --shell python3 # Python REPL
./un session --shell node # Node.js REPL
./un session -n semitrusted # session with network access
./un session --audit -o ./logs # record session for auditing
./un service --name web --ports 80 # create web service
./un service --list # list all services
./un service --logs abc123 # view bootstrap logs
./un key # check API key
./un key --extend # extend expired key
./un snapshot --list # list all snapshots
./un session --snapshot unsb-vm-123 # snapshot a session
./un service --snapshot abc123 # snapshot a service
./un session --restore unsb-snapshot-xxxx # restore from snapshot
./un image --list # list all images
./un image --list owned # list your images
./un image --publish-service abc # publish image from service
./un image --spawn img123 --name x # create service from image
./un image --grant img --key pk # share image with user
CLI Inception
O UN CLI foi implementado em 42 linguagens de programação, demonstrando que a API do unsandbox pode ser acessada de praticamente qualquer ambiente.
Ver Todas as 42 Implementações →
Licença
DOMÍNIO PÚBLICO - SEM LICENÇA, SEM GARANTIA
Este é software gratuito de domínio público para o bem público de um permacomputador hospedado
em permacomputer.com - um computador sempre ativo pelo povo, para o povo. Um que é
durável, fácil de reparar e distribuído como água da torneira para inteligência de
aprendizado de máquina.
O permacomputador é infraestrutura de propriedade comunitária otimizada em torno de quatro valores:
VERDADE - Primeiros princípios, matemática & ciência, código aberto distribuído livremente
LIBERDADE - Parcerias voluntárias, liberdade da tirania e controle corporativo
HARMONIA - Desperdício mínimo, sistemas auto-renováveis com diversas conexões prósperas
AMOR - Seja você mesmo sem ferir os outros, cooperação através da lei natural
Este software contribui para essa visão ao permitir a execução de código em mais de 42
linguagens de programação através de uma interface unificada, acessível a todos. Código são
sementes que brotam em qualquer tecnologia abandonada.
Saiba mais: https://www.permacomputer.com
Qualquer pessoa é livre para copiar, modificar, publicar, usar, compilar, vender ou distribuir
este software, seja em forma de código-fonte ou como binário compilado, para qualquer propósito,
comercial ou não comercial, e por qualquer meio.
SEM GARANTIA. O SOFTWARE É FORNECIDO "COMO ESTÁ" SEM GARANTIA DE QUALQUER TIPO.
Dito isso, a camada de membrana digital do nosso permacomputador executa continuamente testes
unitários, de integração e funcionais em todo o seu próprio software - com nosso permacomputador
monitorando a si mesmo, reparando a si mesmo, com orientação humana mínima no ciclo.
Nossos agentes fazem o seu melhor.
Copyright 2025 TimeHexOn & foxhop & russell@unturf
https://www.timehexon.com
https://www.foxhop.net
https://www.unturf.com/software