un-inception/un_deno.ts
Russell Ballestrini fa40b3bc78 Add key subcommand to remaining 25 implementations
Complete key command across all 42 CLI implementations:
- un.go, un.v, un.fs, un.groovy (systems/JVM)
- un.dart, un_inception.c, un_deno.ts, un.ps1 (mixed)
- un.hs, un.ml, un.clj, un.scm (functional)
- un.lisp, un.erl, un.ex, un.f90 (functional/scientific)
- un.cob, un.pro, un.forth, un.raku (exotic)
- un.m, un.awk (other)
- un.js, un.ts, un.pl (fixes to existing)

All implementations now support:
- key: Validate API key and show status
- key --extend: Open browser to renew key
2025-12-27 12:06:50 -05:00

595 lines
16 KiB
TypeScript

#!/usr/bin/env -S deno run --allow-read --allow-env --allow-net
// 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
// unsandbox CLI - Deno TypeScript implementation
// Full-featured CLI matching un.c/un.py capabilities
const API_BASE = "https://api.unsandbox.com";
const PORTAL_BASE = "https://unsandbox.com";
const BLUE = "\x1b[34m";
const RED = "\x1b[31m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const RESET = "\x1b[0m";
const EXT_MAP: Record<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: "vlang",
dart: "dart", groovy: "groovy", scala: "scala",
f90: "fortran", f95: "fortran", cob: "cobol",
pro: "prolog", forth: "forth", "4th": "forth",
tcl: "tcl", raku: "raku", pl6: "raku", p6: "raku",
m: "objc",
};
function getApiKey(): string {
const key = Deno.env.get("UNSANDBOX_API_KEY");
if (!key) {
console.error(`${RED}Error: UNSANDBOX_API_KEY not set${RESET}`);
Deno.exit(1);
}
return key;
}
function detectLanguage(filename: string): string {
const ext = filename.split(".").pop();
if (!ext) {
console.error(`${RED}Error: No file extension found${RESET}`);
Deno.exit(1);
}
const language = EXT_MAP[ext];
if (!language) {
console.error(`${RED}Error: Unknown file extension '${ext}'${RESET}`);
Deno.exit(1);
}
return language;
}
async function apiRequest(
endpoint: string,
method: string,
data?: unknown,
apiKey?: string,
baseUrl?: string,
): Promise<any> {
const base = baseUrl || API_BASE;
const url = `${base}${endpoint}`;
const headers: Record<string, string> = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
const options: RequestInit = {
method,
headers,
};
if (data && method !== "GET") {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
if (!response.ok) {
console.error(`${RED}Error: HTTP ${response.status}${RESET}`);
const errorText = await response.text();
console.error(errorText);
Deno.exit(1);
}
return await response.json();
}
async function cmdExecute(args: string[]) {
const apiKey = getApiKey();
let sourceFile = "";
const envVars: Record<string, string> = {};
const inputFiles: string[] = [];
let artifacts = false;
let outputDir = ".";
let network = "";
let vcpu = 0;
// Parse arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case "-e":
if (i + 1 < args.length) {
const [key, ...valueParts] = args[++i].split("=");
envVars[key] = valueParts.join("=");
}
break;
case "-f":
if (i + 1 < args.length) {
inputFiles.push(args[++i]);
}
break;
case "-a":
artifacts = true;
break;
case "-o":
if (i + 1 < args.length) {
outputDir = args[++i];
}
break;
case "-n":
if (i + 1 < args.length) {
network = args[++i];
}
break;
case "-v":
if (i + 1 < args.length) {
vcpu = parseInt(args[++i]);
}
break;
default:
sourceFile = arg;
}
}
if (!sourceFile) {
console.error("Usage: un_deno.ts [options] <source_file>");
Deno.exit(1);
}
try {
await Deno.stat(sourceFile);
} catch {
console.error(`${RED}Error: File not found: ${sourceFile}${RESET}`);
Deno.exit(1);
}
// Read source file
const code = await Deno.readTextFile(sourceFile);
const language = detectLanguage(sourceFile);
// Build request payload
const payload: any = {
language,
code,
};
if (Object.keys(envVars).length > 0) {
payload.env = envVars;
}
if (inputFiles.length > 0) {
const files = [];
for (const filepath of inputFiles) {
try {
await Deno.stat(filepath);
} catch {
console.error(`${RED}Error: Input file not found: ${filepath}${RESET}`);
Deno.exit(1);
}
const content = await Deno.readFile(filepath);
const encoder = new TextDecoder("latin1");
const b64Content = btoa(encoder.decode(content));
files.push({
filename: filepath.split("/").pop(),
content_base64: b64Content,
});
}
payload.input_files = files;
}
if (artifacts) {
payload.return_artifacts = true;
}
if (network) {
payload.network = network;
}
if (vcpu > 0) {
payload.vcpu = vcpu;
}
// Execute
const result = await apiRequest("/execute", "POST", payload, apiKey);
// Print output
if (result.stdout) {
Deno.stdout.writeSync(new TextEncoder().encode(`${BLUE}${result.stdout}${RESET}`));
}
if (result.stderr) {
Deno.stderr.writeSync(new TextEncoder().encode(`${RED}${result.stderr}${RESET}`));
}
// Save artifacts
if (artifacts && result.artifacts) {
await Deno.mkdir(outputDir, { recursive: true });
for (const artifact of result.artifacts) {
const filename = artifact.filename;
const decoder = new TextDecoder("latin1");
const content = Uint8Array.from(atob(artifact.content_base64), (c) => c.charCodeAt(0));
const path = `${outputDir}/${filename}`;
await Deno.writeFile(path, content, { mode: 0o755 });
console.error(`${GREEN}Saved: ${path}${RESET}`);
}
}
Deno.exit(result.exit_code || 0);
}
async function cmdSession(args: string[]) {
const apiKey = getApiKey();
let listMode = false;
let killId = "";
let shell = "";
let network = "";
let vcpu = 0;
// Parse arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case "--list":
listMode = true;
break;
case "--kill":
if (i + 1 < args.length) {
killId = args[++i];
}
break;
case "--shell":
if (i + 1 < args.length) {
shell = args[++i];
}
break;
case "-n":
if (i + 1 < args.length) {
network = args[++i];
}
break;
case "-v":
if (i + 1 < args.length) {
vcpu = parseInt(args[++i]);
}
break;
}
}
if (listMode) {
const result = await apiRequest("/sessions", "GET", undefined, apiKey);
const sessions = result.sessions || [];
if (sessions.length === 0) {
console.log("No active sessions");
} else {
console.log(
`${"ID".padEnd(40)} ${"Shell".padEnd(10)} ${"Status".padEnd(10)} Created`,
);
for (const s of sessions) {
console.log(
`${s.id.padEnd(40)} ${s.shell.padEnd(10)} ${s.status.padEnd(10)} ${s.created_at}`,
);
}
}
return;
}
if (killId) {
await apiRequest(`/sessions/${killId}`, "DELETE", undefined, apiKey);
console.log(`${GREEN}Session terminated: ${killId}${RESET}`);
return;
}
// Create new session
const payload: any = {
shell: shell || "bash",
};
if (network) payload.network = network;
if (vcpu > 0) payload.vcpu = vcpu;
console.log(`${YELLOW}Creating session...${RESET}`);
const result = await apiRequest("/sessions", "POST", payload, apiKey);
console.log(`${GREEN}Session created: ${result.id}${RESET}`);
console.log(
`${YELLOW}(Interactive sessions require WebSocket - use un2 for full support)${RESET}`,
);
}
async function cmdKey(args: string[]) {
const apiKey = getApiKey();
let extend = false;
// Parse arguments
for (let i = 0; i < args.length; i++) {
if (args[i] === "--extend") {
extend = true;
}
}
try {
const result = await apiRequest("/keys/validate", "POST", undefined, apiKey, PORTAL_BASE);
// Handle --extend flag
if (extend) {
const publicKey = result.public_key;
if (publicKey) {
const url = `${PORTAL_BASE}/keys/extend?pk=${publicKey}`;
console.log(`${BLUE}Opening browser to extend key...${RESET}`);
if (Deno.build.os === "darwin") {
await new Deno.Command("open", { args: [url] }).output();
} else if (Deno.build.os === "linux") {
await new Deno.Command("xdg-open", { args: [url] }).output();
} else if (Deno.build.os === "windows") {
await new Deno.Command("cmd", { args: ["/c", "start", url] }).output();
} else {
console.log(`${YELLOW}Please open manually: ${url}${RESET}`);
}
return;
} else {
console.error(`${RED}Error: Could not retrieve public key${RESET}`);
Deno.exit(1);
}
}
// Check if key is expired
if (result.expired) {
console.log(`${RED}Expired${RESET}`);
console.log(`Public Key: ${result.public_key || "N/A"}`);
console.log(`Tier: ${result.tier || "N/A"}`);
console.log(`Expired: ${result.expires_at || "N/A"}`);
console.log(`${YELLOW}To renew: Visit ${PORTAL_BASE}/keys/extend${RESET}`);
Deno.exit(1);
}
// Valid key
console.log(`${GREEN}Valid${RESET}`);
console.log(`Public Key: ${result.public_key || "N/A"}`);
console.log(`Tier: ${result.tier || "N/A"}`);
console.log(`Status: ${result.status || "N/A"}`);
console.log(`Expires: ${result.expires_at || "N/A"}`);
console.log(`Time Remaining: ${result.time_remaining || "N/A"}`);
console.log(`Rate Limit: ${result.rate_limit || "N/A"}`);
console.log(`Burst: ${result.burst || "N/A"}`);
console.log(`Concurrency: ${result.concurrency || "N/A"}`);
} catch (e) {
console.log(`${RED}Invalid${RESET}`);
console.log(`Reason: ${e}`);
Deno.exit(1);
}
}
async function cmdService(args: string[]) {
const apiKey = getApiKey();
let listMode = false;
let infoId = "";
let logsId = "";
let sleepId = "";
let wakeId = "";
let destroyId = "";
let name = "";
let ports = "";
let serviceType = "";
let bootstrap = "";
let network = "";
let vcpu = 0;
// Parse arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case "--list":
listMode = true;
break;
case "--info":
if (i + 1 < args.length) {
infoId = args[++i];
}
break;
case "--logs":
if (i + 1 < args.length) {
logsId = args[++i];
}
break;
case "--sleep":
if (i + 1 < args.length) {
sleepId = args[++i];
}
break;
case "--wake":
if (i + 1 < args.length) {
wakeId = args[++i];
}
break;
case "--destroy":
if (i + 1 < args.length) {
destroyId = args[++i];
}
break;
case "--name":
if (i + 1 < args.length) {
name = args[++i];
}
break;
case "--ports":
if (i + 1 < args.length) {
ports = args[++i];
}
break;
case "--type":
if (i + 1 < args.length) {
serviceType = args[++i];
}
break;
case "--bootstrap":
if (i + 1 < args.length) {
bootstrap = args[++i];
}
break;
case "-n":
if (i + 1 < args.length) {
network = args[++i];
}
break;
case "-v":
if (i + 1 < args.length) {
vcpu = parseInt(args[++i]);
}
break;
}
}
if (listMode) {
const result = await apiRequest("/services", "GET", undefined, apiKey);
const services = result.services || [];
if (services.length === 0) {
console.log("No services");
} else {
console.log(
`${"ID".padEnd(20)} ${"Name".padEnd(15)} ${"Status".padEnd(10)} ${"Ports".padEnd(15)} Domains`,
);
for (const s of services) {
const portStr = (s.ports || []).join(",");
const domainStr = (s.domains || []).join(",");
console.log(
`${s.id.padEnd(20)} ${s.name.padEnd(15)} ${s.status.padEnd(10)} ${portStr.padEnd(15)} ${domainStr}`,
);
}
}
return;
}
if (infoId) {
const result = await apiRequest(`/services/${infoId}`, "GET", undefined, apiKey);
console.log(JSON.stringify(result, null, 2));
return;
}
if (logsId) {
const result = await apiRequest(`/services/${logsId}/logs`, "GET", undefined, apiKey);
console.log(result.logs || "");
return;
}
if (sleepId) {
await apiRequest(`/services/${sleepId}/sleep`, "POST", undefined, apiKey);
console.log(`${GREEN}Service sleeping: ${sleepId}${RESET}`);
return;
}
if (wakeId) {
await apiRequest(`/services/${wakeId}/wake`, "POST", undefined, apiKey);
console.log(`${GREEN}Service waking: ${wakeId}${RESET}`);
return;
}
if (destroyId) {
await apiRequest(`/services/${destroyId}`, "DELETE", undefined, apiKey);
console.log(`${GREEN}Service destroyed: ${destroyId}${RESET}`);
return;
}
// Create new service
if (name) {
const payload: any = { name };
if (ports) {
payload.ports = ports.split(",").map((p) => parseInt(p));
}
if (serviceType) {
payload.service_type = serviceType;
}
if (bootstrap) {
// Check if bootstrap is a file
try {
const stat = await Deno.stat(bootstrap);
if (stat.isFile) {
payload.bootstrap = await Deno.readTextFile(bootstrap);
} else {
payload.bootstrap = bootstrap;
}
} catch {
payload.bootstrap = bootstrap;
}
}
if (network) payload.network = network;
if (vcpu > 0) payload.vcpu = vcpu;
const result = await apiRequest("/services", "POST", payload, apiKey);
console.log(`${GREEN}Service created: ${result.id}${RESET}`);
console.log(`Name: ${result.name}`);
if (result.url) {
console.log(`URL: ${result.url}`);
}
return;
}
console.error(
`${RED}Error: Specify --name to create a service, or use --list, --info, etc.${RESET}`,
);
Deno.exit(1);
}
async function main() {
const args = Deno.args;
if (args.length === 0) {
console.error("Usage: un_deno.ts [options] <source_file>");
console.error(" un_deno.ts session [options]");
console.error(" un_deno.ts service [options]");
console.error(" un_deno.ts key [options]");
Deno.exit(1);
}
const firstArg = args[0];
if (firstArg === "session") {
await cmdSession(args.slice(1));
} else if (firstArg === "service") {
await cmdService(args.slice(1));
} else if (firstArg === "key") {
await cmdKey(args.slice(1));
} else {
await cmdExecute(args);
}
}
main();