feat(cli): Add languages and image commands to all 42 SDKs

- Add `un languages [--json]` command to list available execution languages
- Add `un image` command with full image management:
  - --list, --info, --delete, --lock, --unlock
  - --publish, --visibility, --spawn, --clone
- Update CLI_SPEC.md with new command documentation
- All implementations follow consistent patterns
This commit is contained in:
russell@unturf.com 2026-01-17 10:25:20 -05:00
parent aa1d14289e
commit af995cdc1a
42 changed files with 9098 additions and 70 deletions

View file

@ -637,6 +637,55 @@ function cmd_key(do_extend) {
validate_key(do_extend)
}
function languages_list(json_output , timestamp, sig_headers, signature, sig_input, sig_cmd, line, response, i, lang) {
get_api_keys()
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":GET:/languages:"
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "'"
}
cmd = "curl -s '" API_BASE "/languages' -H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " sig_headers
response = ""
while ((cmd | getline line) > 0) {
response = response line
}
close(cmd)
if (json_output) {
# Output raw JSON array of language names
# Extract languages array and convert to simple array
if (match(response, /"languages":\[([^\]]*)\]/, arr)) {
# Parse out language names from the array
content = arr[1]
printf "["
first = 1
while (match(content, /"name":"([^"]*)"/, m)) {
if (!first) printf ","
printf "\"%s\"", m[1]
first = 0
content = substr(content, RSTART + RLENGTH)
}
printf "]\n"
} else {
# Fallback: just print raw response
print response
}
} else {
# Output one language per line
if (match(response, /"languages":\[([^\]]*)\]/, arr)) {
content = arr[1]
while (match(content, /"name":"([^"]*)"/, m)) {
print m[1]
content = substr(content, RSTART + RLENGTH)
}
}
}
}
function snapshot_list( timestamp, sig_headers, signature, sig_input, sig_cmd) {
get_api_keys()
timestamp = systime()
@ -687,6 +736,244 @@ function snapshot_delete(id , timestamp, sig_headers, signature, sig_input, s
print GREEN "Snapshot deleted: " id RESET
}
# Image functions
function image_list( timestamp, sig_headers, signature, sig_input, sig_cmd) {
get_api_keys()
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":GET:/images:"
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "'"
}
cmd = "curl -s '" API_BASE "/images' -H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " sig_headers
while ((cmd | getline line) > 0) print line
close(cmd)
}
function image_info(id , timestamp, sig_headers, signature, sig_input, sig_cmd, endpoint) {
get_api_keys()
endpoint = "/images/" id
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":GET:" endpoint ":"
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "'"
}
cmd = "curl -s '" API_BASE endpoint "' -H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " sig_headers
while ((cmd | getline line) > 0) print line
close(cmd)
}
function image_delete(id , timestamp, sig_headers, signature, sig_input, sig_cmd, endpoint) {
get_api_keys()
endpoint = "/images/" id
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":DELETE:" endpoint ":"
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "'"
}
cmd = "curl -s -X DELETE '" API_BASE endpoint "' -H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " sig_headers
system(cmd)
print GREEN "Image deleted: " id RESET
}
function image_lock(id , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd) {
get_api_keys()
endpoint = "/images/" id "/lock"
json = "{}"
tmp = "/tmp/un_awk_img_" PROCINFO["pid"] ".json"
print json > tmp
close(tmp)
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":POST:" endpoint ":" json
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "' "
}
cmd = "curl -s -X POST '" API_BASE endpoint "' " \
"-H 'Content-Type: application/json' " \
"-H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " \
sig_headers \
"-d '@" tmp "'"
system(cmd " > /dev/null")
system("rm -f " tmp)
print GREEN "Image locked: " id RESET
}
function image_unlock(id , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd) {
get_api_keys()
endpoint = "/images/" id "/unlock"
json = "{}"
tmp = "/tmp/un_awk_img_" PROCINFO["pid"] ".json"
print json > tmp
close(tmp)
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":POST:" endpoint ":" json
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "' "
}
cmd = "curl -s -X POST '" API_BASE endpoint "' " \
"-H 'Content-Type: application/json' " \
"-H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " \
sig_headers \
"-d '@" tmp "'"
system(cmd " > /dev/null")
system("rm -f " tmp)
print GREEN "Image unlocked: " id RESET
}
function image_publish(source_id, source_type, name , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd, line, response) {
get_api_keys()
endpoint = "/images/publish"
json = "{\"source_type\":\"" source_type "\",\"source_id\":\"" source_id "\""
if (name != "") {
json = json ",\"name\":\"" escape_json(name) "\""
}
json = json "}"
tmp = "/tmp/un_awk_img_" PROCINFO["pid"] ".json"
print json > tmp
close(tmp)
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":POST:" endpoint ":" json
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "' "
}
cmd = "curl -s -X POST '" API_BASE endpoint "' " \
"-H 'Content-Type: application/json' " \
"-H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " \
sig_headers \
"-d '@" tmp "'"
response = ""
while ((cmd | getline line) > 0) {
response = response line
}
close(cmd)
system("rm -f " tmp)
print GREEN "Image published" RESET
print response
}
function image_visibility(id, visibility , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd) {
get_api_keys()
endpoint = "/images/" id "/visibility"
json = "{\"visibility\":\"" visibility "\"}"
tmp = "/tmp/un_awk_img_" PROCINFO["pid"] ".json"
print json > tmp
close(tmp)
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":POST:" endpoint ":" json
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "' "
}
cmd = "curl -s -X POST '" API_BASE endpoint "' " \
"-H 'Content-Type: application/json' " \
"-H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " \
sig_headers \
"-d '@" tmp "'"
system(cmd " > /dev/null")
system("rm -f " tmp)
print GREEN "Image visibility set to: " visibility RESET
}
function image_spawn(id, name, ports , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd, line, response) {
get_api_keys()
endpoint = "/images/" id "/spawn"
json = "{"
if (name != "") {
json = json "\"name\":\"" escape_json(name) "\""
}
if (ports != "") {
if (name != "") json = json ","
json = json "\"ports\":[" ports "]"
}
json = json "}"
tmp = "/tmp/un_awk_img_" PROCINFO["pid"] ".json"
print json > tmp
close(tmp)
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":POST:" endpoint ":" json
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "' "
}
cmd = "curl -s -X POST '" API_BASE endpoint "' " \
"-H 'Content-Type: application/json' " \
"-H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " \
sig_headers \
"-d '@" tmp "'"
response = ""
while ((cmd | getline line) > 0) {
response = response line
}
close(cmd)
system("rm -f " tmp)
print GREEN "Service spawned from image" RESET
print response
}
function image_clone(id, name , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd, line, response) {
get_api_keys()
endpoint = "/images/" id "/clone"
json = "{"
if (name != "") {
json = json "\"name\":\"" escape_json(name) "\""
}
json = json "}"
tmp = "/tmp/un_awk_img_" PROCINFO["pid"] ".json"
print json > tmp
close(tmp)
timestamp = systime()
sig_headers = ""
if (GLOBAL_SECRET_KEY != "") {
sig_input = timestamp ":POST:" endpoint ":" json
sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'"
sig_cmd | getline signature
close(sig_cmd)
sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "' "
}
cmd = "curl -s -X POST '" API_BASE endpoint "' " \
"-H 'Content-Type: application/json' " \
"-H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " \
sig_headers \
"-d '@" tmp "'"
response = ""
while ((cmd | getline line) > 0) {
response = response line
}
close(cmd)
system("rm -f " tmp)
print GREEN "Image cloned" RESET
print response
}
function session_snapshot(id, name, hot , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd, line, response) {
get_api_keys()
endpoint = "/sessions/" id "/snapshot"
@ -1010,6 +1297,7 @@ function service_env_delete(id , endpoint, timestamp, sig_headers, signature,
function show_help() {
print "Usage: awk -f un.awk <source_file>"
print " awk -f un.awk languages [--json]"
print " awk -f un.awk session --list"
print " awk -f un.awk session --kill ID"
print " awk -f un.awk session [-s SHELL] [-f FILE]..."
@ -1030,6 +1318,15 @@ function show_help() {
print " awk -f un.awk snapshot --list"
print " awk -f un.awk snapshot --info ID"
print " awk -f un.awk snapshot --delete ID"
print " awk -f un.awk image --list"
print " awk -f un.awk image --info ID"
print " awk -f un.awk image --delete ID"
print " awk -f un.awk image --lock ID"
print " awk -f un.awk image --unlock ID"
print " awk -f un.awk image --publish ID --source-type TYPE [--name NAME]"
print " awk -f un.awk image --visibility ID MODE"
print " awk -f un.awk image --spawn ID [--name NAME] [--ports PORTS]"
print " awk -f un.awk image --clone ID [--name NAME]"
print ""
print "Session options:"
print " -s, --shell SHELL Shell to use (default: bash)"
@ -1063,11 +1360,28 @@ function show_help() {
print " export ID Export vault contents"
print " delete ID Delete vault"
print ""
print "Languages options:"
print " --json Output as JSON array (for scripts)"
print ""
print "Snapshot options:"
print " -l, --list List all snapshots"
print " --info ID Get snapshot details"
print " --delete ID Delete a snapshot"
print ""
print "Image options:"
print " -l, --list List all images"
print " --info ID Get image details"
print " --delete ID Delete an image"
print " --lock ID Lock image to prevent deletion"
print " --unlock ID Unlock image"
print " --publish ID Publish image from service/snapshot (requires --source-type)"
print " --source-type TYPE Source type: service or snapshot"
print " --visibility ID MODE Set visibility: private, unlisted, or public"
print " --spawn ID Spawn new service from image"
print " --clone ID Clone an image"
print " --name NAME Name for spawned service or cloned image"
print " --ports PORTS Ports for spawned service"
print ""
print "Requires: UNSANDBOX_API_KEY environment variable"
}
@ -1159,6 +1473,15 @@ END {
exit 0
}
if (ARGV[1] == "languages") {
json_output = 0
if (ARGC >= 3 && ARGV[2] == "--json") {
json_output = 1
}
languages_list(json_output)
exit 0
}
if (ARGV[1] == "snapshot") {
if (ARGC >= 3 && (ARGV[2] == "--list" || ARGV[2] == "-l")) {
snapshot_list()
@ -1172,6 +1495,83 @@ END {
exit 0
}
if (ARGV[1] == "image") {
if (ARGC >= 3 && (ARGV[2] == "--list" || ARGV[2] == "-l")) {
image_list()
} else if (ARGC >= 4 && ARGV[2] == "--info") {
image_info(ARGV[3])
} else if (ARGC >= 4 && ARGV[2] == "--delete") {
image_delete(ARGV[3])
} else if (ARGC >= 4 && ARGV[2] == "--lock") {
image_lock(ARGV[3])
} else if (ARGC >= 4 && ARGV[2] == "--unlock") {
image_unlock(ARGV[3])
} else if (ARGC >= 4 && ARGV[2] == "--publish") {
# Parse --source-type and --name options
img_source_id = ARGV[3]
img_source_type = ""
img_name = ""
i = 4
while (i < ARGC) {
if (ARGV[i] == "--source-type" && i + 1 < ARGC) {
img_source_type = ARGV[i + 1]
i += 2
} else if (ARGV[i] == "--name" && i + 1 < ARGC) {
img_name = ARGV[i + 1]
i += 2
} else {
i++
}
}
if (img_source_type == "") {
print RED "Error: --publish requires --source-type (service or snapshot)" RESET > "/dev/stderr"
exit 1
}
image_publish(img_source_id, img_source_type, img_name)
} else if (ARGC >= 5 && ARGV[2] == "--visibility") {
image_visibility(ARGV[3], ARGV[4])
} else if (ARGC >= 4 && ARGV[2] == "--spawn") {
# Parse --name and --ports options
img_id = ARGV[3]
img_name = ""
img_ports = ""
i = 4
while (i < ARGC) {
if (ARGV[i] == "--name" && i + 1 < ARGC) {
img_name = ARGV[i + 1]
i += 2
} else if (ARGV[i] == "--ports" && i + 1 < ARGC) {
img_ports = ARGV[i + 1]
i += 2
} else {
i++
}
}
image_spawn(img_id, img_name, img_ports)
} else if (ARGC >= 4 && ARGV[2] == "--clone") {
# Parse --name option
img_id = ARGV[3]
img_name = ""
i = 4
while (i < ARGC) {
if (ARGV[i] == "--name" && i + 1 < ARGC) {
img_name = ARGV[i + 1]
i += 2
} else {
i++
}
}
image_clone(img_id, img_name)
} else {
print "Usage: awk -f un.awk image --list|--info ID|--delete ID|--lock ID|--unlock ID"
print " awk -f un.awk image --publish ID --source-type TYPE [--name NAME]"
print " awk -f un.awk image --visibility ID MODE"
print " awk -f un.awk image --spawn ID [--name NAME] [--ports PORTS]"
print " awk -f un.awk image --clone ID [--name NAME]"
}
exit 0
}
if (ARGV[1] == "service") {
if (ARGC >= 3 && ARGV[2] == "--list") {
service_list()