304 lines
12 KiB
D
304 lines
12 KiB
D
// 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
|
|
|
|
|
|
// UN CLI - D Implementation (using curl subprocess for simplicity)
|
|
// Compile: dmd un.d -of=un_d
|
|
// Or with LDC: ldc2 un.d -of=un_d
|
|
// Usage:
|
|
// un_d script.py
|
|
// un_d -e KEY=VALUE script.py
|
|
// un_d session --list
|
|
// un_d service --name web --ports 8080
|
|
|
|
import std.stdio;
|
|
import std.file;
|
|
import std.path;
|
|
import std.process;
|
|
import std.string;
|
|
import std.conv;
|
|
import std.array;
|
|
import std.algorithm;
|
|
|
|
immutable string API_BASE = "https://api.unsandbox.com";
|
|
immutable string BLUE = "\033[34m";
|
|
immutable string RED = "\033[31m";
|
|
immutable string GREEN = "\033[32m";
|
|
immutable string YELLOW = "\033[33m";
|
|
immutable string RESET = "\033[0m";
|
|
|
|
string detectLanguage(string filename) {
|
|
string[string] langMap = [
|
|
".py": "python", ".js": "javascript", ".ts": "typescript",
|
|
".go": "go", ".rs": "rust", ".c": "c", ".cpp": "cpp",
|
|
".d": "d", ".zig": "zig", ".nim": "nim", ".v": "v",
|
|
".rb": "ruby", ".php": "php", ".sh": "bash"
|
|
];
|
|
|
|
string ext = extension(filename);
|
|
return langMap.get(ext, "");
|
|
}
|
|
|
|
string escapeJson(string s) {
|
|
string result;
|
|
foreach (c; s) {
|
|
switch (c) {
|
|
case '"': result ~= "\\\""; break;
|
|
case '\\': result ~= "\\\\"; break;
|
|
case '\n': result ~= "\\n"; break;
|
|
case '\r': result ~= "\\r"; break;
|
|
case '\t': result ~= "\\t"; break;
|
|
default: result ~= c; break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
string execCurl(string cmd) {
|
|
auto result = executeShell(cmd);
|
|
return result.output;
|
|
}
|
|
|
|
void cmdExecute(string sourceFile, string[] envs, bool artifacts, string network, int vcpu, string apiKey) {
|
|
string lang = detectLanguage(sourceFile);
|
|
if (lang.empty) {
|
|
stderr.writefln("%sError: Cannot detect language%s", RED, RESET);
|
|
exit(1);
|
|
}
|
|
|
|
string code = readText(sourceFile);
|
|
string json = format(`{"language":"%s","code":"%s"`, lang, escapeJson(code));
|
|
|
|
if (envs.length > 0) {
|
|
json ~= `,"env":{`;
|
|
foreach (i, e; envs) {
|
|
auto parts = e.split("=");
|
|
if (parts.length == 2) {
|
|
if (i > 0) json ~= ",";
|
|
json ~= format(`"%s":"%s"`, parts[0], escapeJson(parts[1]));
|
|
}
|
|
}
|
|
json ~= "}";
|
|
}
|
|
|
|
if (artifacts) json ~= `,"return_artifacts":true`;
|
|
if (!network.empty) json ~= format(`,"network":"%s"`, network);
|
|
if (vcpu > 0) json ~= format(`,"vcpu":%d`, vcpu);
|
|
json ~= "}";
|
|
|
|
string cmd = format(`curl -s -X POST '%s/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '%s'`, API_BASE, apiKey, json);
|
|
string result = execCurl(cmd);
|
|
|
|
writeln(result);
|
|
}
|
|
|
|
void cmdSession(bool list, string kill, string shell, string network, int vcpu, bool tmux, bool screen, string apiKey) {
|
|
if (list) {
|
|
string cmd = format(`curl -s -X GET '%s/sessions' -H 'Authorization: Bearer %s'`, API_BASE, apiKey);
|
|
writeln(execCurl(cmd));
|
|
return;
|
|
}
|
|
|
|
if (!kill.empty) {
|
|
string cmd = format(`curl -s -X DELETE '%s/sessions/%s' -H 'Authorization: Bearer %s'`, API_BASE, kill, apiKey);
|
|
execCurl(cmd);
|
|
writefln("%sSession terminated: %s%s", GREEN, kill, RESET);
|
|
return;
|
|
}
|
|
|
|
string json = format(`{"shell":"%s"`, shell.empty ? "bash" : shell);
|
|
if (!network.empty) json ~= format(`,"network":"%s"`, network);
|
|
if (vcpu > 0) json ~= format(`,"vcpu":%d`, vcpu);
|
|
if (tmux) json ~= `,"persistence":"tmux"`;
|
|
if (screen) json ~= `,"persistence":"screen"`;
|
|
json ~= "}";
|
|
|
|
writefln("%sCreating session...%s", YELLOW, RESET);
|
|
string cmd = format(`curl -s -X POST '%s/sessions' -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '%s'`, API_BASE, apiKey, json);
|
|
writeln(execCurl(cmd));
|
|
}
|
|
|
|
void cmdService(string name, string ports, string bootstrap, bool list, string info, string logs, string tail, string sleep, string wake, string destroy, string network, int vcpu, string apiKey) {
|
|
if (list) {
|
|
string cmd = format(`curl -s -X GET '%s/services' -H 'Authorization: Bearer %s'`, API_BASE, apiKey);
|
|
writeln(execCurl(cmd));
|
|
return;
|
|
}
|
|
|
|
if (!info.empty) {
|
|
string cmd = format(`curl -s -X GET '%s/services/%s' -H 'Authorization: Bearer %s'`, API_BASE, info, apiKey);
|
|
writeln(execCurl(cmd));
|
|
return;
|
|
}
|
|
|
|
if (!logs.empty) {
|
|
string cmd = format(`curl -s -X GET '%s/services/%s/logs' -H 'Authorization: Bearer %s'`, API_BASE, logs, apiKey);
|
|
write(execCurl(cmd));
|
|
return;
|
|
}
|
|
|
|
if (!tail.empty) {
|
|
string cmd = format(`curl -s -X GET '%s/services/%s/logs?lines=9000' -H 'Authorization: Bearer %s'`, API_BASE, tail, apiKey);
|
|
write(execCurl(cmd));
|
|
return;
|
|
}
|
|
|
|
if (!sleep.empty) {
|
|
string cmd = format(`curl -s -X POST '%s/services/%s/sleep' -H 'Authorization: Bearer %s'`, API_BASE, sleep, apiKey);
|
|
execCurl(cmd);
|
|
writefln("%sService sleeping: %s%s", GREEN, sleep, RESET);
|
|
return;
|
|
}
|
|
|
|
if (!wake.empty) {
|
|
string cmd = format(`curl -s -X POST '%s/services/%s/wake' -H 'Authorization: Bearer %s'`, API_BASE, wake, apiKey);
|
|
execCurl(cmd);
|
|
writefln("%sService waking: %s%s", GREEN, wake, RESET);
|
|
return;
|
|
}
|
|
|
|
if (!destroy.empty) {
|
|
string cmd = format(`curl -s -X DELETE '%s/services/%s' -H 'Authorization: Bearer %s'`, API_BASE, destroy, apiKey);
|
|
execCurl(cmd);
|
|
writefln("%sService destroyed: %s%s", GREEN, destroy, RESET);
|
|
return;
|
|
}
|
|
|
|
if (!name.empty) {
|
|
string json = format(`{"name":"%s"`, name);
|
|
if (!ports.empty) json ~= format(`,"ports":[%s]`, ports);
|
|
if (!bootstrap.empty) {
|
|
if (exists(bootstrap)) {
|
|
string bootCode = readText(bootstrap);
|
|
json ~= format(`,"bootstrap":"%s"`, escapeJson(bootCode));
|
|
} else {
|
|
json ~= format(`,"bootstrap":"%s"`, escapeJson(bootstrap));
|
|
}
|
|
}
|
|
if (!network.empty) json ~= format(`,"network":"%s"`, network);
|
|
if (vcpu > 0) json ~= format(`,"vcpu":%d`, vcpu);
|
|
json ~= "}";
|
|
|
|
writefln("%sCreating service...%s", YELLOW, RESET);
|
|
string cmd = format(`curl -s -X POST '%s/services' -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '%s'`, API_BASE, apiKey, json);
|
|
writeln(execCurl(cmd));
|
|
return;
|
|
}
|
|
|
|
stderr.writefln("%sError: Specify --name to create a service%s", RED, RESET);
|
|
exit(1);
|
|
}
|
|
|
|
int main(string[] args) {
|
|
string apiKey = environment.get("UNSANDBOX_API_KEY", "");
|
|
|
|
if (args.length < 2) {
|
|
stderr.writefln("Usage: %s [options] <source_file>", args[0]);
|
|
stderr.writefln(" %s session [options]", args[0]);
|
|
stderr.writefln(" %s service [options]", args[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (args[1] == "session") {
|
|
bool list = false;
|
|
string kill, shell, network;
|
|
int vcpu = 0;
|
|
bool tmux = false, screen = false;
|
|
|
|
for (size_t i = 2; i < args.length; i++) {
|
|
if (args[i] == "--list") list = true;
|
|
else if (args[i] == "--kill" && i+1 < args.length) kill = args[++i];
|
|
else if (args[i] == "--shell" && i+1 < args.length) shell = args[++i];
|
|
else if (args[i] == "-n" && i+1 < args.length) network = args[++i];
|
|
else if (args[i] == "-v" && i+1 < args.length) vcpu = to!int(args[++i]);
|
|
else if (args[i] == "--tmux") tmux = true;
|
|
else if (args[i] == "--screen") screen = true;
|
|
else if (args[i] == "-k" && i+1 < args.length) apiKey = args[++i];
|
|
}
|
|
|
|
cmdSession(list, kill, shell, network, vcpu, tmux, screen, apiKey);
|
|
return 0;
|
|
}
|
|
|
|
if (args[1] == "service") {
|
|
string name, ports, bootstrap;
|
|
bool list = false;
|
|
string info, logs, tail, sleep, wake, destroy, network;
|
|
int vcpu = 0;
|
|
|
|
for (size_t i = 2; i < args.length; i++) {
|
|
if (args[i] == "--name" && i+1 < args.length) name = args[++i];
|
|
else if (args[i] == "--ports" && i+1 < args.length) ports = args[++i];
|
|
else if (args[i] == "--bootstrap" && i+1 < args.length) bootstrap = args[++i];
|
|
else if (args[i] == "--list") list = true;
|
|
else if (args[i] == "--info" && i+1 < args.length) info = args[++i];
|
|
else if (args[i] == "--logs" && i+1 < args.length) logs = args[++i];
|
|
else if (args[i] == "--tail" && i+1 < args.length) tail = args[++i];
|
|
else if (args[i] == "--sleep" && i+1 < args.length) sleep = args[++i];
|
|
else if (args[i] == "--wake" && i+1 < args.length) wake = args[++i];
|
|
else if (args[i] == "--destroy" && i+1 < args.length) destroy = args[++i];
|
|
else if (args[i] == "-n" && i+1 < args.length) network = args[++i];
|
|
else if (args[i] == "-v" && i+1 < args.length) vcpu = to!int(args[++i]);
|
|
else if (args[i] == "-k" && i+1 < args.length) apiKey = args[++i];
|
|
}
|
|
|
|
cmdService(name, ports, bootstrap, list, info, logs, tail, sleep, wake, destroy, network, vcpu, apiKey);
|
|
return 0;
|
|
}
|
|
|
|
// Execute mode
|
|
string[] envs;
|
|
bool artifacts = false;
|
|
string network, sourceFile;
|
|
int vcpu = 0;
|
|
|
|
for (size_t i = 1; i < args.length; i++) {
|
|
if (args[i] == "-e" && i+1 < args.length) envs ~= args[++i];
|
|
else if (args[i] == "-a") artifacts = true;
|
|
else if (args[i] == "-n" && i+1 < args.length) network = args[++i];
|
|
else if (args[i] == "-v" && i+1 < args.length) vcpu = to!int(args[++i]);
|
|
else if (args[i] == "-k" && i+1 < args.length) apiKey = args[++i];
|
|
else if (!args[i].startsWith("-")) sourceFile = args[i];
|
|
}
|
|
|
|
if (sourceFile.empty) {
|
|
stderr.writefln("%sError: No source file specified%s", RED, RESET);
|
|
return 1;
|
|
}
|
|
|
|
cmdExecute(sourceFile, envs, artifacts, network, vcpu, apiKey);
|
|
return 0;
|
|
}
|