249 lines
10 KiB
Scheme
249 lines
10 KiB
Scheme
;; 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 guile
|
|
!#
|
|
|
|
;;; Scheme UN CLI - Unsandbox CLI Client
|
|
;;;
|
|
;;; Full-featured CLI matching un.py capabilities
|
|
;;; Uses curl for HTTP (no external dependencies)
|
|
|
|
(use-modules (ice-9 popen)
|
|
(ice-9 rdelim)
|
|
(ice-9 format))
|
|
|
|
(define blue "\x1b[34m")
|
|
(define red "\x1b[31m")
|
|
(define green "\x1b[32m")
|
|
(define yellow "\x1b[33m")
|
|
(define reset "\x1b[0m")
|
|
|
|
(define ext-map
|
|
'((".hs" . "haskell") (".ml" . "ocaml") (".clj" . "clojure")
|
|
(".scm" . "scheme") (".lisp" . "commonlisp") (".erl" . "erlang")
|
|
(".ex" . "elixir") (".exs" . "elixir") (".py" . "python")
|
|
(".js" . "javascript") (".ts" . "typescript") (".rb" . "ruby")
|
|
(".go" . "go") (".rs" . "rust") (".c" . "c") (".cpp" . "cpp")
|
|
(".cc" . "cpp") (".java" . "java") (".kt" . "kotlin")
|
|
(".cs" . "csharp") (".fs" . "fsharp") (".jl" . "julia")
|
|
(".r" . "r") (".cr" . "crystal") (".d" . "d") (".nim" . "nim")
|
|
(".zig" . "zig") (".v" . "v") (".dart" . "dart") (".sh" . "bash")
|
|
(".pl" . "perl") (".lua" . "lua") (".php" . "php")))
|
|
|
|
(define (get-extension filename)
|
|
(let ((dot-pos (string-rindex filename #\.)))
|
|
(if dot-pos (substring filename dot-pos) "")))
|
|
|
|
(define (escape-json s)
|
|
(string-append
|
|
(string-concatenate
|
|
(map (lambda (c)
|
|
(cond
|
|
((char=? c #\\) "\\\\")
|
|
((char=? c #\") "\\\"")
|
|
((char=? c #\newline) "\\n")
|
|
((char=? c #\return) "\\r")
|
|
((char=? c #\tab) "\\t")
|
|
(else (string c))))
|
|
(string->list s)))))
|
|
|
|
(define (read-file filename)
|
|
(call-with-input-file filename
|
|
(lambda (port)
|
|
(let loop ((lines '()))
|
|
(let ((line (read-line port)))
|
|
(if (eof-object? line)
|
|
(string-join (reverse lines) "\n")
|
|
(loop (cons line lines))))))))
|
|
|
|
(define (write-temp-file data)
|
|
(let ((tmp-file (format #f "/tmp/un_scm_~a.json" (random 999999))))
|
|
(call-with-output-file tmp-file
|
|
(lambda (port) (display data port)))
|
|
tmp-file))
|
|
|
|
(define (curl-post api-key endpoint json-data)
|
|
(let* ((tmp-file (write-temp-file json-data))
|
|
(cmd (format #f "curl -s -X POST https://api.unsandbox.com~a -H 'Content-Type: application/json' -H 'Authorization: Bearer ~a' -d @~a"
|
|
endpoint api-key tmp-file))
|
|
(port (open-input-pipe cmd))
|
|
(output (let loop ((lines '()))
|
|
(let ((line (read-line port)))
|
|
(if (eof-object? line)
|
|
(string-join (reverse lines) "\n")
|
|
(loop (cons line lines)))))))
|
|
(close-pipe port)
|
|
(delete-file tmp-file)
|
|
output))
|
|
|
|
(define (curl-get api-key endpoint)
|
|
(let* ((cmd (format #f "curl -s https://api.unsandbox.com~a -H 'Authorization: Bearer ~a'"
|
|
endpoint api-key))
|
|
(port (open-input-pipe cmd))
|
|
(output (let loop ((lines '()))
|
|
(let ((line (read-line port)))
|
|
(if (eof-object? line)
|
|
(string-join (reverse lines) "\n")
|
|
(loop (cons line lines)))))))
|
|
(close-pipe port)
|
|
output))
|
|
|
|
(define (curl-delete api-key endpoint)
|
|
(let* ((cmd (format #f "curl -s -X DELETE https://api.unsandbox.com~a -H 'Authorization: Bearer ~a'"
|
|
endpoint api-key))
|
|
(port (open-input-pipe cmd))
|
|
(output (let loop ((lines '()))
|
|
(let ((line (read-line port)))
|
|
(if (eof-object? line)
|
|
(string-join (reverse lines) "\n")
|
|
(loop (cons line lines)))))))
|
|
(close-pipe port)
|
|
output))
|
|
|
|
(define (get-api-key)
|
|
(or (getenv "UNSANDBOX_API_KEY")
|
|
(begin
|
|
(display "Error: UNSANDBOX_API_KEY not set\n" (current-error-port))
|
|
(exit 1))))
|
|
|
|
(define (execute-cmd file)
|
|
(let* ((api-key (get-api-key))
|
|
(ext (get-extension file))
|
|
(language (assoc-ref ext-map ext)))
|
|
(when (not language)
|
|
(format (current-error-port) "Error: Unknown extension: ~a\n" ext)
|
|
(exit 1))
|
|
(let* ((code (read-file file))
|
|
(json (format #f "{\"language\":\"~a\",\"code\":\"~a\"}"
|
|
language (escape-json code)))
|
|
(response (curl-post api-key "/execute" json)))
|
|
(display response)
|
|
(newline))))
|
|
|
|
(define (session-cmd action id shell)
|
|
(let ((api-key (get-api-key)))
|
|
(cond
|
|
((equal? action "list")
|
|
(display (curl-get api-key "/sessions"))
|
|
(newline))
|
|
((equal? action "kill")
|
|
(curl-delete api-key (format #f "/sessions/~a" id))
|
|
(format #t "~aSession terminated: ~a~a\n" green id reset))
|
|
(else
|
|
(let* ((sh (or shell "bash"))
|
|
(json (format #f "{\"shell\":\"~a\"}" sh))
|
|
(response (curl-post api-key "/sessions" json)))
|
|
(format #t "~aSession created (WebSocket required)~a\n" yellow reset)
|
|
(display response)
|
|
(newline))))))
|
|
|
|
(define (service-cmd action id name ports bootstrap)
|
|
(let ((api-key (get-api-key)))
|
|
(cond
|
|
((equal? action "list")
|
|
(display (curl-get api-key "/services"))
|
|
(newline))
|
|
((equal? action "info")
|
|
(display (curl-get api-key (format #f "/services/~a" id)))
|
|
(newline))
|
|
((equal? action "logs")
|
|
(display (curl-get api-key (format #f "/services/~a/logs" id)))
|
|
(newline))
|
|
((equal? action "sleep")
|
|
(curl-post api-key (format #f "/services/~a/sleep" id) "{}")
|
|
(format #t "~aService sleeping: ~a~a\n" green id reset))
|
|
((equal? action "wake")
|
|
(curl-post api-key (format #f "/services/~a/wake" id) "{}")
|
|
(format #t "~aService waking: ~a~a\n" green id reset))
|
|
((equal? action "destroy")
|
|
(curl-delete api-key (format #f "/services/~a" id))
|
|
(format #t "~aService destroyed: ~a~a\n" green id reset))
|
|
((and (equal? action "create") name)
|
|
(let* ((ports-json (if ports (format #f ",\"ports\":[~a]" ports) ""))
|
|
(bootstrap-json (if bootstrap (format #f ",\"bootstrap\":\"~a\"" (escape-json bootstrap)) ""))
|
|
(json (format #f "{\"name\":\"~a\"~a~a}" name ports-json bootstrap-json))
|
|
(response (curl-post api-key "/services" json)))
|
|
(format #t "~aService created~a\n" green reset)
|
|
(display response)
|
|
(newline)))
|
|
(else
|
|
(display "Error: --name required to create service\n" (current-error-port))
|
|
(exit 1)))))
|
|
|
|
(define (main args)
|
|
(if (null? args)
|
|
(begin
|
|
(display "Usage: un.scm [options] <source_file>\n")
|
|
(display " un.scm session [options]\n")
|
|
(display " un.scm service [options]\n")
|
|
(exit 1))
|
|
(cond
|
|
((equal? (car args) "session")
|
|
(if (and (> (length args) 1) (equal? (cadr args) "--list"))
|
|
(session-cmd "list" #f #f)
|
|
(if (and (> (length args) 2) (equal? (cadr args) "--kill"))
|
|
(session-cmd "kill" (caddr args) #f)
|
|
(session-cmd "create" #f #f))))
|
|
((equal? (car args) "service")
|
|
(cond
|
|
((and (> (length args) 1) (equal? (cadr args) "--list"))
|
|
(service-cmd "list" #f #f #f #f))
|
|
((and (> (length args) 2) (equal? (cadr args) "--info"))
|
|
(service-cmd "info" (caddr args) #f #f #f))
|
|
((and (> (length args) 2) (equal? (cadr args) "--logs"))
|
|
(service-cmd "logs" (caddr args) #f #f #f))
|
|
((and (> (length args) 2) (equal? (cadr args) "--sleep"))
|
|
(service-cmd "sleep" (caddr args) #f #f #f))
|
|
((and (> (length args) 2) (equal? (cadr args) "--wake"))
|
|
(service-cmd "wake" (caddr args) #f #f #f))
|
|
((and (> (length args) 2) (equal? (cadr args) "--destroy"))
|
|
(service-cmd "destroy" (caddr args) #f #f #f))
|
|
((and (> (length args) 2) (equal? (cadr args) "--name"))
|
|
(let ((name (caddr args))
|
|
(ports (if (and (> (length args) 4) (equal? (list-ref args 3) "--ports"))
|
|
(list-ref args 4) #f))
|
|
(bootstrap (if (and (> (length args) 6) (equal? (list-ref args 5) "--bootstrap"))
|
|
(list-ref args 6) #f)))
|
|
(service-cmd "create" #f name ports bootstrap)))
|
|
(else
|
|
(display "Error: Invalid service command\n" (current-error-port))
|
|
(exit 1))))
|
|
(else
|
|
(execute-cmd (car args))))))
|
|
|
|
(main (cdr (command-line)))
|