748 lines
26 KiB
C#
748 lines
26 KiB
C#
// 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.cs - Unsandbox CLI Client (C# Implementation)
|
|
// Compile: csc Un.cs (or mcs Un.cs on Linux)
|
|
// Run: Un.exe [options] <source_file>
|
|
// Requires: UNSANDBOX_API_KEY environment variable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text;
|
|
|
|
class Un
|
|
{
|
|
private const string API_BASE = "https://api.unsandbox.com";
|
|
private const string BLUE = "\x1B[34m";
|
|
private const string RED = "\x1B[31m";
|
|
private const string GREEN = "\x1B[32m";
|
|
private const string YELLOW = "\x1B[33m";
|
|
private const string RESET = "\x1B[0m";
|
|
|
|
private static readonly Dictionary<string, string> ExtMap = new Dictionary<string, string>
|
|
{
|
|
{".py", "python"}, {".js", "javascript"}, {".ts", "typescript"},
|
|
{".rb", "ruby"}, {".php", "php"}, {".pl", "perl"}, {".lua", "lua"},
|
|
{".sh", "bash"}, {".go", "go"}, {".rs", "rust"}, {".c", "c"},
|
|
{".cpp", "cpp"}, {".cc", "cpp"}, {".cxx", "cpp"},
|
|
{".java", "java"}, {".kt", "kotlin"}, {".cs", "csharp"}, {".fs", "fsharp"},
|
|
{".hs", "haskell"}, {".ml", "ocaml"}, {".clj", "clojure"}, {".scm", "scheme"},
|
|
{".lisp", "commonlisp"}, {".erl", "erlang"}, {".ex", "elixir"}, {".exs", "elixir"},
|
|
{".jl", "julia"}, {".r", "r"}, {".R", "r"}, {".cr", "crystal"},
|
|
{".d", "d"}, {".nim", "nim"}, {".zig", "zig"}, {".v", "v"},
|
|
{".dart", "dart"}, {".groovy", "groovy"}, {".scala", "scala"},
|
|
{".f90", "fortran"}, {".f95", "fortran"}, {".cob", "cobol"},
|
|
{".pro", "prolog"}, {".forth", "forth"}, {".4th", "forth"},
|
|
{".tcl", "tcl"}, {".raku", "raku"}, {".m", "objc"}
|
|
};
|
|
|
|
static void Main(string[] args)
|
|
{
|
|
try
|
|
{
|
|
var parsedArgs = ParseArgs(args);
|
|
|
|
if (parsedArgs.Command == "session")
|
|
{
|
|
CmdSession(parsedArgs);
|
|
}
|
|
else if (parsedArgs.Command == "service")
|
|
{
|
|
CmdService(parsedArgs);
|
|
}
|
|
else if (parsedArgs.SourceFile != null)
|
|
{
|
|
CmdExecute(parsedArgs);
|
|
}
|
|
else
|
|
{
|
|
PrintHelp();
|
|
Environment.Exit(1);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"{RED}Error: {ex.Message}{RESET}");
|
|
Environment.Exit(1);
|
|
}
|
|
}
|
|
|
|
static void CmdExecute(Args args)
|
|
{
|
|
string apiKey = GetApiKey(args.ApiKey);
|
|
string code = File.ReadAllText(args.SourceFile);
|
|
string language = DetectLanguage(args.SourceFile);
|
|
|
|
var payload = new Dictionary<string, object>
|
|
{
|
|
["language"] = language,
|
|
["code"] = code
|
|
};
|
|
|
|
if (args.Env.Count > 0)
|
|
{
|
|
var envVars = new Dictionary<string, string>();
|
|
foreach (var e in args.Env)
|
|
{
|
|
var parts = e.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
envVars[parts[0]] = parts[1];
|
|
}
|
|
}
|
|
if (envVars.Count > 0)
|
|
{
|
|
payload["env"] = envVars;
|
|
}
|
|
}
|
|
|
|
if (args.Files.Count > 0)
|
|
{
|
|
var inputFiles = new List<Dictionary<string, string>>();
|
|
foreach (var filepath in args.Files)
|
|
{
|
|
var content = File.ReadAllBytes(filepath);
|
|
inputFiles.Add(new Dictionary<string, string>
|
|
{
|
|
["filename"] = Path.GetFileName(filepath),
|
|
["content_base64"] = Convert.ToBase64String(content)
|
|
});
|
|
}
|
|
payload["input_files"] = inputFiles;
|
|
}
|
|
|
|
if (args.Artifacts)
|
|
{
|
|
payload["return_artifacts"] = true;
|
|
}
|
|
if (args.Network != null)
|
|
{
|
|
payload["network"] = args.Network;
|
|
}
|
|
if (args.Vcpu > 0)
|
|
{
|
|
payload["vcpu"] = args.Vcpu;
|
|
}
|
|
|
|
var result = ApiRequest("/execute", "POST", payload, apiKey);
|
|
|
|
if (result.ContainsKey("stdout") && !string.IsNullOrEmpty((string)result["stdout"]))
|
|
{
|
|
Console.Write($"{BLUE}{result["stdout"]}{RESET}");
|
|
}
|
|
if (result.ContainsKey("stderr") && !string.IsNullOrEmpty((string)result["stderr"]))
|
|
{
|
|
Console.Error.Write($"{RED}{result["stderr"]}{RESET}");
|
|
}
|
|
|
|
if (args.Artifacts && result.ContainsKey("artifacts"))
|
|
{
|
|
var artifacts = result["artifacts"] as List<object>;
|
|
string outDir = args.OutputDir ?? ".";
|
|
Directory.CreateDirectory(outDir);
|
|
foreach (Dictionary<string, object> artifact in artifacts)
|
|
{
|
|
string filename = artifact.ContainsKey("filename") ? (string)artifact["filename"] : "artifact";
|
|
byte[] content = Convert.FromBase64String((string)artifact["content_base64"]);
|
|
string path = Path.Combine(outDir, filename);
|
|
File.WriteAllBytes(path, content);
|
|
Console.Error.WriteLine($"{GREEN}Saved: {path}{RESET}");
|
|
}
|
|
}
|
|
|
|
int exitCode = result.ContainsKey("exit_code") ? Convert.ToInt32(result["exit_code"]) : 0;
|
|
Environment.Exit(exitCode);
|
|
}
|
|
|
|
static void CmdSession(Args args)
|
|
{
|
|
string apiKey = GetApiKey(args.ApiKey);
|
|
|
|
if (args.SessionList)
|
|
{
|
|
var result = ApiRequest("/sessions", "GET", null, apiKey);
|
|
var sessions = result.ContainsKey("sessions") ? result["sessions"] as List<object> : null;
|
|
if (sessions == null || sessions.Count == 0)
|
|
{
|
|
Console.WriteLine("No active sessions");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("{0,-40} {1,-10} {2,-10} {3}", "ID", "Shell", "Status", "Created");
|
|
foreach (Dictionary<string, object> s in sessions)
|
|
{
|
|
Console.WriteLine("{0,-40} {1,-10} {2,-10} {3}",
|
|
s.ContainsKey("id") ? s["id"] : "N/A",
|
|
s.ContainsKey("shell") ? s["shell"] : "N/A",
|
|
s.ContainsKey("status") ? s["status"] : "N/A",
|
|
s.ContainsKey("created_at") ? s["created_at"] : "N/A");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (args.SessionKill != null)
|
|
{
|
|
ApiRequest($"/sessions/{args.SessionKill}", "DELETE", null, apiKey);
|
|
Console.WriteLine($"{GREEN}Session terminated: {args.SessionKill}{RESET}");
|
|
return;
|
|
}
|
|
|
|
var payload = new Dictionary<string, object>
|
|
{
|
|
["shell"] = args.SessionShell ?? "bash"
|
|
};
|
|
if (args.Network != null)
|
|
{
|
|
payload["network"] = args.Network;
|
|
}
|
|
if (args.Vcpu > 0)
|
|
{
|
|
payload["vcpu"] = args.Vcpu;
|
|
}
|
|
|
|
Console.WriteLine($"{YELLOW}Creating session...{RESET}");
|
|
var createResult = ApiRequest("/sessions", "POST", payload, apiKey);
|
|
Console.WriteLine($"{GREEN}Session created: {createResult["id"]}{RESET}");
|
|
Console.WriteLine($"{YELLOW}(Interactive sessions require WebSocket - use un2 for full support){RESET}");
|
|
}
|
|
|
|
static void CmdService(Args args)
|
|
{
|
|
string apiKey = GetApiKey(args.ApiKey);
|
|
|
|
if (args.ServiceList)
|
|
{
|
|
var result = ApiRequest("/services", "GET", null, apiKey);
|
|
var services = result.ContainsKey("services") ? result["services"] as List<object> : null;
|
|
if (services == null || services.Count == 0)
|
|
{
|
|
Console.WriteLine("No services");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("{0,-20} {1,-15} {2,-10} {3,-15} {4}", "ID", "Name", "Status", "Ports", "Domains");
|
|
foreach (Dictionary<string, object> s in services)
|
|
{
|
|
var ports = s.ContainsKey("ports") ? s["ports"] as List<object> : null;
|
|
var domains = s.ContainsKey("domains") ? s["domains"] as List<object> : null;
|
|
string portsStr = ports != null ? string.Join(",", ports) : "";
|
|
string domainsStr = domains != null ? string.Join(",", domains) : "";
|
|
Console.WriteLine("{0,-20} {1,-15} {2,-10} {3,-15} {4}",
|
|
s.ContainsKey("id") ? s["id"] : "N/A",
|
|
s.ContainsKey("name") ? s["name"] : "N/A",
|
|
s.ContainsKey("status") ? s["status"] : "N/A",
|
|
portsStr, domainsStr);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (args.ServiceInfo != null)
|
|
{
|
|
var result = ApiRequest($"/services/{args.ServiceInfo}", "GET", null, apiKey);
|
|
Console.WriteLine(ToJson(result));
|
|
return;
|
|
}
|
|
|
|
if (args.ServiceLogs != null)
|
|
{
|
|
var result = ApiRequest($"/services/{args.ServiceLogs}/logs", "GET", null, apiKey);
|
|
Console.WriteLine(result.ContainsKey("logs") ? result["logs"] : "");
|
|
return;
|
|
}
|
|
|
|
if (args.ServiceTail != null)
|
|
{
|
|
var result = ApiRequest($"/services/{args.ServiceTail}/logs?lines=9000", "GET", null, apiKey);
|
|
Console.WriteLine(result.ContainsKey("logs") ? result["logs"] : "");
|
|
return;
|
|
}
|
|
|
|
if (args.ServiceSleep != null)
|
|
{
|
|
ApiRequest($"/services/{args.ServiceSleep}/sleep", "POST", null, apiKey);
|
|
Console.WriteLine($"{GREEN}Service sleeping: {args.ServiceSleep}{RESET}");
|
|
return;
|
|
}
|
|
|
|
if (args.ServiceWake != null)
|
|
{
|
|
ApiRequest($"/services/{args.ServiceWake}/wake", "POST", null, apiKey);
|
|
Console.WriteLine($"{GREEN}Service waking: {args.ServiceWake}{RESET}");
|
|
return;
|
|
}
|
|
|
|
if (args.ServiceDestroy != null)
|
|
{
|
|
ApiRequest($"/services/{args.ServiceDestroy}", "DELETE", null, apiKey);
|
|
Console.WriteLine($"{GREEN}Service destroyed: {args.ServiceDestroy}{RESET}");
|
|
return;
|
|
}
|
|
|
|
if (args.ServiceName != null)
|
|
{
|
|
var payload = new Dictionary<string, object>
|
|
{
|
|
["name"] = args.ServiceName
|
|
};
|
|
if (args.ServicePorts != null)
|
|
{
|
|
var ports = new List<int>();
|
|
foreach (var p in args.ServicePorts.Split(','))
|
|
{
|
|
ports.Add(int.Parse(p.Trim()));
|
|
}
|
|
payload["ports"] = ports;
|
|
}
|
|
if (args.ServiceBootstrap != null)
|
|
{
|
|
payload["bootstrap"] = args.ServiceBootstrap;
|
|
}
|
|
if (args.Network != null)
|
|
{
|
|
payload["network"] = args.Network;
|
|
}
|
|
if (args.Vcpu > 0)
|
|
{
|
|
payload["vcpu"] = args.Vcpu;
|
|
}
|
|
|
|
var result = ApiRequest("/services", "POST", payload, apiKey);
|
|
Console.WriteLine($"{GREEN}Service created: {result["id"]}{RESET}");
|
|
Console.WriteLine($"Name: {result["name"]}");
|
|
if (result.ContainsKey("url"))
|
|
{
|
|
Console.WriteLine($"URL: {result["url"]}");
|
|
}
|
|
return;
|
|
}
|
|
|
|
Console.Error.WriteLine($"{RED}Error: Specify --name to create a service, or use --list, --info, etc.{RESET}");
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
static string GetApiKey(string argsKey)
|
|
{
|
|
string key = argsKey ?? Environment.GetEnvironmentVariable("UNSANDBOX_API_KEY");
|
|
if (string.IsNullOrEmpty(key))
|
|
{
|
|
Console.Error.WriteLine($"{RED}Error: UNSANDBOX_API_KEY not set{RESET}");
|
|
Environment.Exit(1);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
static string DetectLanguage(string filename)
|
|
{
|
|
int dotIndex = filename.LastIndexOf('.');
|
|
if (dotIndex == -1)
|
|
{
|
|
throw new Exception("Cannot detect language: no file extension");
|
|
}
|
|
string ext = filename.Substring(dotIndex).ToLower();
|
|
if (!ExtMap.ContainsKey(ext))
|
|
{
|
|
throw new Exception($"Unsupported file extension: {ext}");
|
|
}
|
|
return ExtMap[ext];
|
|
}
|
|
|
|
static Dictionary<string, object> ApiRequest(string endpoint, string method, Dictionary<string, object> data, string apiKey)
|
|
{
|
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
|
|
|
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(API_BASE + endpoint);
|
|
request.Method = method;
|
|
request.Headers.Add("Authorization", $"Bearer {apiKey}");
|
|
request.ContentType = "application/json";
|
|
request.Timeout = 300000;
|
|
|
|
if (data != null)
|
|
{
|
|
string json = ToJson(data);
|
|
byte[] bytes = Encoding.UTF8.GetBytes(json);
|
|
request.ContentLength = bytes.Length;
|
|
using (Stream stream = request.GetRequestStream())
|
|
{
|
|
stream.Write(bytes, 0, bytes.Length);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
|
{
|
|
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
|
|
{
|
|
string responseText = reader.ReadToEnd();
|
|
return ParseJson(responseText);
|
|
}
|
|
}
|
|
}
|
|
catch (WebException ex)
|
|
{
|
|
string error = "";
|
|
if (ex.Response != null)
|
|
{
|
|
using (StreamReader reader = new StreamReader(ex.Response.GetResponseStream()))
|
|
{
|
|
error = reader.ReadToEnd();
|
|
}
|
|
}
|
|
throw new Exception($"HTTP error - {error}");
|
|
}
|
|
}
|
|
|
|
static string ToJson(object obj)
|
|
{
|
|
if (obj == null) return "null";
|
|
if (obj is string s) return JsonEscape(s);
|
|
if (obj is int || obj is long || obj is double || obj is float) return obj.ToString();
|
|
if (obj is bool b) return b.ToString().ToLower();
|
|
if (obj is Dictionary<string, object> dict)
|
|
{
|
|
var sb = new StringBuilder("{");
|
|
bool first = true;
|
|
foreach (var kv in dict)
|
|
{
|
|
if (!first) sb.Append(",");
|
|
first = false;
|
|
sb.Append(JsonEscape(kv.Key)).Append(":").Append(ToJson(kv.Value));
|
|
}
|
|
sb.Append("}");
|
|
return sb.ToString();
|
|
}
|
|
if (obj is Dictionary<string, string> strDict)
|
|
{
|
|
var sb = new StringBuilder("{");
|
|
bool first = true;
|
|
foreach (var kv in strDict)
|
|
{
|
|
if (!first) sb.Append(",");
|
|
first = false;
|
|
sb.Append(JsonEscape(kv.Key)).Append(":").Append(JsonEscape(kv.Value));
|
|
}
|
|
sb.Append("}");
|
|
return sb.ToString();
|
|
}
|
|
if (obj is List<object> list)
|
|
{
|
|
var sb = new StringBuilder("[");
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
if (i > 0) sb.Append(",");
|
|
sb.Append(ToJson(list[i]));
|
|
}
|
|
sb.Append("]");
|
|
return sb.ToString();
|
|
}
|
|
if (obj is List<int> intList)
|
|
{
|
|
var sb = new StringBuilder("[");
|
|
for (int i = 0; i < intList.Count; i++)
|
|
{
|
|
if (i > 0) sb.Append(",");
|
|
sb.Append(intList[i]);
|
|
}
|
|
sb.Append("]");
|
|
return sb.ToString();
|
|
}
|
|
if (obj is List<Dictionary<string, string>> dictList)
|
|
{
|
|
var sb = new StringBuilder("[");
|
|
for (int i = 0; i < dictList.Count; i++)
|
|
{
|
|
if (i > 0) sb.Append(",");
|
|
sb.Append(ToJson(dictList[i]));
|
|
}
|
|
sb.Append("]");
|
|
return sb.ToString();
|
|
}
|
|
return JsonEscape(obj.ToString());
|
|
}
|
|
|
|
static string JsonEscape(string s)
|
|
{
|
|
var sb = new StringBuilder("\"");
|
|
foreach (char c in s)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '"': sb.Append("\\\""); break;
|
|
case '\\': sb.Append("\\\\"); break;
|
|
case '\n': sb.Append("\\n"); break;
|
|
case '\r': sb.Append("\\r"); break;
|
|
case '\t': sb.Append("\\t"); break;
|
|
default: sb.Append(c); break;
|
|
}
|
|
}
|
|
sb.Append("\"");
|
|
return sb.ToString();
|
|
}
|
|
|
|
static Dictionary<string, object> ParseJson(string json)
|
|
{
|
|
json = json.Trim();
|
|
if (!json.StartsWith("{")) return new Dictionary<string, object>();
|
|
|
|
var result = new Dictionary<string, object>();
|
|
int i = 1;
|
|
|
|
while (i < json.Length)
|
|
{
|
|
while (i < json.Length && char.IsWhiteSpace(json[i])) i++;
|
|
if (json[i] == '}') break;
|
|
|
|
if (json[i] == '"')
|
|
{
|
|
int keyStart = ++i;
|
|
while (i < json.Length && json[i] != '"')
|
|
{
|
|
if (json[i] == '\\') i++;
|
|
i++;
|
|
}
|
|
string key = json.Substring(keyStart, i - keyStart).Replace("\\\"", "\"").Replace("\\\\", "\\");
|
|
i++;
|
|
|
|
while (i < json.Length && (char.IsWhiteSpace(json[i]) || json[i] == ':')) i++;
|
|
|
|
var valuePair = ParseJsonValue(json, i);
|
|
result[key] = valuePair.Item1;
|
|
i = valuePair.Item2;
|
|
|
|
while (i < json.Length && (char.IsWhiteSpace(json[i]) || json[i] == ',')) i++;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Tuple<object, int> ParseJsonValue(string json, int start)
|
|
{
|
|
int i = start;
|
|
while (i < json.Length && char.IsWhiteSpace(json[i])) i++;
|
|
|
|
if (json[i] == '"')
|
|
{
|
|
i++;
|
|
var sb = new StringBuilder();
|
|
bool escaped = false;
|
|
while (i < json.Length)
|
|
{
|
|
char c = json[i];
|
|
if (escaped)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 'n': sb.Append('\n'); break;
|
|
case 'r': sb.Append('\r'); break;
|
|
case 't': sb.Append('\t'); break;
|
|
case '"': sb.Append('"'); break;
|
|
case '\\': sb.Append('\\'); break;
|
|
default: sb.Append(c); break;
|
|
}
|
|
escaped = false;
|
|
}
|
|
else if (c == '\\')
|
|
{
|
|
escaped = true;
|
|
}
|
|
else if (c == '"')
|
|
{
|
|
return Tuple.Create((object)sb.ToString(), i + 1);
|
|
}
|
|
else
|
|
{
|
|
sb.Append(c);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
else if (json[i] == '{')
|
|
{
|
|
int depth = 1;
|
|
int objStart = i++;
|
|
while (i < json.Length && depth > 0)
|
|
{
|
|
if (json[i] == '{') depth++;
|
|
else if (json[i] == '}') depth--;
|
|
i++;
|
|
}
|
|
return Tuple.Create((object)ParseJson(json.Substring(objStart, i - objStart)), i);
|
|
}
|
|
else if (json[i] == '[')
|
|
{
|
|
var list = new List<object>();
|
|
i++;
|
|
while (i < json.Length)
|
|
{
|
|
while (i < json.Length && char.IsWhiteSpace(json[i])) i++;
|
|
if (json[i] == ']')
|
|
{
|
|
i++;
|
|
break;
|
|
}
|
|
var item = ParseJsonValue(json, i);
|
|
list.Add(item.Item1);
|
|
i = item.Item2;
|
|
while (i < json.Length && (char.IsWhiteSpace(json[i]) || json[i] == ',')) i++;
|
|
}
|
|
return Tuple.Create((object)list, i);
|
|
}
|
|
else if (char.IsDigit(json[i]) || json[i] == '-')
|
|
{
|
|
int numStart = i;
|
|
while (i < json.Length && (char.IsDigit(json[i]) || json[i] == '.' || json[i] == '-')) i++;
|
|
string num = json.Substring(numStart, i - numStart);
|
|
return Tuple.Create((object)(num.Contains(".") ? (object)double.Parse(num) : int.Parse(num)), i);
|
|
}
|
|
else if (json.Substring(i).StartsWith("true"))
|
|
{
|
|
return Tuple.Create((object)true, i + 4);
|
|
}
|
|
else if (json.Substring(i).StartsWith("false"))
|
|
{
|
|
return Tuple.Create((object)false, i + 5);
|
|
}
|
|
else if (json.Substring(i).StartsWith("null"))
|
|
{
|
|
return Tuple.Create((object)null, i + 4);
|
|
}
|
|
return Tuple.Create((object)null, i);
|
|
}
|
|
|
|
class Args
|
|
{
|
|
public string Command = null;
|
|
public string SourceFile = null;
|
|
public string ApiKey = null;
|
|
public string Network = null;
|
|
public int Vcpu = 0;
|
|
public List<string> Env = new List<string>();
|
|
public List<string> Files = new List<string>();
|
|
public bool Artifacts = false;
|
|
public string OutputDir = null;
|
|
public bool SessionList = false;
|
|
public string SessionShell = null;
|
|
public string SessionKill = null;
|
|
public bool ServiceList = false;
|
|
public string ServiceName = null;
|
|
public string ServicePorts = null;
|
|
public string ServiceBootstrap = null;
|
|
public string ServiceInfo = null;
|
|
public string ServiceLogs = null;
|
|
public string ServiceTail = null;
|
|
public string ServiceSleep = null;
|
|
public string ServiceWake = null;
|
|
public string ServiceDestroy = null;
|
|
}
|
|
|
|
static Args ParseArgs(string[] args)
|
|
{
|
|
var result = new Args();
|
|
for (int i = 0; i < args.Length; i++)
|
|
{
|
|
string arg = args[i];
|
|
if (arg == "session") result.Command = "session";
|
|
else if (arg == "service") result.Command = "service";
|
|
else if (arg == "-k" || arg == "--api-key") result.ApiKey = args[++i];
|
|
else if (arg == "-n" || arg == "--network") result.Network = args[++i];
|
|
else if (arg == "-v" || arg == "--vcpu") result.Vcpu = int.Parse(args[++i]);
|
|
else if (arg == "-e" || arg == "--env") result.Env.Add(args[++i]);
|
|
else if (arg == "-f" || arg == "--files") result.Files.Add(args[++i]);
|
|
else if (arg == "-a" || arg == "--artifacts") result.Artifacts = true;
|
|
else if (arg == "-o" || arg == "--output-dir") result.OutputDir = args[++i];
|
|
else if (arg == "-l" || arg == "--list")
|
|
{
|
|
if (result.Command == "session") result.SessionList = true;
|
|
else if (result.Command == "service") result.ServiceList = true;
|
|
}
|
|
else if (arg == "-s" || arg == "--shell") result.SessionShell = args[++i];
|
|
else if (arg == "--kill") result.SessionKill = args[++i];
|
|
else if (arg == "--name") result.ServiceName = args[++i];
|
|
else if (arg == "--ports") result.ServicePorts = args[++i];
|
|
else if (arg == "--bootstrap") result.ServiceBootstrap = args[++i];
|
|
else if (arg == "--info") result.ServiceInfo = args[++i];
|
|
else if (arg == "--logs") result.ServiceLogs = args[++i];
|
|
else if (arg == "--tail") result.ServiceTail = args[++i];
|
|
else if (arg == "--sleep") result.ServiceSleep = args[++i];
|
|
else if (arg == "--wake") result.ServiceWake = args[++i];
|
|
else if (arg == "--destroy") result.ServiceDestroy = args[++i];
|
|
else if (!arg.StartsWith("-")) result.SourceFile = arg;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void PrintHelp()
|
|
{
|
|
Console.WriteLine(@"Usage: Un [options] <source_file>
|
|
Un session [options]
|
|
Un service [options]
|
|
|
|
Execute options:
|
|
-e KEY=VALUE Set environment variable
|
|
-f FILE Add input file
|
|
-a Return artifacts
|
|
-o DIR Output directory for artifacts
|
|
-n MODE Network mode (zerotrust/semitrusted)
|
|
-v N vCPU count (1-8)
|
|
-k KEY API key
|
|
|
|
Session options:
|
|
--list List active sessions
|
|
--shell NAME Shell/REPL to use
|
|
--kill ID Terminate session
|
|
|
|
Service options:
|
|
--list List services
|
|
--name NAME Service name
|
|
--ports PORTS Comma-separated ports
|
|
--bootstrap CMD Bootstrap command
|
|
--info ID Get service details
|
|
--logs ID Get all logs
|
|
--tail ID Get last 9000 lines
|
|
--sleep ID Freeze service
|
|
--wake ID Unfreeze service
|
|
--destroy ID Destroy service");
|
|
}
|
|
}
|