nipbox/nipbox.nim

1166 lines
35 KiB
Nim

# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus SDK.
# See legal/LICENSE_UNBOUND.md for license terms.
# src/npl/nipbox/nipbox.nim
# Phase 21: The Teleporter - Networked Object Pipelines
import strutils, parseutils, tables, sequtils, json
import kdl
import ../../libs/membrane/libc as lb
import ../../libs/membrane/libc_net as lnet
when not defined(NIPBOX_LITE):
import ../../libs/membrane/fs/sfs_user as sfs
import editor
when not defined(NIPBOX_LITE):
import ../../libs/membrane/term # Phase 26: Visual Cortex
# --- M4.4: BKDL Capability Manifest (SPEC-071) ---
{.emit: """
__attribute__((section(".nexus.manifest"), used))
static const unsigned char nexus_manifest[166] = {
/* BkdlHeader (118 bytes) */
0x53, 0x55, 0x58, 0x4E, /* magic: "NXUS" (LE) */
0x01, 0x00, /* version: 1 */
0x00, 0x00, /* flags: 0 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* signature[0..63]: zeros */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* pubkey_hash[0..31]: zeros */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, /* cap_count: 4 */
0x00, 0x00, 0x00, 0x00, /* blob_size: 0 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* entry_point: 0 */
/* CapDescriptor[0]: console.output (0x1001) WRITE */
0x02, 0x02, 0x00, 0x00,
0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* CapDescriptor[1]: VFS (0x2000) READ */
0x02, 0x01, 0x00, 0x00,
0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* CapDescriptor[2]: NET_TX (0x0500) WRITE */
0x02, 0x02, 0x00, 0x00,
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* CapDescriptor[3]: NET_RX (0x0501) READ */
0x02, 0x01, 0x00, 0x00,
0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
""".}
# Phase 30: Pledge Constants
const
PLEDGE_STDIO* = 0x0001'u64
PLEDGE_RPATH* = 0x0002'u64
PLEDGE_WPATH* = 0x0004'u64
PLEDGE_INET* = 0x0008'u64
PLEDGE_EXEC* = 0x0010'u64
PLEDGE_ALL* = 0xFFFFFFFFFFFFFFFF'u64
# Phase 34: Phoenix Version Marker
NIPBOX_VERSION* = "v0.8.8-PHOENIX"
type
PipelineData = seq[Node]
# --- ENVIRONMENT ---
var env_table = initTable[string, string]()
var last_exit_code: int = 0
# --- HELPERS ---
var use_logfile = false
proc print(s: string) =
lb.print(s)
proc expand_vars(text: string): string =
# Replace $var with env value, including special $? for exit code
result = ""
var i = 0
while i < text.len:
if text[i] == '$':
# Extract var name
var varname = ""
var j = i + 1
if j < text.len and text[j] == '?':
varname = "?"
j += 1
else:
while j < text.len and (text[j].isAlphaNumeric() or text[j] == '_'):
varname.add(text[j])
j += 1
if varname.len > 0:
if varname == "?":
result.add($last_exit_code)
elif env_table.hasKey(varname):
result.add(env_table[varname])
else:
result.add("$" & varname) # Leave unexpanded if not found
i = j
else:
result.add('$')
i += 1
else:
result.add(text[i])
i += 1
proc render_output(data: PipelineData) =
if data.len == 0: return
let typeName = if data.len > 0: data[0].name.toUpperAscii() else: "VOID"
print("\n\x1b[1;36mTYPE: " & typeName & "\x1b[0m\n")
print(repeat("-", 40) & "\n")
for node in data:
var line = " "
for p in node.props:
line.add(p.key & ":" & $p.val & " ")
for arg in node.args:
line.add($arg & " ")
# Truncate content for display if too long
if line.len > 80: line = line[0..77] & "..."
print(line & "\n")
print(repeat("-", 40) & "\n")
print("Total: " & $data.len & " objects.\n\n")
# --- PHASE 30: WORKER SYSTEM ---
type
WorkerPacket = ref object
command_fn: proc(args: seq[string], input: PipelineData): PipelineData
args: seq[string]
input: PipelineData
output: PipelineData
exit_code: int
pledge_mask: uint64
# Worker trampoline (C-compatible)
proc dispatch_worker(arg: uint64) {.cdecl.} =
let packet = cast[ptr WorkerPacket](arg)
if packet == nil: return
# Apply pledge
if packet.pledge_mask != PLEDGE_ALL:
discard lb.pledge(packet.pledge_mask)
# Execute command
try:
packet.output = packet.command_fn(packet.args, packet.input)
packet.exit_code = 0
except:
packet.output = @[]
packet.exit_code = 1
# Helper to spawn command as worker
proc spawn_command(cmd_fn: proc(args: seq[string], input: PipelineData): PipelineData,
args: seq[string], input: PipelineData,
pledge: uint64): PipelineData =
var packet = WorkerPacket(
command_fn: cmd_fn,
args: args,
input: input,
output: @[],
exit_code: 0,
pledge_mask: pledge
)
let packet_ptr = cast[uint64](cast[pointer](packet))
let fid = lb.spawn(dispatch_worker, packet_ptr)
if fid < 0:
# Spawn failed, run inline
return cmd_fn(args, input)
discard lb.join(fid)
return packet.output
proc cmd_crash*(args: seq[string], input: PipelineData): PipelineData =
print("[NipBox] PREPARING TO CRASH...\n")
# Crash Logic: Null Pointer Dereference in Worker
let worker_crash = proc(args: seq[string],
input: PipelineData): PipelineData =
print("[Worker] Goodbye, cruel world!\n")
var ptr_null = cast[ptr int](0)
ptr_null[] = 42 # PAGE FAULT
return @[]
# Spawn the suicider
return spawn_command(worker_crash, args, input, PLEDGE_ALL)
proc cmd_upgrade*(args: seq[string], input: PipelineData): PipelineData =
if args.len < 1:
print("Usage: sys.upgrade <path>\n")
return @[]
let path = args[0]
print("[NipBox] Initiating Phoenix Protocol for Self...\n")
print("[NipBox] Target: " & path & "\n")
# Upgrade Self (Subject runs as ID 3 usually)
let res = lb.upgrade(3, path.cstring)
if res < 0:
print("Error: Upgrade failed (" & $res & ")\n")
# Does not return if success.
return @[]
# --- COMMANDS ---
proc cmd_ls*(args: seq[string], input: PipelineData): PipelineData =
result = @[]
let files = lb.get_vfs_listing()
for f in files:
let node = newNode("file")
node.addArg(newVal(f))
node.addProp("name", newVal(f))
if f.endsWith(".nsh"):
node.addProp("type", newVal("script"))
node.addProp("size", newVal(335))
elif f.contains("nipbox"):
node.addProp("type", newVal("binary"))
node.addProp("size", newVal(800000))
else:
node.addProp("type", newVal("unknown"))
node.addProp("size", newVal(100))
result.add(node)
proc cmd_mount*(args: seq[string], input: PipelineData): PipelineData =
print("[mount] System Disk Engaged.\n")
return @[]
proc cmd_cp*(args: seq[string], input: PipelineData): PipelineData =
if args.len < 2:
print("Usage: cp <source> <dest>\n")
return @[]
let src = args[0]
let dest = args[1]
let fd_src = lb.open(src.cstring, 0) # O_RDONLY
if fd_src < 0:
print("cp: cannot stat '" & src & "': No such file\n")
return @[]
# O_WRONLY(1) | O_CREAT(64) | O_TRUNC(512) = 577
let fd_dest = lb.open(dest.cstring, 577)
if fd_dest < 0:
print("cp: cannot create '" & dest & "'\n")
discard lb.close(fd_src)
return @[]
var buf: array[4096, char]
var total = 0
while true:
let n = lb.read(fd_src, addr buf[0], 4096)
if n <= 0: break
let written = lb.write(fd_dest, addr buf[0], csize_t(n))
if written < 0:
print("cp: write error\n")
break
total += int(written)
discard lb.close(fd_src)
discard lb.close(fd_dest)
print("[OK] Copied " & $total & " bytes.\n")
return @[]
proc cmd_mv*(args: seq[string], input: PipelineData): PipelineData =
if args.len < 2:
print("Usage: mv <source> <dest>\n")
return @[]
# Step 1: Copy
print("[mv] Copying...\n")
discard cmd_cp(args, input)
# Step 2: Unlink (Not yet supported by Kernel)
print("[mv] Warning: Original file '" & args[0] & "' retained (SFS unlink not implemented).\n")
return @[]
proc cmd_touch*(args: seq[string], input: PipelineData): PipelineData =
if args.len < 1:
print("Usage: touch <filename>\n")
return @[]
let fd = lb.open(args[0].cstring, 577) # O_CREAT | O_TRUNC
if fd >= 0:
discard lb.close(fd)
else:
print("touch: cannot touch '" & args[0] & "'\n")
return @[]
proc cmd_matrix*(args: seq[string], input: PipelineData): PipelineData =
let state = if args.len > 0: args[0].toUpperAscii() else: "STATUS: NOMINAL"
print("[matrix] " & state & "\n")
return @[]
proc cmd_cat*(args: seq[string], input: PipelineData): PipelineData =
if args.len == 0: return @[]
let fd = lb.open(args[0].cstring, 0)
if fd < 0:
print("Error: Could not open " & args[0] & "\n")
return @[]
var buf: array[1024, char]
while true:
let n = lb.read(fd, addr buf[0], 1024)
if n <= 0: break
discard lb.write(cint(1), addr buf[0], csize_t(n))
discard lb.close(fd)
print("\n")
return @[]
proc cmd_write*(args: seq[string], input: PipelineData): PipelineData =
## write <filename> <content>
## Uses USERLAND SFS (Block Valve architecture)
when not defined(NIPBOX_LITE):
if args.len < 2:
print("Usage: write <filename> <content>\n")
return @[]
let filename = args[0]
let content = args[1..^1].join(" ")
# Mount userland FS if not already done
if not sfs.sfs_is_mounted():
discard sfs.sfs_mount()
let bytes_written = sfs.sfs_write(filename, cast[pointer](unsafeAddr content[0]), content.len)
if bytes_written > 0:
print("[Glass Vault] Written " & $bytes_written & " bytes to: " & filename & " (Userland SFS)\n")
else:
print("Error: Could not write to " & filename & "\n")
return @[]
else:
print("[nipbox] 'write' requires SFS (not available in lite mode)\n")
return @[]
proc cmd_read*(args: seq[string], input: PipelineData): PipelineData =
## read <filename>
## Uses USERLAND SFS (Block Valve architecture)
when not defined(NIPBOX_LITE):
if args.len == 0:
print("Usage: read <filename>\n")
return @[]
let filename = args[0]
# Mount userland FS if not already done
if not sfs.sfs_is_mounted():
discard sfs.sfs_mount()
var buf: array[4096, char]
let bytes_read = sfs.sfs_read(filename, addr buf[0], 4096)
if bytes_read > 0:
discard lb.write(cint(1), addr buf[0], csize_t(bytes_read))
print("\n[Glass Vault] Read " & $bytes_read & " bytes from: " & filename & " (Userland SFS)\n")
else:
print("Error: Could not open " & filename & "\n")
return @[]
else:
print("[nipbox] 'read' requires SFS (not available in lite mode)\n")
return @[]
proc cmd_edit*(args: seq[string], input: PipelineData): PipelineData =
if args.len == 0:
print("Usage: edit <filename>\n")
return @[]
start_editor(args[0])
return @[]
proc cmd_echo*(args: seq[string], input: PipelineData): PipelineData =
let msg = args.join(" ")
if input.len == 0:
print(msg & "\n")
let node = newNode("text")
node.addArg(newVal(msg))
node.addProp("content", newVal(msg))
return @[node]
proc cmd_where*(args: seq[string], input: PipelineData): PipelineData =
if args.len < 3:
print("Usage: where <key> <op> <val>\n")
return input
let key = args[0]
let op = args[1]
let targetValStr = args[2]
var targetVal = 0
var isInt = parseInt(targetValStr, targetVal) > 0
result = @[]
for node in input:
var found = false
var nodeValInt = 0
var nodeValStr = ""
for p in node.props:
if p.key == key:
if p.val.kind == VInt:
nodeValInt = p.val.i
found = true
elif p.val.kind == VString:
nodeValStr = p.val.s
discard parseInt(nodeValStr, nodeValInt)
found = true
break
if found:
let match = case op:
of ">": nodeValInt > targetVal
of "<": nodeValInt < targetVal
of "==": (if not isInt: nodeValStr == targetValStr else: nodeValInt == targetVal)
else: false
if match: result.add(node)
# --- PHASE 21: THE TELEPORTER ---
proc cmd_http_get*(args: seq[string], input: PipelineData): PipelineData =
if args.len == 0:
print("Usage: http.get <ip:port>\n")
return @[]
let target = args[0]
let parts = target.split(':')
if parts.len != 2:
print("Error: Target must be IP:PORT (e.g. 10.0.2.2:8000)\n")
return @[]
let ip_str = parts[0]
let port = uint16(parseInt(parts[1]))
# Parse IP (A.B.C.D)
let ip_parts = ip_str.split('.')
if ip_parts.len != 4: return @[]
# LwIP IP encoding (Network Byte Order for internal, but our pack uses uint32)
# Actually net_glue.nim uses the same pack logic.
let ip_val = (uint32(parseInt(ip_parts[0])) shl 0) or
(uint32(parseInt(ip_parts[1])) shl 8) or
(uint32(parseInt(ip_parts[2])) shl 16) or
(uint32(parseInt(ip_parts[3])) shl 24)
print("[Teleporter] Connecting to " & target & "...\n")
let fd = lb.socket(2, 1, 0) # AF_INET=2, SOCK_STREAM=1
if fd < 100: return @[]
# Construct SockAddrIn
type SockAddrIn = object
sin_family: uint16
sin_port: uint16
sin_addr: uint32
sin_zero: array[8, char]
var addr_in: SockAddrIn
addr_in.sin_family = 2
# htons for port (8000 -> 0x401F -> 0x1F40? No, manual)
addr_in.sin_port = ((port and 0xFF) shl 8) or (port shr 8)
addr_in.sin_addr = ip_val
if lb.connect(fd, addr addr_in, sizeof(addr_in)) < 0:
print("Error: Handshake FAILED.\n")
return @[]
# Wait for establishment (pumping the stack)
var timeout = 0
while timeout < 1000:
# lb.pump_membrane_stack() - Handled by Kernel
# Check if connected (we need a way to check socket state)
# For now, let's assume if we can send, we are connected or it will buffer.
# In our net_glue, glue_write returns -1 if not established.
let test_req = "GET / HTTP/1.1\r\nHost: " & ip_str & "\r\nConnection: close\r\n\r\n"
let n = lb.send(cint(fd), cast[pointer](unsafeAddr test_req[0]), csize_t(
test_req.len), 0)
if n > 0: break
timeout += 1
# Busy wait a bit
for i in 0..1000: discard
if timeout >= 1000:
print("Error: Connection TIMEOUT.\n")
discard lb.close(cint(fd))
return @[]
print("[Teleporter] Request Sent. Waiting for response...\n")
var response_body = ""
var buf: array[2048, char]
timeout = 0
while timeout < 5000:
# lb.pump_membrane_stack()
let n = lb.recv(cint(fd), addr buf[0], 2048, 0)
if n > 0:
for i in 0..<n: response_body.add(buf[i])
timeout = 0 # Reset timeout on data
elif n == 0:
# EOF
break
else:
# EAGAIN
timeout += 1
for i in 0..1000: discard
discard lb.close(cint(fd))
print("[Teleporter] Received " & $response_body.len & " bytes.\n")
let node = newNode("response")
node.addProp("status", newVal(200)) # Simple shim
node.addProp("size", newVal(response_body.len))
node.addProp("body", newVal(response_body))
return @[node]
proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData =
when not defined(NIPBOX_LITE):
if args.len < 2:
print("Usage: http.download <ip:port/path> <outfile>\n")
return @[]
let url_arg = args[0]
let outfile = args[1]
var host_part = url_arg
var path_str = "/"
let slash_pos = url_arg.find('/')
if slash_pos != -1:
host_part = url_arg[0..<slash_pos]
path_str = url_arg[slash_pos..^1]
let parts = host_part.split(':')
if parts.len != 2:
print("Error: Target must be IP:PORT (e.g. 10.0.2.2:8000)\n")
return @[]
let ip_str = parts[0]
let port = uint16(parseInt(parts[1]))
# Parse IP
var ip_val: uint32 = 0
try:
let p = ip_str.split('.')
ip_val = (uint32(parseInt(p[0])) and 0xFF) or
((uint32(parseInt(p[1])) and 0xFF) shl 8) or
((uint32(parseInt(p[2])) and 0xFF) shl 16) or
((uint32(parseInt(p[3])) and 0xFF) shl 24)
except:
print("Error: Invalid IP\n")
return @[]
print("[Download] Connecting to " & host_part & "...\n")
let fd = lb.socket(2, 1, 0)
if fd < 0: return @[]
# SockAddr setup
var addr_buf: array[16, byte]
copyMem(addr addr_buf[2], unsafeAddr port, 2)
copyMem(addr addr_buf[4], unsafeAddr ip_val, 4)
if lb.connect(fd, addr addr_buf[0], 16) < 0:
print("Error: Connection Failed.\n")
return @[]
# Request
let req = "GET " & path_str & " HTTP/1.0\r\nHost: " & ip_str & "\r\nConnection: close\r\n\r\n"
if lb.send(cint(fd), cast[pointer](unsafeAddr req[0]), csize_t(req.len), 0) <= 0:
print("Error: Send Failed.\n")
discard lb.close(cint(fd))
return @[]
# Mount SFS if needed
if not sfs.sfs_is_mounted():
if not sfs.sfs_mount():
print("Error: Could not mount SFS.\n")
discard lb.close(cint(fd))
return @[]
# Open SFS Stream
let sfs_h = sfs.sfs_open_write(outfile)
if sfs_h == nil:
print("Error: Could not create file " & outfile & "\n")
discard lb.close(cint(fd))
return @[]
print("[Download] Downloading...\n")
var buf: array[4096, char]
var header_acc = ""
var header_parsed = false
var total_bytes = 0
var content_len = -1
var timeout = 0
while timeout < 10000:
let n = lb.recv(cint(fd), addr buf[0], 4096, 0)
if n > 0:
timeout = 0
if not header_parsed:
for i in 0..<n: header_acc.add(buf[i])
let sep = header_acc.find("\r\n\r\n")
if sep != -1:
header_parsed = true
let lower_head = header_acc.toLowerAscii()
let cl_idx = lower_head.find("content-length:")
if cl_idx != -1:
let end_line = lower_head.find("\r\n", cl_idx)
if end_line != -1:
try:
content_len = parseInt(lower_head[cl_idx+15..<end_line].strip())
except:
content_len = -1
let body_start = sep + 4
if body_start < header_acc.len:
let chunk = header_acc[body_start..^1]
sfs.sfs_write_chunk(sfs_h, cast[pointer](unsafeAddr chunk[0]), chunk.len)
total_bytes += chunk.len
header_acc = ""
else:
if header_acc.len > 8192:
print("Error: Headers too large.\n")
break
else:
sfs.sfs_write_chunk(sfs_h, addr buf[0], int(n))
total_bytes += int(n)
if content_len > 0:
if total_bytes mod 10240 < int(n): print(".")
else:
if total_bytes mod 10240 < int(n): print(".")
elif n == 0:
break
else:
break
sfs.sfs_close_write(sfs_h)
discard lb.close(cint(fd))
print("\n[Download] Complete. " & $total_bytes & " bytes written to " & outfile & " (Glass Vault).\n")
return @[]
else:
print("[nipbox] 'http.download' requires SFS (not available in lite mode)\n")
return @[]
# Phase 37: HTTP Verification Tool
proc cmd_http_test*(args: seq[string], input: PipelineData): PipelineData =
if args.len < 1:
print("Usage: http.test <host>\n")
return @[]
let host = args[0]
print("Dialing " & host & ":80...\n")
let fd = lnet.net_dial_tcp(host, 80)
if fd < 0:
print("Connection Failed! Error: " & $fd & "\n")
return @[]
print("Connected! Sending GET request...\n")
discard lnet.net_send(fd, "GET / HTTP/1.0\r\nHost: " & host & "\r\nConnection: close\r\n\r\n")
print("Waiting for response...\n")
var total = 0
while true:
let resp = lnet.net_recv(fd, 512)
if resp.len > 0:
print(resp)
total += resp.len
else:
break
print("\n[HTTP] Closed. Total bytes: " & $total & "\n")
lnet.net_close(fd)
return @[]
proc cmd_http_serve*(args: seq[string], input: PipelineData): PipelineData =
print("[Server] Starting Nexus Web/1.0...\n")
let port: uint16 = if args.len > 0: uint16(parseInt(args[0])) else: 80
let s = lb.socket(2, 1, 0)
if s < 0:
print("Error: Socket creation failed.\n")
return @[]
# Bind 0.0.0.0:port
var addr_buf: array[16, byte]
addr_buf[0] = 2 # AF_INET
copyMem(addr addr_buf[2], unsafeAddr port, 2)
# IP 0.0.0.0 is default 0s
if lb.bind_socket(s, addr addr_buf[0], 16) < 0:
print("Error: Bind failed.\n")
return @[]
if lb.listen(s, 1) < 0:
print("Error: Listen failed.\n")
return @[]
print("[Server] Listening on port " & $port & "...\n")
while true:
# Accept blocks and pumps stack
let client = lb.accept(s, nil, nil)
if client < 0:
print("Error: Accept failed.\n")
continue
print("[Server] Client Connected (FD " & $client & ")\n")
var buf: array[1024, char]
let n = lb.recv(client, addr buf[0], 1024, 0)
if n > 0:
var req = ""
for i in 0..<n: req.add(buf[i])
print("[Server] Request:\n" & req & "\n")
let resp = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nHello from Nexus Unikernel!\n"
discard lb.send(client, unsafeAddr resp[0], uint64(resp.len), 0)
discard lb.close(client)
print("[Server] Client Closed.\n")
# Just handle one for testing? No, loop forever.
# But how to exit? Ctrl-C not implemented in NipBox yet?
# We'll just run forever for MVP.
return @[]
proc cmd_from_json*(args: seq[string], input: PipelineData): PipelineData =
if input.len == 0: return @[]
result = @[]
for inNode in input:
var body = ""
for p in inNode.props:
if p.key == "body":
body = p.val.s
break
if body == "": continue
try:
# Find start of JSON if header is present
let start = body.find('{')
if start == -1: continue
let json_str = body[start..^1]
let j = parseJson(json_str)
if j.kind == JObject:
let outNode = newNode("data")
for k, v in j.fields:
case v.kind:
of JString: outNode.addProp(k, newVal(v.getStr()))
of JInt: outNode.addProp(k, newVal(int(v.getBiggestInt())))
of JFloat: outNode.addProp(k, newVal($v.getFloat())) # KDL value doesn't support float yet?
of JBool: outNode.addProp(k, newVal(if v.getBool(): 1 else: 0))
else: discard
result.add(outNode)
elif j.kind == JArray:
for item in j:
if item.kind == JObject:
let outNode = newNode("data")
for k, v in item.fields:
case v.kind:
of JString: outNode.addProp(k, newVal(v.getStr()))
of JInt: outNode.addProp(k, newVal(int(v.getBiggestInt())))
else: discard
result.add(outNode)
except:
print("Error: JSON Parse failed.\n")
proc cmd_set*(args: seq[string], input: PipelineData): PipelineData =
# Syntax: set key = value
if args.len < 3 or args[1] != "=":
print("Usage: set <var> = <value>\n")
last_exit_code = 1
return @[]
let key = args[0]
let value = args[2..^1].join(" ")
env_table[key] = value
last_exit_code = 0
return @[]
proc cmd_help*(args: seq[string], input: PipelineData): PipelineData =
when defined(NIPBOX_LITE):
print("NipBox " & NIPBOX_VERSION & " [LITE] (Phase 34: Orbital Drop)\n")
print("Commands: ls, cat, cp, mv, touch, edit, echo, where, http, http.get,\n")
print(" http.test, http.serve, from_json, mount, matrix, crash,\n")
print(" sys.upgrade, set, if, while, help, exit\n")
print("Disabled: write, read, http.download (requires SFS)\n")
else:
print("NipBox " & NIPBOX_VERSION & " (Phase 34: Orbital Drop)\n")
print("Commands: ls, cat, cp, mv, touch, edit, echo, where, write, read,\n")
print(" http, http.get, http.download, http.test, http.serve,\n")
print(" from_json, mount, matrix, crash, sys.upgrade,\n")
print(" set, if, while, help, exit\n")
return @[]
# --- DISPATCHER ---
proc dispatch_command(name: string, args: seq[string],
input: PipelineData): PipelineData =
let cmd = name.toLowerAscii().strip()
if cmd.len == 0: return input
case cmd:
of "ls": return cmd_ls(args, input)
of "cat": return cmd_cat(args, input)
of "cp": return cmd_cp(args, input)
of "mv": return cmd_mv(args, input)
of "touch": return cmd_touch(args, input)
of "write": return cmd_write(args, input)
of "read": return cmd_read(args, input)
of "edit": return cmd_edit(args, input)
of "echo": return cmd_echo(args, input)
of "where": return cmd_where(args, input)
of "http":
# Phase 38: The Payload (Direct Socket Usage)
if args.len < 1:
print("Usage: http <ip>\n")
return @[]
let host = args[0]
print("[NipBox] Dialing " & host & ":80...\n")
# Use libc.socket/connect (Phase 38 Shim)
let fd = lb.socket(2, 1, 0)
if fd < 0:
print("Socket Error\n")
return @[]
# Parse IP (Quick hack for 10.0.2.2)
# We need proper parsing but let's assume raw IP for MVP
var ip_val: uint32 = 0
try:
let p = host.split('.')
ip_val = (uint32(parseInt(p[0])) and 0xFF) or
((uint32(parseInt(p[1])) and 0xFF) shl 8) or
((uint32(parseInt(p[2])) and 0xFF) shl 16) or
((uint32(parseInt(p[3])) and 0xFF) shl 24)
except:
print("Error: Invalid IP format (use A.B.C.D)\n")
return @[]
# Construct SockAddrIn (Layout must match libc.connect hack)
var addr_buf: array[16, byte]
# Port 80 (0x0050) -> Big Endian 0x0050? No, htons(80) = 0x5000 on LE?
# 80 = 0x0050. LE in mem: 50 00.
# LwIP wants host byte order or network?
# connect() shim expects us to pass port as uint16.
# But the shim casts addr_ptr+2 to uint16*.
# If we write 80 there, it reads 80.
let port: uint16 = 80
copyMem(addr addr_buf[2], unsafeAddr port, 2)
copyMem(addr addr_buf[4], unsafeAddr ip_val, 4)
if lb.connect(fd, addr addr_buf[0], 16) < 0:
print("Connect Failed\n")
return @[]
print("[NipBox] Connected! Sending Payload...\n")
let req = "GET / HTTP/1.0\r\n\r\n"
discard lb.send(fd, unsafeAddr req[0], uint64(req.len), 0)
print("[NipBox] Waiting for Data...\n")
var buf: array[1024, char]
while true:
let n = lb.recv(fd, addr buf[0], 1024, 0)
if n > 0:
var s = ""
for i in 0..<n: s.add(buf[i])
print(s)
else:
break
print("\n[NipBox] Done.\n")
return @[]
of "http.test": return cmd_http_test(args, input)
of "http.serve": return cmd_http_serve(args, input)
of "http.get":
# Phase 30: Spawn in worker with INET pledge only (no file access)
return spawn_command(cmd_http_get, args, input, PLEDGE_INET or PLEDGE_STDIO)
of "http.download":
# Phase 34: Spawn in worker with INET and R/W PATH pledge (needs to write file)
# PLEDGE_WPATH (0x4) + PLEDGE_INET (0x8) + PLEDGE_STDIO (0x1) = 0xD
return spawn_command(cmd_http_download, args, input, PLEDGE_INET or
PLEDGE_WPATH or PLEDGE_STDIO)
of "from_json": return cmd_from_json(args, input)
of "mount": return cmd_mount(args, input)
of "matrix": return cmd_matrix(args, input)
of "crash": return cmd_crash(args, input)
of "sys.upgrade": return cmd_upgrade(args, input)
of "set": return cmd_set(args, input)
of "help": return cmd_help(args, input)
of "exit":
lb.exit(0)
return @[]
else:
print("Error: Command '" & cmd & "' not recognized.\n")
last_exit_code = 127
return @[]
# Forward declaration for recursive calls
proc process_pipeline*(line: string)
proc execute_block(lines: seq[string])
proc parse_block(text: string, startIdx: int): tuple[content: string, endIdx: int] =
# Find matching closing brace
var depth = 0
var i = startIdx
var blockContent = ""
var started = false
while i < text.len:
if text[i] == '{':
if started:
blockContent.add('{')
depth += 1
else:
started = true
i += 1
elif text[i] == '}':
if depth > 0:
blockContent.add('}')
depth -= 1
i += 1
else:
return (blockContent, i)
else:
if started:
blockContent.add(text[i])
i += 1
return (blockContent, i)
proc eval_condition(condLine: string): bool =
# Execute the condition as a pipeline and check exit code
last_exit_code = 0
process_pipeline(condLine)
return last_exit_code == 0
proc execute_block(lines: seq[string]) =
for line in lines:
process_pipeline(line)
proc cmd_if*(fullLine: string) =
# Parse: if <condition> { <block> }
let parts = fullLine.strip().splitWhitespace(maxsplit = 1)
if parts.len < 2:
print("Usage: if <condition> { ... }\n")
last_exit_code = 1
return
let restLine = parts[1]
let bracePos = restLine.find('{')
if bracePos == -1:
print("Error: if block missing '{'\n")
last_exit_code = 1
return
let condition = restLine[0..<bracePos].strip()
let (blockContent, _) = parse_block(restLine, bracePos)
if eval_condition(condition):
let blockLines = blockContent.splitLines().filterIt(it.strip().len > 0)
execute_block(blockLines)
last_exit_code = 0
proc cmd_while*(fullLine: string) =
# Parse: while <condition> { <block> }
let parts = fullLine.strip().splitWhitespace(maxsplit = 1)
if parts.len < 2:
print("Usage: while <condition> { ... }\n")
last_exit_code = 1
return
let restLine = parts[1]
let bracePos = restLine.find('{')
if bracePos == -1:
print("Error: while block missing '{'\n")
last_exit_code = 1
return
let condition = restLine[0..<bracePos].strip()
let (blockContent, _) = parse_block(restLine, bracePos)
let blockLines = blockContent.splitLines().filterIt(it.strip().len > 0)
while eval_condition(condition):
execute_block(blockLines)
last_exit_code = 0
proc process_pipeline*(line: string) =
let expandedLine = expand_vars(line)
let cleanLine = expandedLine.strip()
if cleanLine.len == 0 or cleanLine.startsWith("#"): return
# Check for control flow
if cleanLine.startsWith("if "):
cmd_if(cleanLine)
return
elif cleanLine.startsWith("while "):
cmd_while(cleanLine)
return
var redirectionFile = ""
var pipelineText = cleanLine
# Find redirection at the end of the line
let lastGt = cleanLine.rfind('>')
if lastGt != -1:
# Check if this > is likely a redirection (preceded by space or end of command)
# Simple heuristic: if it's the last segment and followed by a "path-like" string
let potentialFile = cleanLine[lastGt+1..^1].strip()
if potentialFile.len > 0 and not potentialFile.contains(' '):
# Most likely a redirection
pipelineText = cleanLine[0..<lastGt].strip()
redirectionFile = potentialFile
let segments = pipelineText.split("|")
var current_blood: PipelineData = @[]
last_exit_code = 0
for segIdx, seg in segments:
let parts = seg.strip().splitWhitespace()
if parts.len == 0: continue
let cmdName = parts[0]
let args = if parts.len > 1: parts[1..^1] else: @[]
current_blood = dispatch_command(cmdName, args, current_blood)
# Exit code: success if we got data, failure if empty (unless piped)
if current_blood.len == 0:
if segIdx < segments.len - 1:
break
else:
last_exit_code = 1
if current_blood.len > 0:
if redirectionFile.len > 0:
# Write to file (Sovereign Write)
var content = ""
for node in current_blood:
content.add(node.render())
let fd = lb.open(redirectionFile.cstring, 577) # O_WRONLY | O_CREAT | O_TRUNC
if fd >= 0:
discard lb.write(fd, cast[pointer](unsafeAddr content[0]), csize_t(content.len))
discard lb.close(fd)
print("[VFS] Data diverted to: " & redirectionFile & "\n")
else:
print("[VFS] Error: Could not open '" & redirectionFile & "' for diversion.\n")
else:
render_output(current_blood)
# --- BOOTSTRAP ---
proc run_script(path: string) =
let fd = lb.open(path.cstring, 0)
if fd < 0: return
var buf = newString(8192)
let n = lb.read(fd, addr buf[0], 8192)
if n > 0: buf.setLen(n)
discard lb.close(fd)
if n > 0:
var currentLine = ""
for c in buf:
if c == '\n' or c == '\r':
if currentLine.strip().len > 0:
process_pipeline(currentLine)
currentLine = ""
else:
currentLine.add(c)
if currentLine.strip().len > 0:
process_pipeline(currentLine)
# --- MAIN ---
proc nipbox_main*() =
# DIAGNOSTIC: Very first thing - prove we're executing
print("[NIPBOX] Entry point reached!\n")
# Phase 30: Pledge Safety
# NipBox is the Shell, so it needs broad permissions, but we can restrict RPATH/WPATH to specific zones
# For now, we PLEDGE_ALL because the shell needs to explore
# In future (SPEC-401), we drop PLEDGE_INET unless authorized
discard lb.pledge(PLEDGE_ALL)
# Initialize the Biosuit
print("[NipBox] Booting...\n")
# lb.membrane_init() - Handled by Kernel
# term.term_init() # Phase 26: Visual Cortex Init - DISABLED
print("\n\x1b[1;32m╔═══════════════════════════════════════╗\x1b[0m\n")
print("\x1b[1;32m║ SOVEREIGN SUPERVISOR v0.8.7 ║\x1b[0m\n")
print("\x1b[1;32m║ PHASE 21: THE TELEPORTER ACTIVATED ║\x1b[0m\n")
print("\x1b[1;32m╚═══════════════════════════════════════╝\x1b[0m\n\n")
# Phase 38: Boot Script
run_script("/init.nsh")
print("\x1b[1;32m╚═══════════════════════════════════════╝\x1b[0m\n\n")
print("\x1b[1;33mroot@nexus:# \x1b[0m")
var inputBuffer: string = ""
var loop_counter: uint64 = 0
while true:
# Phase 21: Teleporter Logic (Network Pipeline)
# Check network sockets and pipe data
# (Simplified for now - just echo)
lb.pump_membrane_stack()
var c: char
let n = lb.read(0, addr c, 1)
if n > 0:
if c == '\n' or c == '\r':
print("\n")
process_pipeline(inputBuffer)
inputBuffer = ""
print("\x1b[1;33mroot@nexus:# \x1b[0m")
elif c == '\b' or c == char(127):
if inputBuffer.len > 0:
print("\b \b")
inputBuffer.setLen(inputBuffer.len - 1)
else:
inputBuffer.add(c)
var s = ""
s.add(c)
print(s)
else:
# Cooperative multitasking support
lb.yield_fiber()
when isMainModule: nipbox_main()