// site.tg — the program behind targa.me: a terminal written in TargaScript // (https://script.targa.me), run in your browser by its WebAssembly // interpreter. It is the files of the site joined in order, like the files // of a Go package, so every top-level name is visible in all of them. // // Contents line // util.tg HTML, padding, colors, the terminal size 29 // strings.tg string and time helpers from Go 112 // content.tg the text: bio, projects, logs, configs 258 // hash.tg MD5, SHA-256 and hex 590 // fs.tg the virtual filesystem 745 // anim.tg animations that Ctrl+C stops 956 // commands.tg the command list, help, man, theme, exit 1093 // commands/files.tg ls, cd, cat, tree, find, ... 1339 // commands/text.tg echo, grep, sort, base64, bc, ... 1707 // commands/system.tg uname, ps, top, neofetch, ... 2220 // commands/network.tg ping, nmap, dig, curl, ssh, ... 2506 // commands/fun.tg cowsay, lolcat, sl, cmatrix, figlet 2674 // eggs.tg hidden commands and easter eggs 2909 // breakin.tg ssh root@targa.me: a root shell with this source 3328 // targascript.tg TargaScript inside the site and its REPL 3411 // llm.tg the small language model in the browser 3650 // chat.tg the claude command 3928 // main.tg the shell: parsing, history, keys, boot 4372 // // Try it: type help, or targascript to write your own. // ── util.tg ───────────────────────────────────────────────────────────── // util.tg — small helpers shared by every file: HTML, padding, colors, // numbers and the size of the terminal. fn escapeHtml(s) { s.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """).replace("'", "'") } fn unescape(s) { s.replace("<", "<").replace(">", ">").replace(""", "\"").replace("'", "'").replace("&", "&") } // stripTags drops HTML tags, keeping the text between them fn stripTags(s) { let out = "" let inTag = false repeat c in s { if c == "<" { inTag = true } else if c == ">" { inTag = false } else if !inTag { out += c } } out } fn errf(cmd, msg) { "" + escapeHtml(cmd + ": " + msg) + "" } fn dim(s) { "" + s + "" } fn green(s) { "" + s + "" } fn yellow(s) { "" + s + "" } fn red(s) { "" + s + "" } fn pad(s, n) { s.padEnd(n) } fn lpad(s, n) { s.padStart(n) } fn zpad(s, n) { toString(s).padStart(n, "0") } fn itoa(n) { toString(n) } // lastIndex is the position of the last sub in s, or -1 fn lastIndex(s, sub) { let at = -1 let from = 0 repeat true { let i = s.slice(from).indexOf(sub) if i < 0 { break } at = from + i from = at + 1 } at } // rand is a random integer from 0 to n - 1 fn rand(n) { random(0, n - 1) } // The time the terminal started, in milliseconds since the program did let bootClock = clock() fn uptimeSeconds() { (clock() - bootClock) / 1000.0 } fn cores() { js.navigator.hardwareConcurrency ?? 1 } // columns estimates how many monospace characters fit in the terminal fn columns() { let probe = doc.createElement("span") probe.textContent = "M".repeat(20) probe.style.visibility = "hidden" probe.style.whiteSpace = "pre" terminal.appendChild(probe) let w = probe.offsetWidth / 20.0 probe.remove() if w <= 0 { return 80 } max(20, toInt((terminal.clientWidth - 20) / w)) } // ── strings.tg ────────────────────────────────────────────────────────── // strings.tg — Go's string and time helpers, which the commands use: // strings.Fields, TrimSpace, TrimRight, Trim, strconv.Atoi, ParseFloat, // FormatFloat(v, 'f', -1) and time.Format. // The characters Go's unicode.IsSpace accepts let spaceChars = " \t\n\r" + bytes([ 11, 12, 0xC2, 0x85, 0xC2, 0xA0, 0xE1, 0x9A, 0x80, 0xE2, 0x80, 0x80, 0xE2, 0x80, 0x81, 0xE2, 0x80, 0x82, 0xE2, 0x80, 0x83, 0xE2, 0x80, 0x84, 0xE2, 0x80, 0x85, 0xE2, 0x80, 0x86, 0xE2, 0x80, 0x87, 0xE2, 0x80, 0x88, 0xE2, 0x80, 0x89, 0xE2, 0x80, 0x8A, 0xE2, 0x80, 0xA8, 0xE2, 0x80, 0xA9, 0xE2, 0x80, 0xAF, 0xE2, 0x81, 0x9F, 0xE3, 0x80, 0x80, ]).text() fn isSpace(ch) { spaceChars.contains(ch) } // fields splits s around runs of white space, like strings.Fields fn fields(s) { let out = [] let cur = "" repeat ch in s { if isSpace(ch) { if cur != "" { out.insert(cur) cur = "" } } else { cur += ch } } if cur != "" { out.insert(cur) } out } // trimSpace drops white space at both ends, like strings.TrimSpace fn trimSpace(s) { let chars = s.chars() let start = 0 let end = chars.length repeat start < end && isSpace(chars[start]) { start += 1 } repeat end > start && isSpace(chars[end - 1]) { end -= 1 } if start == 0 && end == chars.length { return s } chars.slice(start, end).join("") } // trimRightChars drops the characters in cutset from the end of s, like // strings.TrimRight fn trimRightChars(s, cutset) { let out = s repeat out != "" && cutset.contains(out[-1]) { out = out.slice(0, -1) } out } // trimChars drops the characters in cutset from both ends, like strings.Trim fn trimChars(s, cutset) { let out = trimRightChars(s, cutset) repeat out != "" && cutset.contains(out[0]) { out = out.slice(1) } out } // atoi parses a decimal integer like strconv.Atoi: [n, ok], n is 0 when not ok fn atoi(s) { if !s.matches("^[+-]?[0-9]+$") { return [0, false] } try { [toInt(s), true] } catch e { [0, false] } } // parseFloat parses a number like strconv.ParseFloat: [x, ok] fn parseFloat(s) { let l = s.lower() let sign = if l.startsWith("-") { -1.0 } else { 1.0 } let body = if l.startsWith("-") || l.startsWith("+") { l.slice(1) } else { l } if body == "inf" || body == "infinity" { return [sign / 0.0, true] } if body == "nan" { return [0.0 / 0.0, true] } if !s.matches("^[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eE][+-]?[0-9]+)?$") { return [0.0, false] } let v = toFloat(s) if v == 1.0 / 0.0 || v == -1.0 / 0.0 { return [v, false] } // out of range [v, true] } // formatFloat writes a float in plain decimal notation with the fewest // digits that read back the same, like strconv.FormatFloat(v, 'f', -1, 64) fn formatFloat(v) { if v != v { return "NaN" } if v == 1.0 / 0.0 { return "+Inf" } if v == -1.0 / 0.0 { return "-Inf" } let s = toString(v) let neg = s.startsWith("-") if neg { s = s.slice(1) } let exp = 0 let e = s.lower().indexOf("e") if e >= 0 { exp = toInt(s.slice(e + 1)) s = s.slice(0, e) } let dot = s.indexOf(".") let digits = s.replace(".", "") let point = (if dot >= 0 { dot } else { s.length }) + exp // Drop leading zeros, keeping the point where it was repeat digits.length > 1 && digits.startsWith("0") { digits = digits.slice(1) point -= 1 } let intPart = "" let fracPart = "" if point <= 0 { intPart = "0" fracPart = "0".repeat(-point) + digits } else if point >= digits.length { intPart = digits + "0".repeat(point - digits.length) } else { intPart = digits.slice(0, point) fracPart = digits.slice(point) } fracPart = trimRightChars(fracPart, "0") (if neg { "-" } else { "" }) + intPart + (if fracPart != "" { "." + fracPart } else { "" }) } // goTime formats Unix seconds with a Go time layout: Mon, Jan, January, // 2006, 01, 02, 2, _2, 15, 04 and 05 fn goTime(t, layout) { let tokens = [ ["January", "MMMM"], ["2006", "YYYY"], ["Mon", "ddd"], ["Jan", "MMM"], ["_2", "_2"], ["15", "HH"], ["04", "mm"], ["05", "ss"], ["01", "MM"], ["02", "DD"], ["2", "2"], ] let out = "" let rest = layout repeat rest != "" { let tok = tokens.find(([k, f]) => rest.startsWith(k)) if tok == null { out += rest[0] rest = rest.slice(1) continue } let [k, f] = tok out += match f { "_2" => lpad(toString(toInt(timeFormat(t, "DD"))), 2) "2" => toString(toInt(timeFormat(t, "DD"))) _ => timeFormat(t, f) } rest = rest.slice(k.length) } out } // ── content.tg ────────────────────────────────────────────────────────── // content.tg — static content: the text that visitors read. Files marked html // in fs.tg are rendered as-is; everything else is plain text and gets escaped. const version = "v2.0" let banner = " _____ \n" + "|_ _|_ _ _ __ __ _ __ _ _ __ ___ ___ \n" + " | |/ _` | '__/ _` |/ _` | | '_ ` _ \\ / _ \\\n" + " | | (_| | | | (_| | (_| |_| | | | | | __/\n" + " |_|\\__,_|_| \\__, |\\__,_(_)_| |_| |_|\\___|\n" + " |___/ " + version + "\n\n" + `Type help to get started.` + "\n" const aboutMe = ` Hey there! 👋 I'm Miguel Targa, a software engineer and reverse engineering enthusiast with over a decade in the industry. Quick facts: - Born in sunny Brazil, moved to the United States at 15. - Started dissecting other people's code in Delphi and Pascal at 12. Curiosity about how things work under the hood turned into a career. Security: I enjoy finding vulnerabilities and helping fix them. That earned me a spot in Google's Bug Hunters Hall of Fame. Binary analysis and low-level programming are where I have the most fun. What I work with: - Languages: Go, Python, TypeScript, JavaScript, Kotlin, C#, Java, PHP, Delphi, CFML - Platforms: .NET, Android, Lucee, embedded (ESP32) Have a look at projects or say hi via contact. Thanks for stopping by! ` const contactInfo = `Find me here: - contact@targa.me - github.com/migueltarga - linkedin.com/in/migueltarga - Google Bug Hunters profile` // profile is who Miguel is, in plain words, for the pages build.sh makes for // search engines and AI assistants (see pages.tg). Every fact here is also // somewhere in the terminal. const profile = { name: "Miguel Targa", url: "https://targa.me/", email: "contact@targa.me", title: "Software engineer and reverse engineer", summary: "Miguel Targa is a software engineer and reverse engineer with over a decade in the industry. He is in Google's Bug Hunters Hall of Fame for finding security vulnerabilities and helping fix them, and he created TargaScript, a programming language with its own compiler, bytecode virtual machine and WebAssembly runtime. His personal site, targa.me, is an interactive terminal written in TargaScript.", highlights: [ "Listed in Google's Bug Hunters Hall of Fame for finding security vulnerabilities and helping fix them.", "Created TargaScript, a programming language with a compiler, a bytecode virtual machine, a WebAssembly runtime, a formatter, a test runner, a language server and a playground written in the language itself.", "Wrote targa.me in TargaScript: an interactive terminal with a virtual filesystem, over 150 commands and a small language model that runs in the browser.", "Over a decade in the industry, working across Go, Python, TypeScript, JavaScript, Kotlin, C#, Java, PHP, Delphi and CFML, on .NET, Android, Lucee and ESP32 hardware.", "Started reading other people's code in Delphi and Pascal at 12; how things work under the hood is still what drives him.", "Writes about peer-to-peer networking, with runnable Rust code, and builds hardware projects like kiddo, a storytelling device for kids.", ], story: "Born in Brazil, Miguel moved to the United States at 15. Binary analysis, security research and low-level programming are where he has the most fun.", topics: ["Reverse engineering", "Security research", "Vulnerability research", "Binary analysis", "Low-level programming", "Programming language design", "Compilers", "Virtual machines", "WebAssembly", "Go", "Python", "TypeScript", "JavaScript", "Kotlin", "C#", "Java", "PHP", "Delphi", "CFML", ".NET", "Android", "Embedded systems", "ESP32", "Peer-to-peer networking"], links: [ {name: "GitHub", url: "https://github.com/migueltarga"}, {name: "LinkedIn", url: "https://linkedin.com/in/migueltarga"}, {name: "Google Bug Hunters", url: "https://bughunters.google.com/profile/fa3f5d87-6047-424d-b223-de7cf12e47ac"}, {name: "TargaScript playground", url: "https://script.targa.me/"}, ], } // projectList is every project, for the terminal (projects and the files // in ~/projects) and for the pages build.sh makes for search engines. // summary goes in the list, desc in the project's file; both are HTML. const projectList = [ {file: "targascript.md", name: "TargaScript", url: "https://github.com/migueltarga/TargaScript", stack: "Go", summary: `A scripting language written in Go. This site runs on it: try targascript`, desc: "If TypeScript can be rewritten in Go, why not build a language in it? TargaScript is a small, modern scripting language: pipes, pattern matching, structs and enums, destructuring and top-level await, with a formatter, a test runner, a web server and editor support built in. A compiler turns programs into bytecode for a virtual machine, which runs the same in the terminal and, compiled to WebAssembly, in web pages. This site is written in it. Try it at script.targa.me, a playground also written in TargaScript. Inspired by \"Writing An Interpreter In Go\"."}, {file: "p2p.md", name: "P2P", url: "https://github.com/migueltarga/P2P", stack: "Rust, Portuguese", summary: "Article series on peer-to-peer networking, with Rust companion code", desc: "A written series about how peer-to-peer networks actually work, built from experiments and personal curiosity. Each article ships with runnable Rust code."}, {file: "kiddo.md", name: "kiddo", url: "https://github.com/migueltarga/kiddo", stack: "C, ESP32", summary: "Interactive storytelling device for kids on an ESP32 touchscreen", desc: "A fun, interactive storytelling device for kids built on a Cheap Yellow Display (ESP32 with a touchscreen)."}, {file: "telegram-blockly.md", name: "TelegramBlockly", url: "https://github.com/migueltarga/TelegramBlockly", stack: "JavaScript", summary: "Build Telegram bots with a visual block language", desc: "Create Telegram bots by snapping blocks together instead of writing code. A visual programming environment on top of Blockly."}, {file: "ssh-control.md", name: "SSH-Control", url: "https://github.com/migueltarga/SSH-Control", stack: "TypeScript", summary: "VS Code extension for managing SSH connections", desc: "Manage and open SSH connections without leaving VS Code."}, ] const projectsIndex = `Projects (cat a file for details, e.g. cat projects/targascript.md) ` + projectList.map(p => ` ` + p.name + `` + " ".repeat(17 - p.name.length) + p.summary).join("\n") + ` More on GitHub.` fn project(name, url, stack, desc) { `` + name + ` ` + stack + ` ` + desc + ` ` + url + `` } // What the ai command knows: the system prompt for the small model in the // browser. Prose and an example work better than rules at this size, and // telling it to refuse makes it go silent, so the disclaimer footer does that // job instead. Contact questions never reach the model (see cmdAI). const askFacts = "You are a small assistant in the terminal on targa.me, the personal site of Miguel Targa. Visitors ask you about Miguel. " + "Miguel Targa is a software engineer and reverse engineering enthusiast with over a decade in the industry. He was born in Brazil and moved to the United States at 15. He started reading other people's code in Delphi and Pascal at 12. " + "He finds and helps fix security vulnerabilities and is listed in Google's Bug Hunters Hall of Fame; he enjoys binary analysis and low-level programming. " + "He uses Go, Python, TypeScript, JavaScript, Kotlin, C#, Java, PHP, Delphi and CFML, on .NET, Android, Lucee and ESP32. " + "His projects: TargaScript (a scripting language written in Go, which this site is written in), P2P (articles on peer-to-peer networking with Rust code), kiddo (a storytelling device for kids on an ESP32), TelegramBlockly (build Telegram bots with visual blocks) and SSH-Control (a VS Code extension for SSH connections). " + "This website, targa.me, is a fake terminal written in TargaScript, running in the browser on TargaScript's WebAssembly interpreter, with no backend. " + "TargaScript is Miguel's own scripting language, written in Go. It is not TypeScript, and has nothing to do with TypeScript, Python or JavaScript. It compiles programs to bytecode for a virtual machine that runs in the terminal and, compiled to WebAssembly, in web pages. The website is written in it and embeds it: type targascript for a REPL or targascript hello.tg to run an example. Anyone can try it at script.targa.me, a playground that is also written in TargaScript. TargaScript syntax uses let, fn, repeat, match and braces, with pipes (|>), string interpolation, structs and enums. Example TargaScript program:\n" + "// comment\nlet nums = [1, 2, 3]\nfn double(n) { n * 2 }\nrepeat n in nums { print(double(n)) }\nrepeat i in 1..5 { print(\"line \${i}\") }\n" + "let person = {name: \"Ada\"}\nif person.name == \"Ada\" { print(\"hi \${person.name}\") } else { print(\"who?\") }\nlet evens = nums |> filter(it % 2 == 0)\nprint(evens.length)\n" + "Answer in one or two sentences, speaking about Miguel in the third person. When asked for TargaScript code, reply with a short example in that syntax." // The flag for the little treasure hunt. Chain: ls -a -> .secret -> auth.log -> base64 -d. // Anyone can read this file, so it only keeps the flag's SHA-256: the way to // the flag is still the hunt. const flagHash = "10cadc52f7255484d0a3bfb84ecb777f48a44d36eb757f92bcd3f3ab04ecf75f" // \$ is an escape even in backtick strings, so the prompt's backslash and // dollar come from a quoted string. const bashrc = `# ~/.bashrc: executed by tsh for interactive shells export EDITOR=vim export PS1='\w ` + "\\$" + ` ' alias ll='ls -l' alias la='ls -a' alias please='sudo' # TODO: remove the secret before deploying ` const bashHistory = `ls -la cat .secret cat /etc/shadow sudo cat /etc/shadow ssh root@localhost vim :q :q! rm -rf node_modules git push --force neofetch fortune | cowsay | lolcat ` const secretFile = `Nobody looks at the logs. ` const authLog = `Sep 11 02:14:07 targa sshd[666]: Failed password for root from 10.0.0.66 port 4444 ssh2 Sep 11 02:14:09 targa sshd[666]: Failed password for root from 10.0.0.66 port 4444 ssh2 Sep 11 02:14:12 targa sshd[666]: Disconnected from authenticating user root 10.0.0.66 port 4444 [preauth] Sep 11 02:15:00 targa sudo: guest : command not allowed ; TTY=pts/0 ; PWD=/home/guest ; USER=root ; COMMAND=/bin/cat /etc/shadow Sep 11 02:15:31 targa sshd[667]: Invalid user dGFyZ2F7cjN2M3JzMW5nX3RoM193NHNtfQ== from 127.0.0.1 port 31337 Sep 11 02:15:31 targa sshd[667]: input_userauth_request: invalid user (looks encoded, try base64 -d) [preauth] Sep 11 02:16:00 targa tsh[1]: session opened for user guest by (uid=0) ` const syslog = `Sep 11 00:00:00 targa kernel: [ 0.000000] Booting targa.me ` + version + ` (wasm32) Sep 11 00:00:00 targa kernel: [ 0.000420] Memory: 16384K available Sep 11 00:00:00 targa kernel: [ 0.001337] Loading easter eggs... done Sep 11 00:00:01 targa tsh[1]: Welcome, guest. Try 'help -a'. Sep 11 00:00:01 targa cron[42]: (guest) CMD (fortune | cowsay) ` const passwd = `root:x:0:0:root:/root:/bin/tsh daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin neo:x:1337:1337:Thomas A. Anderson:/nonexistent:/usr/sbin/nologin guest:x:1000:1000:Visitor:/home/guest:/bin/tsh ` const groupFile = `root:x:0: sudo:x:27:root guest:x:1000: ` const hosts = `127.0.0.1 localhost targa.me ::1 localhost 10.0.0.66 definitely-not-a-honeypot ` const motd = `Welcome to targa.me. Unauthorized access is encouraged. ` const osRelease = `NAME="targa.me" VERSION="` + version + ` (Trinity)" ID=targa PRETTY_NAME="targa.me ` + version + `" HOME_URL="https://targa.me" BUILD_ID=wasm32 ` const fstab = `# /dev/wasm0 / wasmfs ro,relatime 0 1 tmpfs /tmp tmpfs rw,nosuid 0 0 ` const resolvConf = `nameserver 127.0.0.1 search targa.me ` const idRSA = `-----BEGIN OPENSSH PRIVATE KEY----- bm90IHRvZGF5LiBuaWNlIHRyeSB0aG91Z2guCg== -----END OPENSSH PRIVATE KEY----- ` const authorizedKeys = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFollowTheWhiteRabbit0000000000000000000 neo@matrix ` const knownHosts = `targa.me ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN0tR34lly4K3y00000000000000000000000 ` const procVersion = `targa.me version ` + version + ` (go@wasm32) #1 SMP PREEMPT_DYNAMIC ` const wasmStrings = `targa.me tsh guest /home/guest/.secret /root/flag.txt is not where the flag is base64 is not encryption Wake up, Neo... ` const cowFile = `## ## Default cow. Moo. ## $the_cow = < ` let fortunes = [ "There are only two hard things in computer science: cache invalidation and naming things.\n -- Phil Karlton", "Talk is cheap. Show me the code.\n -- Linus Torvalds", "Any sufficiently advanced technology is indistinguishable from magic.\n -- Arthur C. Clarke", "It works on my machine.", "The best way to predict the future is to invent it.\n -- Alan Kay", "Programs must be written for people to read, and only incidentally for machines to execute.\n -- Abelson & Sussman", "Security is a process, not a product.\n -- Bruce Schneier", "Premature optimization is the root of all evil.\n -- Donald Knuth", "Simplicity is prerequisite for reliability.\n -- Edsger W. Dijkstra", "First, solve the problem. Then, write the code.\n -- John Johnson", "In theory, theory and practice are the same. In practice, they are not.", "Weeks of coding can save you hours of planning.", "There's no place like 127.0.0.1.", "rm -rf / is not a cleaning command.", "The cake is a lie.", "If debugging is the process of removing bugs, then programming must be the process of putting them in.\n -- Edsger W. Dijkstra", "Every byte tells a story. Reverse it.", "A good disassembler is worth a thousand comments.", ] let trainFrames = [ " ==== ________ ___________ ", " _D _| |_______/ \\__I_I_____===__|_________| ", " |(_)--- | H\\________/ | | =|___ ___| ", " / | | H | | | | ||_| |_|| ", " | | | H |__--------------------| [___] | ", " | ________|___H__/__|_____/[][]~\\_______| | ", " |/ | |-----------I_____I [][] [] D |=======|__ ", "__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ", " |/-=|___|= || || || |_____/~\\___/ ", " \\_/ \\O=====O=====O=====O_/ \\_/ ", ] let neofetchLogo = [ " ████████╗ ", " ╚══██╔══╝ ", " ██║ ", " ██║ ", " ██║ ", " ╚═╝ ", ] // zoeArt is printed by the hidden `zoe` command. const zoeArt = ` ...:^~~~!!!!!~~^^^^^.. .:^~!777777777?????????777!~:. :~!7????77????JJJJJJJJJJYYYYYJ?!~~^:. :!7????????JJJJJJJJJJJJJY5PPPP5YYY?77!!~^. .~7??????JJJJJJYYYYYYYJJJY5PP55Y??JJJ??777??~ :7????JJJJYYYYYYYYYYYYYYYYY555Y7!????JJJJJJJJY?: ^?J?JYYYYYYYYYYYY55555YYYYY5P5Y?~???????J5555YJYY~ !JJJY55YYYYY55555YY555555YYYYYJ?!!???JJJ?J55PPP5YY57. !JJY55555555P55YYY55555YYYJ?7!!~~~~!??JJYYY555PGP5Y557. ~JY5P555PPPPPYYY5555YYJ??7!~~^^^^^^~~!7?JY55PPP5PBG5Y55! :JY55PPGGPPP5YY5P5YJ?7!!~^^^::^^^^^^^^~77JJY55PPPPPBP555Y: .!Y5PPGGGGP55Y5PP5J7~~^^^::::::::::::::^~!7?JY5555PPPBP555? ^JPPGGGGGP5JY5PPY7!~^^:::::::::::::::::^^~!77JY5PPPPPGB5555~ ~5PGGBBGPG5J5PGY7!~^^^:::::::::::::^^^^^^~~!!7J5PGGGPPBG555J. .?PGBB#BPPGYYPG5?!~^^^^^^^^^^^^^^^^^^^^^^~~~~!!7J5PGGGPPGP555~ :5GB##BGPPPY5GGJ7!~~~~~~~~~~~~~~~~~~~~~~~~~~!!!!?Y5GBBGGPGP55? ..7GB##BGPPP5Y5B5?7!!~~~~~~~~~~~~~~~~~~~~~~~~!!!!!7?YPBBBBGBG55Y: :5GBBGPPPP55YGBY7!!!~~~~~~~~~~~~~~~~~!~!!!!!!!!!!!7?YB#B####G5Y~ :YGP55PPPP5JYGP?!!!!!!!!!~~!!!~~~~~~!!!!~~!!!!!~~~~!7YG##&&&BPY!. .:7YY5PPPP5JJPBY777777????777!!!~~~~!!!!77??????7!!!!!!JG#&&&#P57. .!?YPGPGP5?7PGP?77777777????777!!~~!!77??JJ??????777!~~!5#&###G5?. .?5PGGGGP?75G5J77??JYYYYYJ???77!!~~!!7?JJJYYYYYYJJ?7!~~~YBBB##G5J. :5GGGPPP5J55YJ???JYY5PGGP?JJ777!~~~~!??YY5PBGGY55YJ?!~~~YGB#&#P5J. ~PGPPPPGP5J?7!!!!7777JJJ?!77!!!~^^^^~7!777JJJ?7?77!~~^~!5B#&&#G5Y: ^PPPPGGGP?7!~^^^^^^~~!!777!!~~~^::::^~~!77777!!!~^^^^^~?BB#&#BP5J^ .75PPPPPP?!~^^:::::^^^~~~^^^^^^:....:^~~~~~~~^^^:::^^^!YBB##BBPY?: .~?YP5555?~~^:::::::::::::^^^^:. .:^~^^^:::::::^^^~7PG###GGPY7:. ^~!JP5Y5J?!~^::::::::::::^~~^::::::::^:^~~^^::::::^^^~?PB#BBGG5J7: .~~^?5Y5G?5?~^^:::::::^^^~~!~~!??!!!!?J7~~!!~^^^^^^^^~!55##BGGP5J?~ .:^~?YPB###G~^^^^^^^^^^~~~!!~~!777??7??7!~~!!~~~~^~~~~?&#BBBGGP5YJ7: .^7J5PGB###?^^^^^^~~~~~~~~~~^^~~!!!~~~~~~~~!!~~~~~~~~Y##BBBGGP555J~. .^!JY5GPGB#&G!~~~~~~~~~~!!7?77?JJJJJJJJ?7???!!!~!!!!!7B#B#BBBGP5Y?YJ^ .:~7Y5PGGPGB#&P!~~~~~~~~~!?JJYYYYYY55YY5555Y?7!!~!!!!7PB###BBBGGP5JJ?^ .^~!?YPPGGGGGB##&G?!!!!!!!!!!~!!!77????????7!~!!!!!!77JPB####B5PGBBGPY! .~?Y55PPGGPGGBBB&&&BJ77777!!!!~~~!!7777??777!!!!777??JPB#&&#BBB5YYPGBGP7 :!JPGPP55PPGGGBBB#&J????777!!!~~~~~~~~~~~~!!!7??JYGB#&&&&BGBBBG5YJYBBP! :7J55YY55PPGGGGB#BB#&&5J???JJJ?77!!~~~~~~~~~~!!7?JJY5B&##&&&&##BGGPPPPPPGP?. .:~JYJJYPGGPPGGBBGGB#J?77???JJJ??777777777???JYYYYYPB##&#######BGGGGP5J!:..: .~?YPPPPPPPGBBGPGGG#BG5J?77777???JJJJJJJJJJJJJJJJJJJY5PPGB#&#BBBBBBGP5YJJ~^.^??J?:.. ^~7?5PG5P#BBGBBBGGGPPYJ??777777??????JJJJJJJJJJJJJJYYY555P#&&&&&&#B5JYJ7..?B#&&#BGPJ^. .~~!77!755GG5G#GGGGGGGPP5YJ?77777777777???????JJJJJJ?JJJJJJYY5#&&&&&&&&#GPGPY5B######BB#BY. :~G#&&&&&#GPGGGGGGGBBGP55YJ??7777777777????????JJJJJ???77????JP&&&&&&&&&&&&&&&&###BB#B#BBBP7 ^!J5G##&&&&&&@&#BBGGGB##&B5YJJ?7777777777777???????????777!!!7777JB&&&&&&&&&&&&#####&###GG##BGPJ .Y####&&&&&&&&@@@&&&&&&&&??777!!!!!!!!7777777777??77!!!!~~!!~!7G&&&&&&&&&&&&&&&####&##B#&#BGP! .!Y5PPB#&&&&&&@@@@@@@@@&&&&BJ!!!!!!~~~!!!!!!!!!!!!!!!!~~~~~~~~~~!G&&&&&&&&&&&&&&&&###########BBGY ...:?#&&&&&&&&&&&&&&&&&&&&&#Y!~~~~~~~~~~~~~~~~~~~~~~~^~~^^^~~!Y#&&&&&&&&&&&&&&&&&###B#######BP?: ..::.:G&&&&&&&&&&&&&&&&&&&&&&&&G?~~~~~~~~~~~~~~~~~~~^^^^^^^^~~?G&&&&&&&&&&&&&&&&&&&&&######GPGY!~^ ` // ── hash.tg ───────────────────────────────────────────────────────────── // hash.tg — compact MD5, SHA-256 and hex. Integers are 64-bit, so the // 32-bit words are kept with & 0xFFFFFFFF after every addition and shift. // hexEncode is the lowercase hex of some bytes fn hexEncode(b) { b.hex() } fn rotl32(x, n) { ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF } fn rotr32(x, n) { ((x >> n) | (x << (32 - n))) & 0xFFFFFFFF } // padMessage appends the standard Merkle–Damgård padding with the bit length // encoded big- or little-endian. fn padMessage(msg, bigEndian) { let n = msg.length * 8 let zeros = (55 - msg.length % 64 + 64) % 64 let out = msg + bytes([0x80]) + bytes(zeros + 8) if bigEndian { out.writeBE(out.length - 8, 8, n) } else { out.write(out.length - 8, 8, n) } out } // Precomputed floor(abs(sin(i+1)) * 2^32) constants. const md5K = [ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, ] const md5S = [ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, ] // md5Hex is the MD5 of a text (its UTF-8) or of bytes, in hex fn md5Hex(data) { let msg = padMessage(bytes(data), false) let a0 = 0x67452301 let b0 = 0xefcdab89 let c0 = 0x98badcfe let d0 = 0x10325476 let m = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] let off = 0 repeat off < msg.length { repeat i in 0..15 { m[i] = msg.read(off + i * 4, 4) } let a = a0 let b = b0 let c = c0 let d = d0 repeat i in 0..63 { let f = 0 let g = 0 if i < 16 { f = (b & c) | (~b & d) g = i } else if i < 32 { f = (d & b) | (~d & c) g = (5 * i + 1) % 16 } else if i < 48 { f = b ^ c ^ d g = (3 * i + 5) % 16 } else { f = (c ^ (b | ~d)) & 0xFFFFFFFF g = (7 * i) % 16 } f = (f + a + md5K[i] + m[g]) & 0xFFFFFFFF a = d d = c c = b b = (b + rotl32(f, md5S[i])) & 0xFFFFFFFF } a0 = (a0 + a) & 0xFFFFFFFF b0 = (b0 + b) & 0xFFFFFFFF c0 = (c0 + c) & 0xFFFFFFFF d0 = (d0 + d) & 0xFFFFFFFF off += 64 } let out = bytes(16) out.write(0, 4, a0) out.write(4, 4, b0) out.write(8, 4, c0) out.write(12, 4, d0) hexEncode(out) } const sha256K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, ] // sha256Hex is the SHA-256 of a text (its UTF-8) or of bytes, in hex fn sha256Hex(data) { let msg = padMessage(bytes(data), true) let h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19] let w = [] repeat 64 { w.insert(0) } let off = 0 repeat off < msg.length { repeat i in 0..15 { w[i] = msg.readBE(off + i * 4, 4) } repeat i in 16..63 { let x = w[i - 15] let y = w[i - 2] let s0 = rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3) let s1 = rotr32(y, 17) ^ rotr32(y, 19) ^ (y >> 10) w[i] = (w[i - 16] + s0 + w[i - 7] + s1) & 0xFFFFFFFF } let [a, b, c, d, e, f, g, hh] = h repeat i in 0..63 { let S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25) let ch = (e & f) ^ (~e & g) let t1 = (hh + S1 + ch + sha256K[i] + w[i]) & 0xFFFFFFFF let S0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22) let maj = (a & b) ^ (a & c) ^ (b & c) let t2 = S0 + maj hh = g g = f f = e e = (d + t1) & 0xFFFFFFFF d = c c = b b = a a = (t1 + t2) & 0xFFFFFFFF } h[0] = (h[0] + a) & 0xFFFFFFFF h[1] = (h[1] + b) & 0xFFFFFFFF h[2] = (h[2] + c) & 0xFFFFFFFF h[3] = (h[3] + d) & 0xFFFFFFFF h[4] = (h[4] + e) & 0xFFFFFFFF h[5] = (h[5] + f) & 0xFFFFFFFF h[6] = (h[6] + g) & 0xFFFFFFFF h[7] = (h[7] + hh) & 0xFFFFFFFF off += 64 } let out = bytes(32) repeat i, v in h { out.writeBE(i * 4, 4, v) } hexEncode(out) } // ── fs.tg ─────────────────────────────────────────────────────────────── // fs.tg — the virtual filesystem. Directories map to lists of entries; // paths are absolute and the visitor's home is /home/guest, shown as ~. // // An entry is an object with a name and some of: dir, content, gen (a // function making the content, as for /proc), html (the content is trusted // HTML), exec (shown as an executable), binary (cat refuses, xxd and strings // work), denied (root only) and link (a symlink's target, for display). const home = "/home/guest" // The magic number and version that start a WebAssembly file let wasmHeader = bytes([0, 97, 115, 109, 1, 0, 0, 0]).text() let tree = { "/": [ {name: "bin", dir: true}, {name: "boot", dir: true}, {name: "dev", dir: true}, {name: "etc", dir: true}, {name: "home", dir: true}, {name: "proc", dir: true}, {name: "root", dir: true, denied: true}, {name: "tmp", dir: true}, {name: "usr", dir: true}, {name: "var", dir: true}, ], "/boot": [ {name: "targa.wasm", binary: true, content: wasmHeader + wasmStrings}, {name: "config-wasm", content: "CONFIG_EASTER_EGGS=y\nCONFIG_ROOT_LOGIN=n\nCONFIG_COFFEE=m\n"}, ], "/dev": [ {name: "null", content: ""}, {name: "zero", gen: () => "^@".repeat(64) + "\n"}, {name: "random", gen: randomBytes}, {name: "urandom", gen: randomBytes}, {name: "tty", gen: () => "It's you.\n"}, {name: "wasm0", binary: true, content: wasmHeader.slice(0, 4)}, ], "/etc": [ {name: "fstab", content: fstab}, {name: "group", content: groupFile}, {name: "hostname", content: "targa.me\n"}, {name: "hosts", content: hosts}, {name: "motd", content: motd}, {name: "os-release", content: osRelease}, {name: "passwd", content: passwd}, {name: "resolv.conf", content: resolvConf}, {name: "shadow", denied: true, content: "root:$6$nice$try:19999:0:99999:7:::\n"}, ], "/home": [{name: "guest", dir: true}], "/home/guest": [ {name: "projects", dir: true}, {name: "about.txt", html: true, content: aboutMe}, {name: "contact.txt", html: true, content: contactInfo}, {name: ".bashrc", content: bashrc}, {name: ".bash_history", content: bashHistory}, {name: ".secret", content: secretFile}, {name: ".ssh", dir: true}, ], "/home/guest/.ssh": [ {name: "authorized_keys", content: authorizedKeys}, {name: "id_rsa", content: idRSA}, {name: "known_hosts", content: knownHosts}, ], "/home/guest/projects/targascript": tgExamples(), "/home/guest/projects": [ {name: "README.md", html: true, content: projectsIndex}, {name: "targascript", dir: true}, ...projectList.map(p => {name: p.file, html: true, content: project(p.name, p.url, p.stack, p.desc)}), ], "/proc": [ {name: "cpuinfo", gen: procCPUInfo}, {name: "meminfo", gen: procMemInfo}, {name: "uptime", gen: () => format("%.2f %.2f\n", uptimeSeconds(), uptimeSeconds() * 0.9)}, {name: "version", content: procVersion}, ], "/root": [{name: "flag.txt", denied: true, content: "Not here. Try harder.\n"}], "/tmp": [], "/usr": [{name: "bin", dir: true, link: "/bin"}, {name: "games", dir: true}, {name: "local", dir: true}, {name: "share", dir: true}], "/usr/bin": [], "/usr/games": [], "/usr/local": [{name: "bin", dir: true}], "/usr/local/bin": [{name: "tsh", exec: true, content: "#!/bin/tsh\n# the targa shell. you're soaking in it.\n"}], "/usr/share": [{name: "cowsay", dir: true}, {name: "fortunes", dir: true}], "/usr/share/cowsay": [{name: "default.cow", content: cowFile}], "/usr/share/fortunes": [{name: "quotes", gen: () => fortunes.join("\n%\n") + "\n"}], "/var": [{name: "log", dir: true}, {name: "www", dir: true}], "/var/log": [{name: "auth.log", content: authLog}, {name: "syslog", content: syslog}], "/var/www": [{name: "index.html", content: wwwIndex}, {name: "targa.me", dir: true, denied: true}], "/bin": [], } // populateBin fills /bin and /usr/games from the command table fn populateBin() { let bin = [] let games = [] repeat c in commands { if c.hidden { continue } let e = {name: c.name, exec: true, content: "#!/bin/tsh\n# " + c.name + ": " + c.desc + "\n"} if c.cat == "fun" { games.insert(e) } else { bin.insert(e) } } tree["/bin"] = bin.sortBy(e => e.name) tree["/usr/bin"] = tree["/bin"] tree["/usr/games"] = games.sortBy(e => e.name) } // resolve turns what the visitor typed into a normalized absolute path fn resolve(p) { let stack = [] let rest = p if p == "~" || p.startsWith("~/") { stack = home.slice(1).split("/") rest = p.slice(1) } else if !p.startsWith("/") && cwd != "/" { stack = cwd.slice(1).split("/") } repeat seg in rest.split("/") { match seg { "", "." => null ".." => if stack.length > 0 { stack.pop() } _ => stack.insert(seg) } } "/" + stack.join("/") } // display shortens the home directory to ~ for prompts and messages fn display(p) { if p == home { return "~" } if p.startsWith(home + "/") { return "~" + p.slice(home.length) } p } // splitPath splits a path at its last slash: [dir, base] fn splitPath(p) { let i = lastIndex(p, "/") if i < 0 { return ["", p] } [p.slice(0, i), p.slice(i + 1)] } // find looks up an absolute path, returning its entry or null fn find(path) { if path == "/" { return {name: "/", dir: true} } let [parent, base] = splitPath(path) (tree[if parent == "" { "/" } else { parent }] ?? []).find(e => e.name == base) } // denied reports whether the visitor may not read a path. After the // break-in (breakin.tg) the visitor is root and may read everything. fn denied(path, e) { if root { return false } (e.denied ?? false) || path == "/root" || path.startsWith("/root/") || path.startsWith("/var/www/targa.me/") } fn children(path) { let e = find(path) if e != null && e.link != null { return tree[e.link] ?? [] } tree[path] ?? [] } // text is a file's content as plain text, without HTML fn text(e) { let c = if e.gen != null { e.gen() } else { e.content ?? "" } if e.html { stripTags(c) } else { c } } // rendered is a file's content ready to put in the terminal fn rendered(e) { if e.html { return trimNewlines(e.content) } escapeHtml(text(e).trimEnd()) } fn trimNewlines(s) { let out = s repeat out.startsWith("\n") { out = out.slice(1) } repeat out.endsWith("\n") { out = out.slice(0, -1) } out } fn fileSize(e) { bytes(text(e)).length } // walk visits every path under root, depth first, in tree order fn walk(root, visit) { fn visitDir(dir, depth) { repeat e in children(dir) { let p = if dir == "/" { "/" + e.name } else { dir + "/" + e.name } visit(p, e, depth) if e.dir && e.link == null && !denied(p, e) { visitDir(p, depth + 1) } } } visitDir(root, 0) } fn randomBytes() { let glyphs = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~▒░▓█".chars() let out = "" repeat i in 0..255 { out += glyphs[rand(glyphs.length)] if i % 64 == 63 { out += "\n" } } out } fn procCPUInfo() { let out = "" repeat i in 0..cores() - 1 { out += "processor\t: ${i}\nvendor_id\t: WebAssembly\nmodel name\t: Virtual wasm32 CPU @ 1337 MHz\ncache size\t: 64 KB\nflags\t\t: simd128 bulk-memory easter-eggs\n\n" } out } fn procMemInfo() { "MemTotal: 16384 kB\nMemFree: 12288 kB\nMemAvailable: 12288 kB\nWasmMemory: 16384 kB\nSwapTotal: 0 kB\n" } // ── anim.tg ───────────────────────────────────────────────────────────── // anim.tg — animations: anything that keeps writing to the screen after the // command returned. Only one runs at a time. While busy, Enter, Escape and // Ctrl+C call the animation's cancel function (null means it can't be // interrupted, like sl). let busy = false let animBuffer = "" // shown after terminalContent, replaced every frame let timers = [] let ticker = null let onCancel = null let cancelKeys = "all" // which keys cancel: "all" or "ctrl-c" fn beginAnim(cancel, keys) { busy = true onCancel = cancel cancelKeys = keys inputField.value = "" } fn endAnim() { repeat t in timers { js.clearTimeout(t) } timers = [] if ticker != null { js.clearInterval(ticker) ticker = null } animBuffer = "" busy = false onCancel = null inputLine.style.display = "" render() inputField.focus() } // interrupt is called by the keyboard handler and reports whether it used // the key fn interrupt(key, ctrl) { if !busy { return false } let isCtrlC = ctrl && key == "c" if !isCtrlC && !(cancelKeys == "all" && (key == "Enter" || key == "Escape")) { return true // other keys are swallowed while busy } if onCancel == null { return true // can't be interrupted } let cancel = onCancel endAnim() cancel() render() true } fn after(ms, f) { timers.insert(js.setTimeout(() => { f() }, ms)) } fn every(ms, f) { ticker = js.setInterval(() => { f() }, ms) } // sequence adds text to the terminal step by step, each {delay, text} after // the one before. Ctrl+C stops it and prints ^C, like a long-running process. fn sequence(steps, hideInput, done = null) { beginAnim(() => { terminalContent += "^C\n" }, "ctrl-c") if hideInput { inputLine.style.display = "none" } let t = 0 repeat i, s in steps { let last = i == steps.length - 1 t += s.delay after(t, () => { terminalContent += s.text render() if last { endAnim() if done != null { done() render() } } }) } } // frames runs a loop of frames: frame(i) returns [text, done]. tail goes to // the transcript when the animation ends. fn frames(ms, cancellable, frame, tail) { beginAnim(if cancellable { () => null } else { null }, "all") let i = 0 every(ms, () => { let [text, done] = frame(i) i += 1 animBuffer = text render() if done { endAnim() terminalContent += tail render() } }) } // typewriter shows HTML a few characters at a time; tags appear at once so // markup never shows half typed. Any cancel key completes it. fn typewriter(html) { let chars = html.chars() let pos = 0 let shown = "" beginAnim(() => { terminalContent += html + "\n" }, "all") every(8, () => { let n = 0 repeat n < 3 && pos < chars.length { if chars[pos] == "<" { let end = pos repeat end < chars.length && chars[end] != ">" { end += 1 } if end < chars.length { end += 1 } shown += chars.slice(pos, end).join("") pos = end continue } shown += chars[pos] pos += 1 n += 1 } animBuffer = shown render() if pos >= chars.length { endAnim() terminalContent += html + "\n" render() } }) } // ── commands.tg ───────────────────────────────────────────────────────── // commands.tg — the list of the commands that help and help -a show, and // the general ones: help, man, theme and exit. The others are in commands/, // one file per category. // The time the terminal started, in Unix seconds (Go's bootTime in main.go) let bootTime = now() let coreCommands = [ // core {name: "help", usage: "[-a]", desc: "List commands (-a for all)", cat: "core", run: cmdHelp}, {name: "about", desc: "Learn more about me", cat: "core", run: fn(c) { typewriter(trimNewlines(aboutMe)) "" }}, {name: "projects", desc: "Things I've built", cat: "core", run: fn(c) { projectsIndex }}, {name: "contact", desc: "How to reach me", cat: "core", run: fn(c) { contactInfo }}, {name: "ai", usage: "[-y] ", desc: "Ask a small LLM that runs in your browser", cat: "core", run: cmdAI}, {name: "claude", usage: "[question]", desc: "Chat with it, Claude Code style", cat: "core", run: cmdClaude}, {name: "clear", desc: "Clear the screen", cat: "core", run: fn(c) { terminalContent = "" "" }}, {name: "man", usage: "", desc: "Manual page for a command", cat: "core", run: cmdMan}, {name: "theme", usage: "[name]", desc: "Change the terminal color", cat: "core", run: cmdTheme}, {name: "exit", desc: "Close the session", cat: "core", run: cmdExit}, // files {name: "ls", usage: "[-la] [path]", desc: "List directory contents", cat: "files", run: cmdLs}, {name: "cd", usage: "[dir]", desc: "Change directory", cat: "files", run: cmdCd}, {name: "pwd", desc: "Print working directory", cat: "files", run: fn(c) { cwd }}, {name: "cat", usage: "", desc: "Print files", cat: "files", run: cmdCat}, {name: "tree", usage: "[dir]", desc: "Directory tree", cat: "files", run: cmdTree}, {name: "find", usage: "[dir] [-name x]", desc: "Find files", cat: "files", run: cmdFind}, {name: "file", usage: "", desc: "Identify file type", cat: "files", run: cmdFile}, {name: "stat", usage: "", desc: "File status", cat: "files", run: cmdStat}, {name: "touch", usage: "", desc: "Create a file", cat: "files", run: readOnly("touch")}, {name: "mkdir", usage: "", desc: "Create a directory", cat: "files", run: readOnly("mkdir")}, {name: "rm", usage: "", desc: "Remove files", cat: "files", run: cmdRm}, {name: "mv", usage: " ", desc: "Move files", cat: "files", run: readOnly("mv")}, {name: "cp", usage: " ", desc: "Copy files", cat: "files", run: readOnly("cp")}, {name: "chmod", usage: " ", desc: "Change permissions", cat: "files", run: readOnly("chmod")}, {name: "chown", usage: " ", desc: "Change owner", cat: "files", run: readOnly("chown")}, {name: "ln", usage: " ", desc: "Link files", cat: "files", run: readOnly("ln")}, {name: "du", usage: "[path]", desc: "Disk usage", cat: "files", run: cmdDu}, {name: "df", desc: "Filesystem usage", cat: "files", run: cmdDf}, {name: "mount", desc: "Mounted filesystems", cat: "files", run: fn(c) { "/dev/wasm0 on / type wasmfs (ro,relatime)\ntmpfs on /tmp type tmpfs (rw,nosuid)\nproc on /proc type proc (rw,nosuid,nodev,noexec)" }}, // text {name: "echo", usage: "", desc: "Print text", cat: "text", run: cmdEcho}, {name: "printf", usage: "", desc: "Print text", cat: "text", run: fn(c) { escapeHtml(c.args.join(" ").replace(`\n`, "\n")) }}, {name: "head", usage: "[-n N] ", desc: "First lines of a file", cat: "text", run: cmdHead}, {name: "tail", usage: "[-n N] ", desc: "Last lines of a file", cat: "text", run: cmdTail}, {name: "wc", usage: "", desc: "Count lines, words, bytes", cat: "text", run: cmdWc}, {name: "grep", usage: "[-i] ", desc: "Search text", cat: "text", run: cmdGrep}, {name: "sort", usage: "[-r] ", desc: "Sort lines", cat: "text", run: cmdSort}, {name: "uniq", usage: "", desc: "Drop repeated lines", cat: "text", run: cmdUniq}, {name: "rev", usage: "", desc: "Reverse each line", cat: "text", run: cmdRev}, {name: "tac", usage: "", desc: "Reverse line order", cat: "text", run: cmdTac}, {name: "nl", usage: "", desc: "Number lines", cat: "text", run: cmdNl}, {name: "strings", usage: "", desc: "Printable strings in a file", cat: "text", run: cmdStrings}, {name: "xxd", usage: "", desc: "Hex dump", cat: "text", run: cmdXxd}, {name: "base64", usage: "[-d] ", desc: "Encode or decode base64", cat: "text", run: cmdBase64}, {name: "md5sum", usage: "", desc: "MD5 checksum", cat: "text", run: cmdHash("md5sum")}, {name: "sha256sum", usage: "", desc: "SHA-256 checksum", cat: "text", run: cmdHash("sha256sum")}, {name: "seq", usage: " [last]", desc: "Print a sequence", cat: "text", run: cmdSeq}, {name: "bc", usage: "", desc: "Calculator", cat: "text", run: cmdBc}, {name: "yes", usage: "[text]", desc: "Repeat until interrupted", cat: "text", run: cmdYes}, // system {name: "whoami", desc: "Current user", cat: "system", run: fn(c) { env.USER }}, {name: "id", desc: "User identity", cat: "system", run: fn(c) { if root { "uid=0(root) gid=0(root) groups=0(root)" } else { "uid=1000(guest) gid=1000(guest) groups=1000(guest)" } }}, {name: "groups", desc: "User groups", cat: "system", run: fn(c) { env.USER }}, {name: "hostname", desc: "System hostname", cat: "system", run: fn(c) { "targa.me" }}, {name: "uname", usage: "[-a]", desc: "System information", cat: "system", run: cmdUname}, {name: "uptime", desc: "Time since boot", cat: "system", run: cmdUptime}, {name: "date", desc: "Current date and time", cat: "system", run: fn(c) { goTime(now(), "Mon Jan _2 15:04:05 2006") }}, {name: "cal", desc: "Calendar", cat: "system", run: cmdCal}, {name: "env", desc: "Environment variables", cat: "system", run: cmdEnv}, {name: "export", usage: "NAME=value", desc: "Set a variable", cat: "system", run: cmdExport}, {name: "alias", desc: "List aliases", cat: "system", run: cmdAlias}, {name: "which", usage: "", desc: "Locate a command", cat: "system", run: cmdWhich}, {name: "type", usage: "", desc: "Describe a command", cat: "system", run: cmdWhich}, {name: "history", usage: "[-c]", desc: "Command history", cat: "system", run: cmdHistory}, {name: "ps", desc: "Running processes", cat: "system", run: cmdPs}, {name: "top", desc: "Process monitor", cat: "system", run: cmdTop}, {name: "kill", usage: "", desc: "Terminate a process", cat: "system", run: cmdKill}, {name: "free", desc: "Memory usage", cat: "system", run: fn(c) { " total used free shared\nMem: 16384 4096 12288 0\nSwap: 0 0 0" }}, {name: "lscpu", desc: "CPU information", cat: "system", run: cmdLscpu}, {name: "dmesg", desc: "Kernel messages", cat: "system", run: cmdDmesg}, {name: "neofetch", desc: "System summary with style", cat: "system", run: cmdNeofetch}, {name: "who", desc: "Logged-in users", cat: "system", run: fn(c) { "guest pts/0 " + goTime(bootTime, "2006-01-02 15:04") + " (127.0.0.1)" }}, {name: "w", desc: "Who is doing what", cat: "system", run: fn(c) { " " + goTime(now(), "15:04:05") + " up " + prettyUptime() + ", 1 user\nUSER TTY FROM LOGIN@ WHAT\nguest pts/0 127.0.0.1 " + goTime(bootTime, "15:04") + " w" }}, {name: "last", desc: "Login history", cat: "system", run: fn(c) { "guest pts/0 127.0.0.1 " + goTime(bootTime, "Mon Jan _2 15:04") + " still logged in\nroot pts/1 10.0.0.66 Thu Sep 11 02:14 - 02:14 (00:00) <- failed, see /var/log/auth.log" }}, {name: "sleep", usage: "", desc: "Wait", cat: "system", run: cmdSleep}, {name: "time", usage: "", desc: "Time a command", cat: "system", run: cmdTime}, {name: "true", desc: "Succeed", cat: "system", run: fn(c) { "" }}, {name: "false", desc: "Fail", cat: "system", run: fn(c) { lastStatus = 1 "" }}, {name: "reboot", desc: "Reload the page", cat: "system", run: cmdReboot}, {name: "shutdown", desc: "Power off", cat: "system", run: cmdShutdown}, // network {name: "ping", usage: "", desc: "Send ICMP echo requests", cat: "network", run: cmdPing}, {name: "ifconfig", desc: "Network interfaces", cat: "network", run: fn(c) { "lo: flags=73 mtu 65536\n inet 127.0.0.1 netmask 255.0.0.0\nwasm0: flags=4163 mtu 1500\n inet 10.0.0.1 netmask 255.255.255.0\n ether 00:de:ad:be:ef:00" }}, {name: "netstat", desc: "Network connections", cat: "network", run: fn(c) { "Proto Local Address Foreign Address State\ntcp 0.0.0.0:22 0.0.0.0:* LISTEN\ntcp 0.0.0.0:443 0.0.0.0:* LISTEN\ntcp 127.0.0.1:31337 10.0.0.66:4444 ESTABLISHED" }}, {name: "nmap", usage: "", desc: "Port scan", cat: "network", run: cmdNmap}, {name: "traceroute", usage: "", desc: "Trace the route to a host", cat: "network", run: cmdTraceroute}, {name: "dig", usage: "", desc: "DNS lookup", cat: "network", run: cmdDig}, {name: "nslookup", usage: "", desc: "DNS lookup", cat: "network", run: cmdDig}, {name: "whois", usage: "", desc: "Domain registration", cat: "network", run: cmdWhois}, {name: "curl", usage: "", desc: "Fetch a URL", cat: "network", run: cmdCurl}, {name: "wget", usage: "", desc: "Download a URL", cat: "network", run: fn(c) { errf("wget", "unable to resolve host address '" + c.arg(0) + "'. This box has no uplink; try curl targa.me") }}, {name: "ssh", usage: "", desc: "Remote login", cat: "network", run: cmdSSH}, // languages {name: "targascript", usage: "[file.tg | -e code]", desc: "Run TargaScript, my own language", cat: "lang", run: cmdTargaScript}, // fun (listed, lives in /usr/games) {name: "fortune", desc: "Random wisdom", cat: "fun", run: fn(c) { escapeHtml(fortunes[rand(fortunes.length)]) }}, {name: "cowsay", usage: "", desc: "A cow says things", cat: "fun", run: cmdCowsay}, {name: "lolcat", desc: "Rainbow text (pipe into it)", cat: "fun", run: cmdLolcat}, {name: "sl", desc: "Steam locomotive", cat: "fun", run: cmdSl}, {name: "cmatrix", desc: "Digital rain", cat: "fun", run: cmdCmatrix}, {name: "figlet", usage: "", desc: "Big letters", cat: "fun", run: cmdFiglet}, {name: "banner", desc: "Show the logo again", cat: "fun", run: fn(c) { trimRightChars(banner, "\n") }}, ] let catLabels = [ {key: "core", label: "General"}, {key: "files", label: "Files"}, {key: "text", label: "Text"}, {key: "system", label: "System"}, {key: "network", label: "Network"}, {key: "lang", label: "Languages"}, {key: "fun", label: "Fun"}, ] // --------------------------------------------------------------------------- // General // --------------------------------------------------------------------------- fn cmdHelp(c) { let b = "" if c.flag("a") || c.has("all") { repeat cat in catLabels { b += green(cat.label) + "\n" repeat cmd in commands { if cmd.cat == cat.key && !cmd.hidden { b += helpRow(cmd) } } b += "\n" } b += dim("Pipes, && and ; work: fortune | cowsay | lolcat. Variables too: echo $USER.\nThere are easter eggs. ls -a is a good start.") return b } b += green("Available commands:") + "\n" repeat cmd in commands { if cmd.cat == "core" || cmd.cat == "lang" || cmd.name == "ls" || cmd.name == "cd" || cmd.name == "cat" { b += helpRow(cmd) } } b += "\n" + dim("This is a small Linux-like shell: try help -a for " + itoa(visibleCount()) + " commands.\nTab completes, Up/Down browse history, Ctrl+C interrupts, Enter skips animations.") b } fn helpRow(cmd) { let label = cmd.name if (cmd.usage ?? "") != "" { label += " " + cmd.usage } if columns() < 64 { return " " + yellow(escapeHtml(label)) + "\n " + dim(cmd.desc) + "\n" } " " + yellow(escapeHtml(pad(label, 24))) + dim(cmd.desc) + "\n" } fn visibleCount() { commands.count(c => !c.hidden) } fn cmdMan(c) { let name = c.arg(0) if name == "" { return "What manual page do you want?\nFor example, try 'man ls'." } if name in aliases { name = fields(aliases[name])[0] } let cmd = commandIndex[name] if cmd == null || cmd.hidden { if name == "woman" { return "No manual entry for woman. (Yes, that is the classic one.)" } return "No manual entry for " + escapeHtml(name) } green("NAME") + "\n " + cmd.name + " - " + cmd.desc + "\n\n" + green("SYNOPSIS") + "\n " + escapeHtml(trimSpace(cmd.name + " " + (cmd.usage ?? ""))) + "\n\n" + green("DESCRIPTION") + "\n " + cmd.desc + ". Part of tsh, the targa shell, which runs entirely in your browser as WebAssembly.\n\n" + green("SEE ALSO") + "\n help(1), help -a" } let themes = { green: "#0f0", amber: "#ffb000", white: "#e6e6e6", cyan: "#00e5ff", pink: "#ff6ec7", red: "#ff4444", purple: "#c084fc", } fn cmdTheme(c) { let name = c.arg(0) if name == "" { let names = themes.keys().sort() return "usage: theme \navailable: " + names.join(", ") } if !(name in themes) { return errf("theme", "unknown theme '" + name + "'") } doc.documentElement.style.setProperty("--fg", themes[name]) dim("theme set to " + name) } fn cmdExit(c) { if root { return leaveRoot() } mode = "closed" closedMessage = "logout\nConnection to targa.me closed.\n" + dim("Press Enter to reconnect.") after(400, () => { terminalContent += closedMessage + "\n" render() }) "" } // ── commands/files.tg ─────────────────────────────────────────────────── // commands/files.tg — ls, cd, cat, tree, find and the other commands for // the virtual filesystem, which is read-only. fn readOnly(name) { fn(c) { let target = c.arg(0) if name == "chmod" || name == "chown" || name == "mv" || name == "cp" || name == "ln" { target = c.arg(1) if target == "" { target = c.arg(0) } } if target == "" { lastStatus = 1 return errf(name, "missing operand") } lastStatus = 1 errf(name, "cannot touch '" + target + "': Read-only file system") } } fn cmdLs(c) { let target = c.arg(0) if target == "" { target = "." } let path = resolve(target) let e = find(path) if e == null { lastStatus = 2 return errf("ls", "cannot access '" + target + "': No such file or directory") } if denied(path, e) { lastStatus = 2 return errf("ls", "cannot open directory '" + target + "': Permission denied") } let long = c.flag("l") let showAll = c.flag("a") if !e.dir { if long { return longRow(path, e) } return escapeHtml(target) } let entries = children(path) let rows = [] if showAll { entries = [{name: ".", dir: true}, {name: "..", dir: true}] + entries } repeat k in entries { if !showAll && k.name.startsWith(".") { continue } if long { rows.insert(longRow(path + "/" + k.name, k)) } else { rows.insert(colorName(k)) } } if long { return "total " + itoa(rows.length) + "\n" + rows.join("\n") } rows.join(" ") } fn colorName(e) { if e.dir { return `` + e.name + `` } if e.exec { return `` + e.name + `` } if e.denied { return `` + e.name + `` } e.name } fn longRow(path, e) { let perms = "-rw-r--r--" let owner = "guest guest" if e.dir && e.link != null { perms = "lrwxrwxrwx" } else if e.dir { perms = "drwxr-xr-x" } else if e.exec { perms = "-rwxr-xr-x" owner = "root root " } if denied(path, e) { perms = perms.slice(0, 1) + "rw-------" if e.dir { perms = "drwx------" } owner = "root root " } if !path.startsWith(home) { owner = "root root " } let size = 4096 if !e.dir { size = fileSize(e) } let name = colorName(e) if e.link != null { name += " -> " + e.link } perms + " 1 " + owner + " " + lpad(itoa(size), 6) + " " + goTime(bootTime, "Jan _2 15:04") + " " + name } fn cmdCd(c) { let target = c.arg(0) if target == "" { target = "~" } if target == "-" { if (env.OLDPWD ?? "") == "" { return errf("cd", "OLDPWD not set") } target = env.OLDPWD } let path = resolve(target) let e = find(path) if e == null { lastStatus = 1 return errf("cd", target + ": No such file or directory") } if !e.dir { lastStatus = 1 return errf("cd", target + ": Not a directory") } if denied(path, e) { lastStatus = 1 return errf("cd", target + ": Permission denied") } if e.link != null { path = e.link } env.OLDPWD = cwd cwd = path env.PWD = cwd "" } fn cmdCat(c) { if c.arg(0) == "" { if c.piped { return escapeHtml(trimRightChars(c.stdin, "\n")) } return errf("cat", "missing file operand") } let out = [] repeat a in c.args { if a.startsWith("-") { continue } let path = resolve(a) let e = find(path) if e == null { lastStatus = 1 out.insert(errf("cat", a + ": No such file or directory")) } else if e.dir { lastStatus = 1 out.insert(errf("cat", a + ": Is a directory")) } else if denied(path, e) { lastStatus = 1 out.insert(errf("cat", a + ": Permission denied")) } else if e.binary { out.insert(errf("cat", a + ": binary file. Try xxd or strings.")) } else { out.insert(rendered(e)) } } out.join("\n") } fn cmdTree(c) { let target = c.arg(0) if target == "" { target = "." } let root = resolve(target) let e = find(root) if e == null || !e.dir { lastStatus = 1 return errf("tree", target + ": No such directory") } if denied(root, e) { lastStatus = 1 return errf("tree", target + ": Permission denied") } let b = `` + escapeHtml(display(root)) + `` + "\n" let dirs = 0 let files = 0 fn rec(dir, indent) { let kids = children(dir) repeat i, k in kids { if k.name.startsWith(".") && !c.flag("a") { continue } let branch = "├── " let next = "│ " if i == kids.length - 1 { branch = "└── " next = " " } b += indent + branch + colorName(k) + "\n" let p = if dir == "/" { "/" + k.name } else { dir + "/" + k.name } if k.dir { dirs += 1 if k.link == null && !denied(p, k) { rec(p, indent + next) } } else { files += 1 } } } rec(root, "") b += "\n" + itoa(dirs) + " directories, " + itoa(files) + " files" b } fn cmdFind(c) { let root = "." let pattern = "" let i = 0 repeat i < c.args.length { if c.args[i] == "-name" && i + 1 < c.args.length { pattern = trimChars(c.args[i + 1], "*") i += 1 } else if !c.args[i].startsWith("-") { root = c.args[i] } i += 1 } let abs = resolve(root) let e = find(abs) if e == null || !e.dir { lastStatus = 1 return errf("find", "'" + root + "': No such file or directory") } let out = [] walk(abs, (p, k, depth) => { if pattern != "" && !k.name.contains(pattern) { return null } let shown = p if abs == cwd && root == "." { shown = "." + (if p.startsWith(abs) { p.slice(abs.length) } else { p }) } out.insert(escapeHtml(shown)) }) if out.length == 0 { return "" } out.join("\n") } fn cmdFile(c) { let target = c.arg(0) if target == "" { return errf("file", "missing file operand") } let path = resolve(target) let e = find(path) if e == null { lastStatus = 1 return errf("file", target + ": cannot open (No such file or directory)") } if e.dir { return escapeHtml(target) + ": directory" } if e.binary { return escapeHtml(target) + ": WebAssembly (wasm) binary module version 0x1 (MVP)" } if e.exec { return escapeHtml(target) + ": tsh script, ASCII text executable" } if e.name.endsWith(".md") { return escapeHtml(target) + ": Markdown text, UTF-8 Unicode" } if e.name.endsWith(".tg") { return escapeHtml(target) + ": TargaScript source, ASCII text (run it: targascript " + escapeHtml(target) + ")" } if e.html { return escapeHtml(target) + ": UTF-8 Unicode text, with very long lines" } escapeHtml(target) + ": ASCII text" } fn cmdStat(c) { let target = c.arg(0) if target == "" { return errf("stat", "missing operand") } let path = resolve(target) let e = find(path) if e == null { lastStatus = 1 return errf("stat", "cannot stat '" + target + "': No such file or directory") } let kind = "regular file" if e.dir { kind = "directory" } " File: " + escapeHtml(target) + "\n Size: " + itoa(fileSize(e)) + "\tBlocks: 8\tIO Block: 4096\t" + kind + "\nDevice: wasm0\tInode: " + itoa(1000 + bytes(path).length * 7) + "\tLinks: 1\nAccess: " + fields(longRow(path, e))[0] + "\nModify: " + goTime(bootTime, "2006-01-02 15:04:05") } fn cmdRm(c) { let target = c.arg(0) if target == "" { return errf("rm", "missing operand") } let recursive = c.flag("r") || c.flag("R") || c.has("--recursive") if (target == "/" || target == "/*") && recursive { if !c.has("--no-preserve-root") { lastStatus = 1 return errf("rm", "it is dangerous to operate recursively on '/'") + "\n" + errf("rm", "use --no-preserve-root to override this failsafe") } let steps = [] repeat d in ["/bin", "/boot", "/etc", "/home/guest/projects", "/home/guest/about.txt", "/usr", "/var/log"] { steps.insert({delay: 120, text: "removing '" + d + "'\n"}) } steps.insert({delay: 600, text: `rm: cannot remove '/': Device or resource busy` + "\n"}) steps.insert({delay: 800, text: "Just kidding. The filesystem is read-only and lives in your browser tab. Everything is fine.\n"}) sequence(steps, false, null) return "" } let path = resolve(target) let e = find(path) if e == null { lastStatus = 1 return errf("rm", "cannot remove '" + target + "': No such file or directory") } if e.dir && !recursive { lastStatus = 1 return errf("rm", "cannot remove '" + target + "': Is a directory") } if denied(path, e) { lastStatus = 1 return errf("rm", "cannot remove '" + target + "': Permission denied") } lastStatus = 1 errf("rm", "cannot remove '" + target + "': Read-only file system") } fn cmdDu(c) { let target = c.arg(0) if target == "" { target = "." } let root = resolve(target) let e = find(root) if e == null { lastStatus = 1 return errf("du", "cannot access '" + target + "': No such file or directory") } else if !e.dir { return itoa((fileSize(e) + 1023) / 1024) + "\t" + escapeHtml(target) } let total = 0 walk(root, (p, k, depth) => { if !k.dir { total += fileSize(k) } }) itoa((total + 1023) / 1024) + "\t" + escapeHtml(target) } fn cmdDf(c) { let total = 0 walk("/", (p, e, depth) => { if !e.dir { total += fileSize(e) } }) let used = itoa((total + 1023) / 1024) "Filesystem 1K-blocks Used Available Use% Mounted on\n/dev/wasm0 16384 " + lpad(used, 4) + " " + lpad(itoa(16384 - (total + 1023) / 1024), 5) + " 1% /\ntmpfs 1024 0 1024 0% /tmp" } // ── commands/text.tg ──────────────────────────────────────────────────── // commands/text.tg — echo, head, grep, sort, base64, the checksums, bc // and the other commands that read and print text. fn lines(s) { let t = trimRightChars(s, "\n") if t == "" { return [] } t.split("\n") } // nFlag reads -n N, -nN or -N: [n, the other arguments] fn nFlag(c, def) { let n = def let rest = [] let i = 0 repeat i < c.args.length { let a = c.args[i] if a == "-n" && i + 1 < c.args.length { n = atoi(c.args[i + 1])[0] i += 1 } else if a.startsWith("-n") { n = atoi(a.slice(2))[0] } else if a.startsWith("-") && a.length > 1 { let [v, ok] = atoi(a.slice(1)) if ok { n = v } } else { rest.insert(a) } i += 1 } [n, rest] } fn cmdEcho(c) { let words = c.args let escapes = false repeat words.length > 0 && (words[0] == "-n" || words[0] == "-e" || words[0] == "-ne" || words[0] == "-en") { escapes = escapes || words[0].contains("e") words = words.slice(1) } let out = words.join(" ") if escapes { out = out.replace(`\n`, "\n").replace(`\t`, "\t") } escapeHtml(out) } fn cmdHead(c) { let [n, rest] = nFlag(c, 10) c.args = rest let [src, err] = c.input("head", 0) if err != "" { return err } let l = lines(src) if l.length > n { l = l.slice(0, n) } escapeHtml(l.join("\n")) } fn cmdTail(c) { let [n, rest] = nFlag(c, 10) c.args = rest let [src, err] = c.input("tail", 0) if err != "" { return err } let l = lines(src) if l.length > n { l = l.slice(l.length - n) } escapeHtml(l.join("\n")) } fn cmdWc(c) { let [src, err] = c.input("wc", 0) if err != "" { return err } let name = c.arg(0) lpad(itoa(lines(src).length), 4) + lpad(itoa(fields(src).length), 5) + lpad(itoa(bytes(src).length), 6) + " " + escapeHtml(name) } fn cmdGrep(c) { let pattern = c.arg(0) if pattern == "" { return "usage: grep [-i] " } let [src, err] = c.input("grep", 1) if err != "" { return err } let ci = c.flag("i") let out = [] repeat l in lines(src) { let hay = l let needle = pattern if ci { hay = l.lower() needle = pattern.lower() } let i = hay.indexOf(needle) if i >= 0 { let end = i + needle.length out.insert(escapeHtml(l.slice(0, i)) + `` + escapeHtml(l.slice(i, end)) + `` + escapeHtml(l.slice(end))) } } if out.length == 0 { lastStatus = 1 } out.join("\n") } fn cmdSort(c) { let [src, err] = c.input("sort", 0) if err != "" { return err } let l = lines(src) l.sort() if c.flag("r") { l.reverse() } escapeHtml(l.join("\n")) } fn cmdUniq(c) { let [src, err] = c.input("uniq", 0) if err != "" { return err } let out = [] repeat l in lines(src) { if out.length == 0 || out[-1] != l { out.insert(l) } } escapeHtml(out.join("\n")) } fn cmdRev(c) { let [src, err] = c.input("rev", 0) if err != "" { return err } escapeHtml(lines(src).map(l => l.reverse()).join("\n")) } fn cmdTac(c) { let [src, err] = c.input("tac", 0) if err != "" { return err } let l = lines(src) l.reverse() escapeHtml(l.join("\n")) } fn cmdNl(c) { let [src, err] = c.input("nl", 0) if err != "" { return err } lines(src).map((l, i) => lpad(itoa(i + 1), 6) + "\t" + escapeHtml(l)).join("\n") } fn cmdStrings(c) { let [src, err] = c.input("strings", 0) if err != "" { return err } let out = [] repeat l in src.split("\n") { let cur = "" repeat ch in l { if ch >= " " && ch <= "~" { cur += ch } else { if cur.length >= 4 { out.insert(cur) } cur = "" } } if cur.length >= 4 { out.insert(cur) } } escapeHtml(out.join("\n")) } fn cmdXxd(c) { let [src, err] = c.input("xxd", 0) if err != "" { return err } let whole = bytes(src) let data = if whole.length > 256 { whole.slice(0, 256) } else { whole } let b = "" let off = 0 repeat off < data.length { let end = min(off + 16, data.length) let chunk = data.slice(off, end) b += dim(zpad(format("%x", off), 8) + ":") + " " repeat i in 0..15 { if i < chunk.length { b += hexEncode(chunk.slice(i, i + 1)) } else { b += " " } if i % 2 == 1 { b += " " } } b += " " repeat ch in chunk { if ch >= 32 && ch < 127 { b += escapeHtml(bytes([ch]).text()) } else { b += "." } } b += "\n" off += 16 } if whole.length > 256 { b += dim("... (" + itoa(whole.length - 256) + " more bytes)") } trimRightChars(b, "\n") } let base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" // base64Encode is base64.StdEncoding.EncodeToString fn base64Encode(data) { let digits = base64Alphabet.chars() let out = "" let i = 0 repeat i < data.length { let n = data.length - i let v = data[i] << 16 if n > 1 { v = v | data[i + 1] << 8 } if n > 2 { v = v | data[i + 2] } out += digits[v >> 18 & 63] + digits[v >> 12 & 63] out += if n > 1 { digits[v >> 6 & 63] } else { "=" } out += if n > 2 { digits[v & 63] } else { "=" } i += 3 } out } // base64Decode is base64.StdEncoding.DecodeString: the bytes, or null when // the input isn't valid (newlines are skipped, padding is required) fn base64Decode(s) { let chars = s.chars().filter(ch => ch != "\n" && ch != "\r") if chars.length % 4 != 0 { return null } let out = [] let i = 0 repeat i < chars.length { let quad = chars.slice(i, i + 4) let last = i + 4 == chars.length let vals = [] let pads = 0 repeat j, ch in quad { if ch == "=" { if !last || j < 2 { return null } pads += 1 } else { if pads > 0 { return null } let v = base64Alphabet.indexOf(ch) if v < 0 { return null } vals.insert(v) } } let v = 0 repeat k in 0..3 { v = v << 6 | (vals[k] ?? 0) } out.insert(v >> 16 & 255) if pads < 2 { out.insert(v >> 8 & 255) } if pads < 1 { out.insert(v & 255) } i += 4 } bytes(out) } fn cmdBase64(c) { let decode = c.flag("d") || c.has("--decode") let src = c.arg(0) let data = "" if src == "" && c.piped { data = trimRightChars(c.stdin, "\n") } else if src == "" { return "usage: base64 [-d] " } else { let e = find(resolve(src)) if e != null && !e.dir { data = trimRightChars(text(e), "\n") } else { data = argsWithoutFlags(c).join(" ") } } if decode { let out = base64Decode(trimSpace(data)) if out == null { lastStatus = 1 return errf("base64", "invalid input") } return escapeHtml(out.text()) } base64Encode(bytes(data)) } // argsWithoutFlags is every argument that isn't a flag (a Ctx method in Go) fn argsWithoutFlags(c) { c.args.filter(a => !(a.startsWith("-") && a.length > 1)) } fn cmdHash(name) { fn(c) { let [src, err] = c.input(name, 0) if err != "" { return err } let label = c.arg(0) if label == "" { label = "-" } if name == "md5sum" { return md5Hex(src) + " " + escapeHtml(label) } sha256Hex(src) + " " + escapeHtml(label) } } fn cmdSeq(c) { let [a, ok] = atoi(c.arg(0)) if !ok { return "usage: seq [last]" } let b = a if c.arg(1) != "" { b = atoi(c.arg(1))[0] } else { a = 1 } let out = [] let i = a repeat i <= b && out.length < 1000 { out.insert(itoa(i)) i += 1 } out.join("\n") } fn cmdYes(c) { let word = c.args.join(" ") if word == "" { word = "y" } let steps = [] repeat i in 1..40 { steps.insert({delay: 40, text: escapeHtml(word) + "\n"}) } steps.insert({delay: 200, text: dim("(yes would go on forever; I stopped it for you)") + "\n"}) sequence(steps, false, null) "" } // satInt converts a float to an integer as Go's int64(v) does in the wasm // build: truncating, saturating at the ends, and NaN gives 0 fn satInt(v) { if v != v { return 0 } if v >= 9223372036854775807.0 { return 9223372036854775807 } if v <= -9223372036854775808.0 { return -9223372036854775807 - 1 } toInt(v) } // --------------------------------------------------------------------------- // bc: a tiny expression evaluator (+ - * / % ^ and parentheses) // --------------------------------------------------------------------------- struct exprParser { s = [], pos = 0, err = false // peek skips spaces and gives the next character, or "" at the end fn peek() { repeat this.pos < this.s.length && this.s[this.pos] == " " { this.pos += 1 } this.s[this.pos] ?? "" } fn expr() { let v = this.term() repeat true { match this.peek() { "+" => { this.pos += 1 v += this.term() } "-" => { this.pos += 1 v -= this.term() } _ => { return v } } } } fn term() { let v = this.factor() repeat true { match this.peek() { "*" => { this.pos += 1 v *= this.factor() } "/" => { this.pos += 1 let d = this.factor() if d == 0 { this.err = true return 0.0 } v /= d } "%" => { this.pos += 1 let d = this.factor() if d == 0 { this.err = true return 0.0 } v = toFloat(satInt(v) % satInt(d)) } _ => { return v } } } } fn factor() { let base = this.unary() if this.peek() == "^" { this.pos += 1 let power = this.factor() let r = 1.0 let i = 0 repeat i < satInt(power) { r *= base i += 1 } return r } base } fn unary() { match this.peek() { "-" => { this.pos += 1 return -this.unary() } "(" => { this.pos += 1 let v = this.expr() if this.peek() != ")" { this.err = true return 0.0 } this.pos += 1 return v } _ => null } let start = this.pos repeat this.pos < this.s.length && (this.s[this.pos] >= "0" && this.s[this.pos] <= "9" || this.s[this.pos] == ".") { this.pos += 1 } if start == this.pos { this.err = true return 0.0 } let [v, ok] = parseFloat(this.s.slice(start, this.pos).join("")) if !ok { this.err = true } v } } fn cmdBc(c) { let src = c.args.join(" ") if src == "" && c.piped { src = trimSpace(c.stdin) } if src == "" { return "usage: bc e.g. bc 2^10 or echo 6*7 | bc" } let p = exprParser(src.chars()) let v = p.expr() if p.err || p.peek() != "" { lastStatus = 1 return errf("bc", "syntax error") } if toFloat(satInt(v)) == v { return itoa(satInt(v)) } formatFloat(v) } // ── commands/system.tg ────────────────────────────────────────────────── // commands/system.tg — uname, uptime, ps, top, neofetch and the other // commands about the (pretend) machine. fn cmdUname(c) { if c.flag("a") { return "targa.me " + version + " wasm32 #1 SMP " + goTime(bootTime, "Mon Jan _2 15:04:05 2006") + " wasm32 WebAssembly GNU/Browser" } if c.flag("r") { return version + "-wasm32" } if c.flag("m") { return "wasm32" } "targa.me" } fn prettyUptime() { let s = toInt(uptimeSeconds()) if s < 60 { return itoa(s) + " sec" } if s < 3600 { return itoa(s / 60) + " min" } itoa(s / 3600) + ":" + lpad(itoa(s % 3600 / 60), 2) } fn cmdUptime(c) { " " + goTime(now(), "15:04:05") + " up " + prettyUptime() + ", 1 user, load average: 0.13, 0.37, 0.42" } fn cmdCal(c) { let t = now() let year = toInt(timeFormat(t, "YYYY")) let month = toInt(timeFormat(t, "MM")) let today = toInt(timeFormat(t, "DD")) let first = parseTime("01/" + timeFormat(t, "MM/YYYY"), "DD/MM/YYYY") let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) let days = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] let title = goTime(t, "January 2006") let padLeft = (20 - bytes(title).length) / 2 let b = " ".repeat(padLeft) + title + "\nSu Mo Tu We Th Fr Sa\n" let col = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(timeFormat(first, "ddd")) b += " ".repeat(col) repeat d in 1..days { let cell = lpad(itoa(d), 2) if d == today { cell = `` + cell + `` } b += cell col += 1 if col % 7 == 0 { b += "\n" } else { b += " " } } trimRightChars(b, " \n") } fn cmdEnv(c) { env.keys().sort().map(k => escapeHtml(k + "=" + env[k])).join("\n") } fn cmdExport(c) { if c.args.length == 0 { return cmdEnv(c) } repeat a in c.args { let i = a.indexOf("=") if i > 0 { env[a.slice(0, i)] = a.slice(i + 1) } else { return errf("export", "'" + a + "': not a valid identifier") } } "" } fn cmdAlias(c) { aliases.keys().sort().map(k => "alias " + escapeHtml(k) + "='" + escapeHtml(aliases[k]) + "'").join("\n") } fn cmdWhich(c) { let name = c.arg(0) if name == "" { return "" } if name in aliases { return escapeHtml(name) + " is aliased to '" + escapeHtml(aliases[name]) + "'" } let cmd = commandIndex[name] if cmd == null { lastStatus = 1 return errf("which", "no " + name + " in (" + (env.PATH ?? "") + ")") } if cmd.hidden { return escapeHtml(name) + " is a shell built-in" } if cmd.cat == "fun" { return "/usr/games/" + name } if cmd.cat == "core" || name == "cd" || name == "pwd" || name == "history" || name == "export" || name == "alias" { return escapeHtml(name) + " is a shell built-in" } "/usr/bin/" + name } fn cmdHistory(c) { if c.flag("c") { history = [] historyIndex = 0 return "" } let b = "" repeat i, h in history { b += dim(lpad(itoa(i + 1), 5)) + " " + escapeHtml(h) + "\n" } trimRightChars(b, "\n") } let processes = [ {pid: 1, user: "root", cpu: "0.0", mem: "0.4", command: "/sbin/init"}, {pid: 2, user: "root", cpu: "0.0", mem: "0.0", command: "[kthreadd]"}, {pid: 42, user: "root", cpu: "0.0", mem: "0.1", command: "/usr/sbin/cron"}, {pid: 666, user: "root", cpu: "0.0", mem: "0.2", command: "/usr/sbin/sshd -D"}, {pid: 1000, user: "guest", cpu: "0.3", mem: "1.2", command: "/bin/tsh"}, {pid: 1337, user: "neo", cpu: "99.9", mem: "13.37", command: "/usr/bin/follow --the-white-rabbit"}, {pid: 4096, user: "guest", cpu: "0.1", mem: "2.0", command: "wasm [targa.wasm]"}, ] fn cmdPs(c) { let b = "USER PID %CPU %MEM COMMAND\n" repeat p in processes { b += pad(p.user, 8) + lpad(itoa(p.pid), 6) + lpad(p.cpu, 5) + lpad(p.mem, 5) + " " + p.command + "\n" } trimRightChars(b, "\n") } fn cmdTop(c) { "top - " + goTime(now(), "15:04:05") + " up " + prettyUptime() + ", 1 user, load average: 0.13, 0.37, 0.42\nTasks: " + itoa(processes.length) + " total, 1 running\n%Cpu(s): 0.4 us, 0.1 sy, 99.5 id\nMiB Mem : 16.0 total, 12.0 free, 4.0 used\n\n" + cmdPs(c) + "\n\n" + dim("(this is a snapshot; a real top would trap you until q)") } fn cmdKill(c) { let target = c.arg(0) if target == "" { return "usage: kill [-9] " } let [pid, ok] = atoi(target) if !ok { return errf("kill", "failed to parse argument: '" + target + "'") } if processes.some(p => p.pid == pid) { return match pid { 1 => errf("kill", "(1) - Operation not permitted. Killing init? Bold.") 1337 => "kill: (1337) - Operation not permitted. Nobody stops Neo." 1000, 4096 => errf("kill", "(" + target + ") - Operation not permitted. That is you.") _ => errf("kill", "(" + target + ") - Operation not permitted") } } lastStatus = 1 errf("kill", "(" + target + ") - No such process") } fn cmdLscpu(c) { "Architecture: wasm32\nCPU(s): " + itoa(cores()) + "\nVendor ID: WebAssembly\nModel name: Virtual wasm32 CPU\nFlags: simd128 bulk-memory easter-eggs" } fn cmdDmesg(c) { "[ 0.000000] Booting targa.me " + version + " (wasm32)\n[ 0.000420] Memory: 16384K available\n[ 0.001337] Detected " + itoa(cores()) + " CPU cores\n[ 0.002000] wasmfs: mounted / read-only\n[ 0.003000] Loading " + itoa(commands.length - visibleCount()) + " easter eggs... done\n[ 0.004000] tsh: session opened for user guest\n[ 0.005000] random: crng init done (browser entropy)" } fn cmdNeofetch(c) { let nav = js.navigator let ua = nav.userAgent let browser = "Browser" if ua.contains("Firefox") { browser = "Firefox" } else if ua.contains("Edg/") { browser = "Edge" } else if ua.contains("Chrome") { browser = "Chrome" } else if ua.contains("Safari") { browser = "Safari" } let host = "Unknown" if ua.contains("Mac") { host = "macOS" } else if ua.contains("Windows") { host = "Windows" } else if ua.contains("Android") { host = "Android" } else if ua.contains("Linux") { host = "Linux" } else if ua.contains("iPhone") || ua.contains("iPad") { host = "iOS" } let scr = js.screen let res = itoa(toInt(scr.width)) + "x" + itoa(toInt(scr.height)) let mem = "unknown" let dm = nav.deviceMemory if type(dm) == "integer" || type(dm) == "float" { mem = itoa(toInt(dm)) + " GB" } let info = [ green("guest") + "@" + green("targa.me"), "-----------------", yellow("OS") + ": targa.me " + version + " wasm32", yellow("Host") + ": " + host + " / " + browser, yellow("Kernel") + ": WebAssembly (TargaScript)", yellow("Uptime") + ": " + prettyUptime(), yellow("Shell") + ": tsh " + version, yellow("Resolution") + ": " + res, yellow("Terminal") + ": " + browser, yellow("CPU") + ": " + itoa(cores()) + " cores", yellow("Memory") + ": " + mem, yellow("Language") + ": " + nav.language, "", `██████████████████`, ] let b = "" let i = 0 repeat i < info.length || i < neofetchLogo.length { let logo = " ".repeat(11) if i < neofetchLogo.length { logo = green(neofetchLogo[i]) } let line = "" if i < info.length { line = info[i] } b += logo + " " + line + "\n" i += 1 } trimRightChars(b, "\n") } fn cmdSleep(c) { let [n, ok] = parseFloat(c.arg(0)) if !ok { return "usage: sleep " } if n > 10 { n = 10 } // A negative or NaN delay waits no time, as setTimeout does let ms = if n != n || n < 0 { 0 } else { toInt(n * 1000) } sequence([{delay: ms, text: ""}], false, null) "" } fn cmdTime(c) { let start = clock() let out = "" if c.args.length > 0 { out = execPipeline(c.args.join(" ")) } let sec = format("%.3f", (clock() - start) / 1000.0) if out != "" { out += "\n" } out + "\nreal\t0m" + sec + "s\nuser\t0m" + sec + "s\nsys\t0m0.000s" } fn cmdReboot(c) { sequence([{delay: 0, text: "Rebooting...\n"}, {delay: 800, text: dim("[ OK ] Reached target Reboot.") + "\n"}], true, () => { js.location.reload() }) "" } fn cmdShutdown(c) { mode = "closed" sequence([ {delay: 0, text: "Broadcast message from root@targa.me:\n\nThe system is going down for power off NOW!\n"}, {delay: 900, text: dim("[ OK ] Stopped tsh session.") + "\n"}, {delay: 500, text: dim("[ OK ] Reached target Power-Off.") + "\n"}, {delay: 700, text: "System halted.\n" + dim("Press Enter to power on.") + "\n"}, ], true, null) "" } // ── commands/network.tg ───────────────────────────────────────────────── // commands/network.tg — ping, nmap, dig, curl, ssh and the other network // commands, all simulated. fn hostOf(c) { let h = c.arg(0) if h == "" { return "" } if h.startsWith("https://") { h = h.slice(8) } if h.startsWith("http://") { h = h.slice(7) } let i = h.indexOf("/") if i >= 0 { h = h.slice(0, i) } h } fn isSelf(h) { h == "targa.me" || h == "www.targa.me" || h == "localhost" || h == "127.0.0.1" || h == "::1" } fn cmdPing(c) { let h = hostOf(c) if h == "" { return "usage: ping " } if !isSelf(h) { lastStatus = 2 return errf("ping", h + ": Temporary failure in name resolution (no uplink from inside a wasm sandbox)") } let steps = [{delay: 0, text: "PING " + escapeHtml(h) + " (127.0.0.1) 56(84) bytes of data.\n"}] let total = 0.0 repeat i in 1..4 { let t = 0.02 + random() * 0.05 total += t steps.insert({delay: 1000, text: "64 bytes from " + escapeHtml(h) + " (127.0.0.1): icmp_seq=" + itoa(i) + " ttl=64 time=" + format("%.3f", t) + " ms\n"}) } steps.insert({delay: 300, text: "\n--- " + escapeHtml(h) + " ping statistics ---\n4 packets transmitted, 4 received, 0% packet loss\nrtt avg = " + format("%.3f", total / 4) + " ms\n"}) sequence(steps, false, null) "" } fn cmdNmap(c) { let h = hostOf(c) if h == "" { return "usage: nmap " } if !isSelf(h) { return "Starting Nmap 7.95 ( https://nmap.org )\nNote: Host seems down. This sandbox has no network. Try nmap targa.me" } let steps = [ {delay: 0, text: "Starting Nmap 7.95 ( https://nmap.org ) at " + goTime(now(), "2006-01-02 15:04") + "\n"}, {delay: 900, text: "Nmap scan report for " + escapeHtml(h) + " (127.0.0.1)\nHost is up (0.00013s latency).\n\nPORT STATE SERVICE\n"}, {delay: 500, text: "22/tcp open ssh " + dim("try: ssh root@localhost") + "\n"}, {delay: 300, text: "80/tcp closed http\n"}, {delay: 300, text: "443/tcp open https " + dim("you are here") + "\n"}, {delay: 300, text: "31337/tcp open Elite " + dim("suspicious. check netstat") + "\n"}, {delay: 400, text: "\nNmap done: 1 IP address (1 host up) scanned in 2.70 seconds\n"}, ] sequence(steps, false, null) "" } fn cmdTraceroute(c) { let h = hostOf(c) if h == "" { return "usage: traceroute " } let steps = [ {delay: 0, text: "traceroute to " + escapeHtml(h) + " (127.0.0.1), 30 hops max\n"}, {delay: 600, text: " 1 your-browser (127.0.0.1) 0.041 ms\n"}, {delay: 600, text: " 2 wasm-sandbox (10.0.0.1) 0.113 ms\n"}, {delay: 600, text: " 3 the-cloud ☁ (10.0.0.2) 13.370 ms\n"}, ] if isSelf(h) { steps.insert({delay: 600, text: " 4 " + escapeHtml(h) + " (127.0.0.1) 0.001 ms " + dim("you never left") + "\n"}) } else { steps.insert({delay: 600, text: " 4 * * *\n"}) steps.insert({delay: 600, text: " 5 * * * " + dim("(no uplink from inside a wasm sandbox)") + "\n"}) } sequence(steps, false, null) "" } fn cmdDig(c) { let h = hostOf(c) if h == "" { h = "targa.me" } if !isSelf(h) { return ";; connection timed out; no servers could be reached (no uplink)" } ";; ANSWER SECTION:\n" + escapeHtml(h) + ".\t\t300\tIN\tA\t127.0.0.1\n" + escapeHtml(h) + ".\t\t300\tIN\tTXT\t\"you are already here\"\n\n;; Query time: 0 msec\n;; SERVER: 127.0.0.1#53" } fn cmdWhois(c) { let h = hostOf(c) if h == "" { return "usage: whois " } if !isSelf(h) { return "No match for \"" + escapeHtml(h) + "\" (this sandbox only knows about itself)" } "Domain Name: TARGA.ME\nRegistrant: Miguel Targa\nRegistrant Job: software engineer, reverse engineer\nStatus: clientTransferProhibited (nice try)\nName Server: 127.0.0.1\nContact: contact@targa.me" } fn cmdCurl(c) { let h = hostOf(c) if h == "" { return "curl: try 'curl targa.me'" } if isSelf(h) { return escapeHtml("\n\n\n") } if h.contains("wttr.in") { return "Weather report: your location\n\n \\ / Partly cloudy\n _ /\"\".-. +20(22) °C\n \\_( ). ↗ 11 km/h\n /(___(__) 10 km\n 0.0 mm\n\n" + dim("(fake; there is no uplink from inside a wasm sandbox)") } if h.contains("parrot.live") { return "🦜 🦜 🦜 (imagine it dancing)" } if h.contains("google.com") { return "curl: (7) Failed to connect to " + escapeHtml(h) + " port 443: the sandbox has no uplink. Also, it's Google. They're fine." } lastStatus = 6 errf("curl", "(6) Could not resolve host: " + h + " (no uplink from inside a wasm sandbox)") } // cmdSSH is the fake login easter egg (kept from v1). fn cmdSSH(c) { if c.args.length == 0 { return dim(`usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-J destination] [-L address] [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-P tag] [-p port] [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] destination [command [argument ...]] ssh [-Q query_option]`) } let dest = c.arg(0) let user = "guest" let host = dest let i = dest.indexOf("@") if i >= 0 { user = dest.slice(0, i) host = dest.slice(i + 1) } if !isSelf(host) { lastStatus = 255 return errf("ssh", "connect to host " + host + " port 22: Connection refused") } if user != "root" { lastStatus = 255 return escapeHtml(user + "@" + host) + ": Permission denied (publickey). " + dim("Hint: who else is in /etc/passwd?") } let h = escapeHtml(host) sequence([ {delay: 0, text: dim("Connecting to " + h + "...") + "\n"}, {delay: 2000, text: yellow("Warning: Remote host identification has changed!") + "\n" + green("SSH key fingerprint:") + " SHA256:12:f8:7e:78:61:b4:bf:e2:de:24:15:96:4e:d4:72:53\n" + dim("Are you sure you want to continue connecting (yes/no)?") + " "}, {delay: 2000, text: "yes\n" + dim("root@" + h + "'s password:") + " "}, {delay: 2000, text: "\n" + `Permission denied, please try again.` + "\n" + dim("root@" + h + "'s password:") + " "}, {delay: 2000, text: "\n" + dim("Last login: " + goTime(now(), "Mon Jan 2 15:04:05 2006") + " from 127.0.0.1") + "\nWelcome to the Matrix, " + green("Neo") + ".\n"}, ], true, breakIn) "" } // ── commands/fun.tg ───────────────────────────────────────────────────── // commands/fun.tg — cowsay, lolcat, sl, cmatrix and figlet. fn cmdCowsay(c) { let msg = c.args.join(" ") if msg == "" && c.piped { msg = trimSpace(c.stdin) } if msg == "" { msg = "Moo?" } let wrapped = [] repeat para in msg.split("\n") { let line = "" repeat w in fields(para) { if line != "" && line.length + 1 + w.length > 40 { wrapped.insert(line) line = w } else if line == "" { line = w } else { line += " " + w } } wrapped.insert(line) } let width = 0 repeat l in wrapped { if l.length > width { width = l.length } } let b = " " + "_".repeat(width + 2) + "\n" repeat i, l in wrapped { let left = "|" let right = "|" if wrapped.length == 1 { left = "<" right = ">" } else if i == 0 { left = "/" right = "\\" } else if i == wrapped.length - 1 { left = "\\" right = "/" } b += left + " " + escapeHtml(pad(l, width)) + " " + right + "\n" } b += " " + "-".repeat(width + 2) + "\n" let eyes = "oo" if c.flag("d") { eyes = "xx" } b += " \\ ^__^\n \\ (" + eyes + ")\\_______\n (__)\\ )\\/\\\n ||----w |\n || ||" b } fn cmdLolcat(c) { let src = c.args.join(" ") if c.piped { src = trimRightChars(c.stdin, "\n") } if src == "" { return "usage: something | lolcat" } let b = "" let line = 0 let col = 0 repeat r in src { if r == "\n" { b += "\n" line += 1 col = 0 continue } let hue = (col * 6 + line * 20) % 360 b += `` + escapeHtml(r) + `` col += 1 } b } fn cmdSl(c) { let cols = columns() let width = trainFrames[0].length let off = cols frames(35, false, i => { off -= 1 let b = "" repeat line in trainFrames { let row = "" if off >= 0 { row = " ".repeat(off) + line } else if -off < width { row = line.slice(-off) } if row.length > cols { row = row.slice(0, cols) } b += escapeHtml(row) + "\n" } [b, off <= -width] }, "") "" } fn cmdCmatrix(c) { let cols = min(columns(), 120) let rows = 16 let glyphs = "ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ0123456789Z*+-<>|:=".chars() let drops = [] repeat x in 1..cols { drops.insert({y: -rand(rows * 2), length: 4 + rand(10), speed: 1 + rand(2)}) } let grid = [] repeat y in 1..rows { let row = [] repeat x in 1..cols { row.insert(glyphs[rand(glyphs.length)]) } grid.insert(row) } frames(70, true, f => { let b = "" repeat r in 0..rows - 1 { repeat x in 0..cols - 1 { let d = drops[x] if r == d.y { b += `` + grid[r][x] + `` } else if r < d.y && r > d.y - d.length { if r > d.y - d.length / 2 { b += `` + grid[r][x] + `` } else { b += `` + grid[r][x] + `` } } else { b += " " } } b += "\n" } repeat x in 0..cols - 1 { if f % drops[x].speed == 0 { drops[x].y += 1 } if drops[x].y - drops[x].length > rows { drops[x] = {y: -rand(rows), length: 4 + rand(10), speed: 1 + rand(2)} } if rand(20) == 0 { grid[rand(rows)][x] = glyphs[rand(glyphs.length)] } } [b, f >= 90] }, green("Wake up, Neo...") + "\n") "" } fn cmdFiglet(c) { let msg = c.args.join(" ") if msg == "" && c.piped { msg = trimSpace(c.stdin) } if msg == "" { return "usage: figlet " } // Go cuts at 24 bytes; a character cut in half reads as one invalid // character per byte left, each drawn as ? let chars = [] let size = 0 repeat ch in msg { let n = bytes(ch).length if size + n > 24 { repeat k in 1..24 - size { chars.insert("?") } break } chars.insert(ch) size += n } let rows = ["", "", "", "", ""] repeat ch in chars { let up = ch.upper() if up.length != 1 { up = ch } // Go upper-cases one character to one let g = figFont[up] ?? figFont["?"] repeat r in 0..4 { rows[r] += g[r] + " " } } rows.map(r => escapeHtml(trimRightChars(r, " "))).join("\n") } // A compact 5-row block font. let figFont = { "A": [" ▄▀▄ ", "█▀▀█ ", "█ █ ", "█ █ ", "▀ ▀ "], "B": ["█▀▀▄ ", "█▄▄▀ ", "█ █ ", "█▄▄▀ ", "▀▀▀ "], "C": [" ▄▀▀ ", "█ ", "█ ", "█ ", " ▀▀▀ "], "D": ["█▀▀▄ ", "█ █ ", "█ █ ", "█ █ ", "▀▀▀ "], "E": ["█▀▀▀ ", "█▄▄ ", "█ ", "█ ", "▀▀▀▀ "], "F": ["█▀▀▀ ", "█▄▄ ", "█ ", "█ ", "▀ "], "G": [" ▄▀▀ ", "█ ", "█ ▀█ ", "█ █ ", " ▀▀▀ "], "H": ["█ █ ", "█▄▄█ ", "█ █ ", "█ █ ", "▀ ▀ "], "I": ["▀█▀ ", " █ ", " █ ", " █ ", "▀▀▀ "], "J": [" ▀█ ", " █ ", " █ ", "█ █ ", " ▀▀ "], "K": ["█ ▄▀ ", "█▀▄ ", "█ █ ", "█ █ ", "▀ ▀ "], "L": ["█ ", "█ ", "█ ", "█ ", "▀▀▀▀ "], "M": ["█▄ ▄█ ", "█ ▀ █ ", "█ █ ", "█ █ ", "▀ ▀ "], "N": ["█▄ █ ", "█ ▀▄█ ", "█ █ ", "█ █ ", "▀ ▀ "], "O": [" ▄▀▄ ", "█ █", "█ █", "█ █", " ▀▄▀ "], "P": ["█▀▀▄ ", "█▄▄▀ ", "█ ", "█ ", "▀ "], "Q": [" ▄▀▄ ", "█ █", "█ ▄ █", "█ ▀█", " ▀▀▀▀"], "R": ["█▀▀▄ ", "█▄▄▀ ", "█ ▀▄ ", "█ █ ", "▀ ▀ "], "S": [" ▄▀▀ ", "▀▄▄ ", " █ ", " █ ", "▀▀▀ "], "T": ["▀▀█▀▀ ", " █ ", " █ ", " █ ", " ▀ "], "U": ["█ █ ", "█ █ ", "█ █ ", "█ █ ", " ▀▀ "], "V": ["█ █ ", "█ █ ", "█ █ ", " █ █ ", " ▀ "], "W": ["█ █ ", "█ █ ", "█ █ █ ", "█ █ █ ", " ▀ ▀ "], "X": ["█ █ ", " █ █ ", " █ ", " █ █ ", "▀ ▀ "], "Y": ["█ █ ", " █ █ ", " █ ", " █ ", " ▀ "], "Z": ["▀▀▀▀█ ", " ▄▀ ", " ▄▀ ", " ▄▀ ", "▀▀▀▀▀ "], "0": [" ▄▀▄ ", "█ ▄█", "█ ▄ █", "█▄ █", " ▀▄▀ "], "1": [" ▄█ ", " █ ", " █ ", " █ ", "▀▀▀▀"], "2": ["▀▀▀▄ ", " █ ", " ▄▀ ", "█ ", "▀▀▀▀ "], "3": ["▀▀▀▄ ", " ▄▀ ", " █ ", " █ ", "▀▀▀ "], "4": ["█ █ ", "█ █ ", "▀▀▀█ ", " █ ", " ▀ "], "5": ["█▀▀▀ ", "█▄▄ ", " █ ", " █ ", "▀▀▀ "], "6": [" ▄▀▀ ", "█▄▄ ", "█ █ ", "█ █ ", " ▀▀ "], "7": ["▀▀▀█ ", " ▄▀ ", " █ ", " █ ", " ▀ "], "8": [" ▄▀▄ ", " ▀▄▀ ", "█ █", "█ █", " ▀▀▀ "], "9": [" ▄▀▄ ", "█ █", " ▀▀█ ", " █ ", " ▀▀ "], " ": [" ", " ", " ", " ", " "], "!": ["█ ", "█ ", "█ ", " ", "▀ "], "?": ["▀▀▄ ", " █ ", " ▄▀ ", " ", " ▀ "], ".": [" ", " ", " ", " ", "▀ "], "-": [" ", " ", "▀▀▀ ", " ", " "], "_": [" ", " ", " ", " ", "▀▀▀ "], } // ── eggs.tg ───────────────────────────────────────────────────────────── // eggs.tg — easter eggs: hidden from help, completion and /bin. Found by // trying. let flagFound = false fn cmdFlagFound() { if flagFound { return green("You already found it.") + " Show-off." } flagFound = true after(50, () => { cmdFiglet(Ctx(["YOU WIN"])) }) green("Correct!") + " 🎉\n\n" + cmdFiglet(Ctx(["NICE"])) + "\n\nYou followed the trail: hidden file → log → base64.\nThat's the same instinct that finds real bugs.\nIf you want to talk about it: " + `contact@targa.me` } // --------------------------------------------------------------------------- // sudo // --------------------------------------------------------------------------- let sudoAttempts = 0 fn cmdSudo(c) { if c.args.length == 0 { return "usage: sudo " } if root { return "You are root already. sudo is for mortals." } sudoAttempts += 1 let full = c.args.join(" ") if c.arg(0) == "make" && c.arg(1) == "sandwich" || full.contains("make me a sandwich") { return "Okay. 🥪" } if c.arg(0) == "rm" && (c.has("-rf") || c.has("-fr")) && (c.has("/") || c.has("/*")) { return cmdRm(Ctx(c.args.slice(1) + ["--no-preserve-root"])) } if c.arg(0) == "su" || c.arg(0) == "-i" || c.arg(0) == "bash" || c.arg(0) == "sh" || c.arg(0) == "-s" { return "[sudo] password for guest: \n" + errf("sudo", "3 incorrect password attempts") + "\n" + dim("Root has no password. Root doesn't need you.") } match sudoAttempts { 1 => "[sudo] password for guest: \n" + `guest is not in the sudoers file. This incident will be reported.` 2 => "[sudo] password for guest: \n" + `guest is still not in the sudoers file. The incident has been reported again.` 3 => `sudo: a report has been sent to root. root is not amused.` _ => `sudo: seriously? Try: cat /etc/sudoers... oh wait.` } } // --------------------------------------------------------------------------- // vim: the classic trap // --------------------------------------------------------------------------- let vimFile = "" fn cmdVim(c) { mode = "vim" vimFile = c.arg(0) terminalContent = vimScreen() "" } fn vimScreen() { let name = "[No Name]" let body = "" if vimFile != "" { name = vimFile let e = find(resolve(vimFile)) if e != null && !e.dir && !denied(resolve(vimFile), e) && !e.binary { let t = text(e) repeat t.endsWith("\n") { t = t.slice(0, -1) } body = escapeHtml(t) + "\n" } } let rows = 14 if body != "" { rows -= body.count("\n") if rows < 2 { rows = 2 } } body + (`~` + "\n").repeat(rows) + ` ` + escapeHtml(name) + ` ` + dim("0,0-1 All") + "\n" + dim("Type :q and press to exit Vim. Yes, really. This is the whole joke.") + "\n" } fn vimInput(raw) { if raw in [":q", ":q!", ":wq", ":x", ":qa", ":qa!", "ZZ", "ZQ", ":wq!", ":exit", ":quit"] { mode = "shell" terminalContent = banner + dim("You escaped vim. Achievement unlocked.") + "\n" if raw == ":wq" || raw == ":x" || raw == ":wq!" || raw == "ZZ" { terminalContent = banner + dim(`"` + vimName() + `" E212: Can't open file for writing (read-only filesystem). You escaped anyway.`) + "\n" } } else if raw == ":help" || raw == ":h" { terminalContent += dim("The help says: type :q to quit. Which you could have guessed.") + "\n" } else if raw in ["i", "a", "o", "I", "A", "O"] { terminalContent += dim("-- INSERT -- (there is nowhere to insert. press Esc in your mind, then :q)") + "\n" } else if raw == ":w" { terminalContent += `E212: Can't open file for writing` + "\n" } else if raw == "" { terminalContent += dim("~") + "\n" } else if raw.startsWith(":") { terminalContent += `E492: Not an editor command: ` + escapeHtml(raw.slice(1)) + `` + "\n" } else { terminalContent += dim("Vim ignores '" + escapeHtml(raw) + "'. It's not that kind of program. :q to leave.") + "\n" } render() } fn vimName() { if vimFile == "" { return "Untitled" } vimFile } // --------------------------------------------------------------------------- // python REPL joke, git, apt // --------------------------------------------------------------------------- fn cmdPython(c) { if c.flag("c") && c.args.length > 1 { let src = c.args.slice(1).join(" ") if src.startsWith("print(") && src.endsWith(")") { // Trim quotes from both ends, like Go's strings.Trim let inner = src.slice(6, -1) repeat inner.startsWith("\"") || inner.startsWith("'") { inner = inner.slice(1) } repeat inner.endsWith("\"") || inner.endsWith("'") { inner = inner.slice(0, -1) } let v = cmdBc(Ctx([inner])) let hasDigit = "0123456789".chars().some(d => inner.contains(d)) if !v.contains("syntax error") && hasDigit && !inner.contains("\"") && !inner.contains("'") { return v } return escapeHtml(inner) } return `SyntaxError: this Python only knows print(). Try: python -c "print(2**10)" (hint: use ^ ; it is bc underneath)` } if c.arg(0) != "" { return errf("python", "can't open file '" + c.arg(0) + "': [Errno 2] No such file or directory") } "Python 3.13.0 (wasm32)\n>>> import antigravity\n>>> " + dim("(REPL not available; try python -c \"print(6*7)\")") } fn cmdGit(c) { match c.arg(0) { "" => "usage: git \n\nTry: git status, git log, git push, git blame" "status" => "On branch main\nYour branch is up to date with 'origin/main'.\n\nnothing to commit, working tree clean " + dim("(it's a read-only filesystem, after all)") "log" => yellow("commit 0a750b5") + " ssh easter egg\n" + yellow("commit 6449ac9") + " add clear\n" + yellow("commit ab00e9d") + " easter egg\n" + yellow("commit 780bab2") + " new version\n" + yellow("commit 4b588b8") + " first commit\n" + dim("(real history: github.com/migueltarga)") "push" => "fatal: could not read Username for 'https://github.com': No such device or address\n" + dim("You're pushing from inside a browser tab. Ambitious.") "pull", "fetch" => "Already up to date." "blame" => "^0a750b5 (Miguel Targa 2025-02-26 1) everything\n" + dim("It was me. It's always me.") "commit" => "nothing to commit, working tree clean" "clone" => { let repo = c.arg(1) if repo.startsWith("https://github.com/") { repo = repo.slice("https://github.com/".length) } if repo.endsWith(".git") { repo = repo.slice(0, -4) } "Cloning into '" + escapeHtml(repo) + "'...\nfatal: no uplink from inside a wasm sandbox. Open the link with your other hand." } "checkout", "switch" => "error: pathspec '" + escapeHtml(c.arg(1)) + "' did not match any file(s) known to git" "stash" => "Saved working directory and index state WIP on main: nothing" "rebase" => "fatal: it is not possible to rebase reality." _ => "git: '" + escapeHtml(c.arg(0)) + "' is not a git command. See 'git --help'." } } fn cmdApt(c) { let pkg = c.arg(1) match c.arg(0) { "install", "add" => { if pkg == "" { return "E: Nothing to install." } if commandIndex[pkg] != null { return pkg + " is already the newest version. Try running it." } let steps = [ {delay: 0, text: "Reading package lists... Done\nBuilding dependency tree... Done\n"}, {delay: 500, text: "The following NEW packages will be installed:\n " + escapeHtml(pkg) + "\n"}, {delay: 500, text: "Get:1 https://targa.me/apt " + escapeHtml(pkg) + " [" + itoa(100 + rand(900)) + " kB]\n"}, {delay: 700, text: `E: Failed to fetch https://targa.me/apt/` + escapeHtml(pkg) + ` Read-only file system (and no uplink)` + "\n"}, ] sequence(steps, false, null) "" } "update" => "Hit:1 https://targa.me/apt stable InRelease\nReading package lists... Done\nAll packages are up to date. Everything is compiled in." "upgrade" => "0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded." "remove", "purge" => "E: Unable to remove " + escapeHtml(pkg) + ": Read-only file system" "moo" => " (__)\n (oo)\n /------\\/\n / | ||\n * /\\---/\\\n ~~ ~~\n...\"Have you mooed today?\"..." _ => "apt " + version + " (wasm32)\nUsage: apt install | apt update | apt moo" } } // --------------------------------------------------------------------------- // Bigger eggs // --------------------------------------------------------------------------- fn cmdStarwars(c) { let scenes = [ " A long time ago in a browser tab\n far, far away....", " STAR WARS\n Episode IV.V: A NEW SHELL", " It is a period of civil war between tabs and spaces.\n Rebel programs, striking from a hidden wasm module,\n have won their first victory against the evil\n JavaScript Empire.", " .-. /\\ \n (o o) / \\ \n | O \\ / \\ \n \\ \\ /______\\\n `~~~' || \n || \n (it's a droid, use your imagination)", " THE END", ] let steps = [] repeat i, s in scenes { let d = if i == 0 { 0 } else { 2500 } steps.insert({delay: d, text: "\n" + yellow(escapeHtml(s)) + "\n"}) } steps.insert({delay: 1500, text: "\n " + dim("the full ascii movie lives at towel.blinkenlights.nl (real telnet needed)") + "\n"}) sequence(steps, false, null) "" } fn cmdHack(c) { let target = c.arg(0) if target == "" { target = "the mainframe" } let t = escapeHtml(target) let steps = [ {delay: 0, text: green("[*]") + " Initializing hack on " + t + "...\n"}, {delay: 600, text: green("[*]") + " Bypassing firewall............ " + green("OK") + "\n"}, {delay: 600, text: green("[*]") + " Enumerating open ports........ 22, 443, 31337\n"}, {delay: 600, text: green("[*]") + " Cracking SHA-256 with rainbow tables... " + yellow("(this would take longer than the universe)") + "\n"}, {delay: 900, text: green("[*]") + " Uploading payload [" + "=".repeat(10) + " ] 50%\n"}, {delay: 700, text: green("[*]") + " Uploading payload [" + "=".repeat(20) + "] 100%\n"}, ] // This machine is the one target that falls if isSelf(target) { sequence(steps, true, breakIn) return "" } steps = [ ...steps, {delay: 600, text: green("[*]") + " Gaining root......... " + `DENIED` + "\n"}, {delay: 800, text: green("[+]") + " ACCESS GRANTED " + dim("(to the guest account, which you already had)") + "\n"}, {delay: 600, text: dim("I'm in. 😎 Real hacking is 90% reading logs. Try: cat /var/log/auth.log") + "\n"}, ] sequence(steps, false, null) "" } // --------------------------------------------------------------------------- // The table. It comes last: top-level values may only use functions defined // above them. // --------------------------------------------------------------------------- let eggCommands = [ {name: "sudo", hidden: true, run: cmdSudo}, {name: "su", hidden: true, run: fn(c) { lastStatus = 1 "Password: \n" + errf("su", "Authentication failure") }}, {name: "passwd", hidden: true, run: fn(c) { "Changing password for guest.\nCurrent password: \n" + errf("passwd", "Authentication token manipulation error. This is a static site.") }}, {name: "vim", hidden: true, run: cmdVim}, {name: "emacs", hidden: true, run: fn(c) { "emacs: not installed. This machine has taste. Try vim." }}, {name: "nano", hidden: true, run: fn(c) { "nano: not installed. Real hackers use vim. (You can try it.)" }}, {name: "python", hidden: true, run: cmdPython}, {name: "node", hidden: true, run: fn(c) { "Welcome to Node.js.\n> " + dim("Just kidding. This shell is TargaScript running on wasm. Try: uname -a") }}, {name: "go", hidden: true, run: fn(c) { if c.arg(0) == "version" { return "go version go1.25 js/wasm" } "Go is a tool for managing Go source code.\n\nUsage:\n\n\tgo [arguments]\n\nTargaScript, the language of this shell, was built with it. Try: go version" }}, {name: "gcc", hidden: true, run: fn(c) { "gcc: fatal error: no input files. Also, no compiler. Also, this is a browser." }}, {name: "make", hidden: true, run: fn(c) { if c.arg(0) == "coffee" { return "make: *** No rule to make target 'coffee'. Try: coffee" } "make: *** No targets specified and no makefile found. Stop." }}, {name: "git", hidden: true, run: cmdGit}, {name: "docker", hidden: true, run: fn(c) { "Cannot connect to the Docker daemon. You are already inside a sandbox inside a sandbox." }}, {name: "apt", hidden: true, run: cmdApt}, {name: "yum", hidden: true, run: cmdApt}, {name: "pacman", hidden: true, run: fn(c) { "ᗧ · · · ᗣ " + dim("(wrong pacman)") }}, {name: "brew", hidden: true, run: fn(c) { "==> Brewing... ☕\nError: No formulae found. This is a static site, not a Mac." }}, {name: "npm", hidden: true, run: fn(c) { if c.arg(0) == "install" || c.arg(0) == "i" { return "added 1,337 packages in 0.4s\n\n" + yellow("42 vulnerabilities") + " (13 moderate, 29 high)\n\n" + dim("(fake, but you believed it for a second)") } "npm: run 'npm install' if you want to feel something" }}, {name: "coffee", hidden: true, run: fn(c) { "HTTP/1.1 418 I'm a teapot\n\n ( (\n ) )\n .______.\n | |]\n \\ /\n `----'" }}, {name: "tea", hidden: true, run: fn(c) { "Brewing... 🍵 Steep for 3 minutes. Do not microwave." }}, {name: "beer", hidden: true, run: fn(c) { "🍺 Cheers! You've earned it after all these commands." }}, {name: "hello", hidden: true, run: fn(c) { "Hello, world! 👋 It's a pleasure to have you here." }}, {name: "hi", hidden: true, run: fn(c) { "Hi! Type help if you're lost, or ls -a if you're curious." }}, {name: "thanks", hidden: true, run: fn(c) { "You're welcome. Come back anytime." }}, {name: "42", hidden: true, run: fn(c) { "The answer to life, the universe and everything. But what was the question?" }}, {name: "xyzzy", hidden: true, run: fn(c) { "Nothing happens." }}, {name: "plugh", hidden: true, run: fn(c) { "A hollow voice says 'Fool.'" }}, {name: "moo", hidden: true, run: fn(c) { " (__)\n (oo)\n /------\\/\n / | ||\n * /\\---/\\\n ~~ ~~\n...\"Have you mooed today?\"..." }}, {name: "dice", hidden: true, run: fn(c) { "🎲 You rolled a " + itoa(1 + rand(6)) }}, {name: "roll", hidden: true, run: fn(c) { "🎲 You rolled a " + itoa(1 + rand(6)) }}, {name: "flip", hidden: true, run: fn(c) { if rand(2) == 0 { return "🪙 Heads" } "🪙 Tails" }}, {name: "coin", hidden: true, run: fn(c) { if rand(2) == 0 { return "🪙 Heads" } "🪙 Tails" }}, {name: "rickroll", hidden: true, run: fn(c) { "Never gonna give you up\nNever gonna let you down\n" + dim("(that's all you get; copyright is a thing)") }}, {name: "starwars", hidden: true, run: cmdStarwars}, {name: "hack", hidden: true, run: cmdHack}, {name: "hackerman", hidden: true, run: cmdHack}, {name: "matrix", hidden: true, run: cmdCmatrix}, {name: "flag", hidden: true, run: fn(c) { "There is a flag hidden on this system. It starts with targa{ and is not in /root.\nRun it as a command when you find it." }}, {name: "hint", hidden: true, run: fn(c) { "Hints:\n 1. ls -a shows hidden files.\n 2. Logs are worth reading.\n 3. base64 is not encryption.\n 4. Once you have the flag, type it as a command." }}, {name: "credits", hidden: true, run: fn(c) { green("targa.me " + version) + "\n Shell, filesystem, animations and " + itoa(commands.length) + " commands\n written in TargaScript, running entirely in your browser\n on its WebAssembly interpreter.\n No frameworks, no analytics, no backend.\n\n Source: it's plain TargaScript. Read it at " + dim("targa.me/site.tg") }}, {name: "source", hidden: true, run: fn(c) { "The source is plain TargaScript: targa.me/site.tg" }}, {name: "konami", hidden: true, run: fn(c) { "↑ ↑ ↓ ↓ ← → ← → B A +30 lives. Unfortunately this is not that kind of site." }}, {name: "love", hidden: true, run: fn(c) { "❤️ <3 Made with love, TargaScript and questionable amounts of coffee." }}, {name: "vibe", hidden: true, run: fn(c) { "vibe check: passed ✅" }}, {name: "wasm", hidden: true, run: fn(c) { "You're soaking in it. Everything you see was rendered by TargaScript code, run by an interpreter compiled to WebAssembly." }}, {name: "tsh", hidden: true, run: fn(c) { "tsh, version " + version + " (wasm32)\nThe targa shell. You are already in it." }}, {name: "bash", hidden: true, run: fn(c) { "bash: not installed. You have tsh, which is like bash but with more easter eggs." }}, {name: "zsh", hidden: true, run: fn(c) { "zsh: not installed. No oh-my-zsh either. Just tsh." }}, {name: "sh", hidden: true, run: fn(c) { "sh: already running. It's turtles all the way down." }}, {name: "telnet", hidden: true, run: fn(c) { if c.args.join(" ").contains("towel.blinkenlights.nl") { return cmdStarwars(c) } "Trying " + escapeHtml(c.arg(0)) + "...\ntelnet: Unable to connect to remote host: No uplink. Try: telnet towel.blinkenlights.nl" }}, {name: "mail", hidden: true, run: fn(c) { "You have 1 new message.\n\nFrom: root@targa.me\nSubject: Welcome\n\n Enjoy the box. Don't touch /root.\n Also: contact@targa.me is a real inbox." }}, {name: "wall", hidden: true, run: fn(c) { "Broadcast message from guest@targa.me (pts/0):\n\n" + escapeHtml(c.args.join(" ")) + "\n\n" + dim("(nobody else is logged in)") }}, {name: "cowthink", hidden: true, run: fn(c) { cmdCowsay(c).replace("\\", "o") }}, {name: "sudoku", hidden: true, run: fn(c) { "sudoku: not installed. But sudo is, and it doesn't like you either." }}, {name: "fuck", hidden: true, run: fn(c) { if history.length >= 2 { return "The Fuck: did you mean " + yellow(escapeHtml(history[history.length - 2])) + "? Probably. Try again with fewer typos." } "Language! Also, nothing to correct yet." }}, {name: "lol", hidden: true, run: fn(c) { "😂" }}, {name: "whatis", hidden: true, run: fn(c) { let cmd = commandIndex[c.arg(0)] if cmd != null && !cmd.hidden { return cmd.name + " (1) - " + cmd.desc } escapeHtml(c.arg(0)) + ": nothing appropriate." }}, {name: "whereis", hidden: true, run: fn(c) { if c.arg(0) == "waldo" { return "waldo: /somewhere/in/a/crowd" } cmdWhich(c) }}, {name: "locate", hidden: true, run: fn(c) { if c.arg(0) == "" { return "usage: locate " } let out = [] walk("/", fn(p, e, depth) { if e.name.contains(c.arg(0)) { out.insert(escapeHtml(p)) } }) if out.length == 0 { lastStatus = 1 return "" } out.join("\n") }}, {name: "zoe", hidden: true, run: fn(c) { let art = zoeArt repeat art.endsWith("\n") { art = art.slice(0, -1) } "" + escapeHtml(art) + "" }}, {name: "clearflag", hidden: true, run: fn(c) { flagFound = false "flag state reset" }}, ] // ── breakin.tg ────────────────────────────────────────────────────────── // breakin.tg — the break-in: ssh root@targa.me or hack targa.me end in a // root shell, and root can read /var/www/targa.me/src, the real source of // this site, split back into its files from site.tg. let root = false const breakInReadme = `Ok, you got in. Well, sort of: this whole machine is a TargaScript program running in your browser, so you broke into your own tab. But what you found is real. Everything in src/ is what draws this terminal: the shell, the filesystem you've been walking around, every command, the easter eggs, and this break-in too. It's written in TargaScript, a language Miguel made from scratch: a compiler, a virtual machine and a WebAssembly runtime. Start with main.tg, or read it all with colors at https://targa.me/source/. Found a real hole somewhere? Miguel is in Google's Bug Hunters Hall of Fame and would love to hear about it: contact@targa.me ` tree["/var/www/targa.me"] = [{name: "README.txt", content: breakInReadme}, {name: "src", dir: true}] tree["/var/www/targa.me/src"] = [] // breakIn plays the exploit and opens the root shell. It starts fetching // the source right away, so it's there when the shell opens. fn breakIn() { loadSource() sequence([ {delay: 600, text: green("[*]") + " Exploiting tsh (CVE-2026-31337: the shell trusts the browser)...\n"}, {delay: 800, text: green("[*]") + " Escaping the WebAssembly sandbox....... " + green("OK") + "\n"}, {delay: 700, text: green("[*]") + " Mounting /var/www/targa.me............ " + yellow("read-only, but readable") + "\n"}, {delay: 700, text: green("[+]") + " uid=0(root) gid=0(root) groups=0(root)\n"}, {delay: 500, text: "\n" + `root shell obtained.` + " The source of this site is in " + yellow("/var/www/targa.me") + ".\n" + dim("Start with cat README.txt. Type exit to go back to being a guest.") + "\n"}, ], true, () => { root = true env.USER = "root" cwd = "/var/www/targa.me" }) } // loadSource fills /var/www/targa.me from site.tg, with the same version the // page loaded so the browser has it cached. Every file in it starts with a // rule: "// ── commands/files.tg ───". fn loadSource() { let script = doc.querySelector("script[src*='targascript.js']") let version = if script == null { null } else { js.URL.new(script.src).searchParams.get("v") } let url = "/site.tg" + if version == null { "" } else { "?v=" + version } then(js.fetch(url), resp => { then(resp.text(), source => { let files = {} let name = "" repeat line in toString(source).split("\n") { if line.startsWith("// ── ") { name = line.slice(6).split(" ")[0] files[name] = [] } else if name != "" { files[name].insert(line) } } let src = [] let commandDir = [] repeat name in files.keys() { let e = {name: splitPath("/" + name)[1], content: files[name].join("\n").trim() + "\n"} if name.startsWith("commands/") { commandDir.insert(e) } else { src.insert(e) } } tree["/var/www/targa.me/src"] = [{name: "commands", dir: true}, ...src].sortBy(e => e.name) tree["/var/www/targa.me/src/commands"] = commandDir.sortBy(e => e.name) }, _ => null) }, _ => null) } // leaveRoot is exit in the root shell: back to the guest account fn leaveRoot() { root = false env.USER = "guest" cwd = home "logout\n" + dim("You are a guest again. The source stays at ") + `targa.me/source` + dim(".") } // ── targascript.tg ────────────────────────────────────────────────────── // targascript.tg — TargaScript, in TargaScript. The page itself runs on the // TargaScript runtime, which can run another program in a fresh environment // (targascript.run) or keep a session whose variables last between runs // (targascript.session()). So the targascript command can run a .tg file // from the virtual filesystem, evaluate inline code, or drop into a REPL // with a persistent environment. let tgSession = null // tgRun runs src and returns what the program printed, plus the value of // the last expression when wantValue is set (REPL behaviour). Without a // value it runs in a fresh environment; with one it evaluates in session. // Errors come from the runtime already formatted, with the source line. fn tgRun(src, session, wantValue) { let buffer = "" let onOutput = fn(chunk) { buffer += chunk } let failure = null let result = null if wantValue { try { result = await session.eval(src, onOutput) } catch e { failure = e.message } } else { failure = await targascript.run(src, onOutput) } repeat buffer.endsWith("\n") { buffer = buffer.slice(0, -1) } let out = escapeHtml(buffer) if failure != null { lastStatus = 1 if out != "" { out += "\n" } out += "" + escapeHtml(failure) + "" } else if wantValue && result != null { if out != "" { out += "\n" } out += dim(escapeHtml(toString(result))) } out } fn newTgEnv() { targascript.session() } const tgUsage = `TargaScript — a scripting language written in Go. This site is written in it, running here in your browser on its WebAssembly runtime. usage: targascript start a REPL (exit with :q, exit or Ctrl+D) targascript <file.tg> run a script from this filesystem targascript -e '<code>' evaluate code inline cat file.tg | targascript read a script from a pipe examples: ls ~/projects/targascript playground: script.targa.me this site: targa.me/site.tg source: github.com/migueltarga/TargaScript` fn cmdTargaScript(c) { if c.has("-e") || c.has("--eval") { let code = [] let skip = false repeat a in c.args { if skip { code.insert(a) } else if a == "-e" || a == "--eval" { skip = true } } let src = code.join(" ") if src == "" { return "usage: targascript -e '<code>'" } return tgRun(src, newTgEnv(), true) } if c.has("-h") || c.has("--help") { return tgUsage } let f = c.arg(0) if f != "" { let path = resolve(f) let e = find(path) if e == null { lastStatus = 1 return errf("targascript", "cannot open '" + f + "': No such file or directory") } if e.dir { lastStatus = 1 return errf("targascript", "'" + f + "' is a directory") } if denied(path, e) { lastStatus = 1 return errf("targascript", "cannot open '" + f + "': Permission denied") } return tgRun(text(e), null, false) } if c.piped { return tgRun(c.stdin, null, false) } // REPL mode = "targascript" tgSession = newTgEnv() green("TargaScript REPL") + dim(" (persistent session; :q or exit to leave, :reset to clear variables)") + "\n" + dim("try: let x = [1, 2, 3] then x |> map(it * 2)") } fn tgReplInput(raw) { terminalContent += ">> " + escapeHtml(raw) + "\n" match raw.trim() { "" => null ":q", "exit", "quit", ":quit", ":exit" => { mode = "shell" terminalContent += dim("bye") + "\n" } ":reset" => { tgSession = newTgEnv() terminalContent += dim("environment reset") + "\n" } ":help", "help" => { terminalContent += tgUsage + "\n" } _ => { let out = tgRun(raw, tgSession, true) if out != "" { terminalContent += out + "\n" } } } render() } // tgExamples are the example programs shown under ~/projects/targascript. // fs.tg builds its tree with them before this file's top-level values run, // so the programs live inside the function. fn tgExamples() { const tgHello = `// hello.tg — the basics fn greet(name, greeting = "Hello") { "\${greeting}, \${name}!" } let person = {name: "Visitor", skills: ["curiosity", "typing commands"]} print(greet(person.name)) print(greet("TargaScript", "Hi")) repeat i, skill in person.skills { print("\${i + 1}. \${skill}") } print(person.address?.city ?? "no address") ` const tgFib = `// fib.tg — recursion, ranges and pipes fn fib(n) { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } } repeat i in 0..14 { printf("fib(%2d) = %d", i, fib(i)) } let total = 0..20 |> map(fib) |> sum print("sum of the first 21: \${total}") ` const tgArrays = `// arrays.tg — pipes, it and arrow functions let nums = 1..10 let squares = nums |> filter(it % 2 == 0) |> map(it * it) print(squares) print("sum: \${squares |> sum}") let words = ["wasm", "terminal", "go", "repeat", "match"] print(words.sortBy(w => w.length)) print(words.groupBy(w => if w.length > 4 { "long" } else { "short" })) ` const tgClosures = `// closures.tg — functions that remember fn counter(start = 0) { let count = start () => { count++ count } } let next = counter() print(next(), next(), next()) let fromTen = counter(10) print(fromTen()) ` const tgShapes = `// shapes.tg — structs, enums and match enum Kind { Circle, Square } struct Shape { kind = Kind.Square, size = 1 fn area() { match this.kind { Kind.Circle => 3.14159 * this.size ** 2 Kind.Square => this.size ** 2 } } } let shapes = [Shape(Kind.Circle, 2), Shape(Kind.Square, 3), Shape()] repeat s in shapes { printf("%-14s size %d area %6.2f", s.kind, s.size, s.area()) } let {kind, size} = shapes[0] print("the first is a \${kind.name} of size \${size}") ` [ {name: "README.md", html: true, content: `TargaScript examples Run one with: targascript hello.tg Or explore: targascript (REPL) hello.tg functions, defaults, interpolation, ?. and ?? fib.tg recursion, ranges, printf and |> arrays.tg pipes, it, sortBy and groupBy closures.tg functions that capture state shapes.tg structs, enums and match This whole site is written in TargaScript: read it at targa.me/site.tg. Try more in the playground: script.targa.me`}, {name: "hello.tg", content: tgHello}, {name: "fib.tg", content: tgFib}, {name: "arrays.tg", content: tgArrays}, {name: "closures.tg", content: tgClosures}, {name: "shapes.tg", content: tgShapes}, ] } // ── llm.tg ────────────────────────────────────────────────────────────── // llm.tg — a small language model running inside the visitor's browser, // behind the ai (one-shot) and claude (chat) commands. build/llm.js wraps // wllama (llama.cpp in wasm); models are downloaded from Hugging Face once and // kept in the browser cache. Nothing leaves the machine and there is still no // backend. // A model is {id, name, size, url}. Pick with /model in claude. The first one // is the default. let llmModels = [ {id: "qwen3-0.6b", name: "Qwen3 0.6B", size: "397 MB", url: "https://huggingface.co/unsloth/Qwen3-0.6B-GGUF/resolve/main/Qwen3-0.6B-Q4_K_M.gguf"}, {id: "qwen3-1.7b", name: "Qwen3 1.7B", size: "1.1 GB", url: "https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf"}, {id: "gemma3-1b", name: "Gemma 3 1B", size: "806 MB", url: "https://huggingface.co/unsloth/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf"}, {id: "gemma3-270m", name: "Gemma 3 270M", size: "253 MB", url: "https://huggingface.co/unsloth/gemma-3-270m-it-GGUF/resolve/main/gemma-3-270m-it-Q4_K_M.gguf"}, {id: "lfm2-350m", name: "LFM2 350M", size: "229 MB", url: "https://huggingface.co/unsloth/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q4_K_M.gguf"}, ] let llmCur = llmModels[0] // selected model let llmLoaded = "" // id of the model in memory, "" for none let llmScript = false // llm.js has been injected let llmWarned = false // the "it makes things up" footer was shown let llmGen = 0 // bumps on every operation so stale callbacks are ignored fn llmName() { llmCur.name } fn llmSize() { llmCur.size } // findModel is the model with that id, or null fn findModel(id) { llmModels.find(m => m.id == id.lower()) } // A message is {role, content}. // LlmOp is one download or generation: an animation the visitor can Ctrl+C, // plus the AbortController that stops the JS side when they do. struct LlmOp { gen = 0 ctl = null partial = "" // streamed text so far fromCache = false // the model file is local, so "progress" is a cache read retried = false // the model was reloaded once after vanishing mid-chat onFail = null // extra cleanup when the operation fails // live reports whether callbacks for this operation should still do anything fn live() { this.gen == llmGen && busy } fn fail(msg) { if !this.live() { return null } endAnim() if this.onFail != null { let f = this.onFail f() } // Whatever broke, the JS side starts over next time. llmLoaded = "" js.targaLLM.reset() if isOutOfSpace(msg) { terminalContent += "ai: " + yellow("your device ran out of room trying to fit a language model in its head.") + "\n" + " Free up some space (photos of food, the 47 open tabs, that game from 2019) and try again.\n" } else { terminalContent += "ai: the model hiccupped. Try again. " + dim(escapeHtml(msg)) + "\n" } if mode == "claude" { terminalContent += dim("Falling back to the classic terminal.") + "\n" leaveChat() } render() } // ensure makes sure the selected model is in memory, then calls ready. // When that needs a download and yes is false, consent is called instead // (animation ended). fn ensure(yes, ready, consent) { let op = this op.withScript(() => { if llmLoaded == llmCur.id { ready() return null } op.cached(urls => { if urls[llmCur.url] == true || yes { op.fromCache = urls[llmCur.url] == true op.loadModel(ready) return null } endAnim() consent() render() }) }) } // cached looks up which model files are already in the browser cache fn cached(f) { let op = this then(js.targaLLM.cached(), v => { if !op.live() { return null } let urls = {} repeat u in v { urls[u] = true } f(urls) }, e => { op.fail(jsError(e)) }) } // withScript runs f once llm.js is loaded fn withScript(f) { let op = this if llmScript { f() return null } let s = doc.createElement("script") s.src = "llm.js" s.onload = () => { llmScript = true f() } s.onerror = () => { op.fail("could not load llm.js") } doc.head.appendChild(s) } fn loadModel(ready) { let op = this let onProgress = (loaded, total) => { if !op.live() { return null } if total <= 0 { return null } let pct = toInt(toFloat(loaded) / toFloat(total) * 100) let bar = "#".repeat(pct / 5) + " ".repeat(20 - pct / 5) let mb = itoa(toInt(loaded / 1000000.0)) + "/" + itoa(toInt(total / 1000000.0)) + " MB" let verb = if op.fromCache { "reading cached " } else { "downloading " } animBuffer = verb + llmName() + " [" + bar + "] " + itoa(pct) + "% " + mb + "\n" if pct >= 100 { animBuffer += dim("loading model...") + "\n" } render() } llmLoaded = "" then(js.targaLLM.load(llmCur.url, onProgress, op.ctl.signal), _ => { if !op.live() { return null } llmLoaded = llmCur.id ready() }, e => { op.fail(jsError(e)) }) } // stream generates a reply to the conversation. show is called with the // text so far on every token (and once with "" at the start); done gets // the full text, with the animation still busy so the caller decides how // to end it. fn stream(messages, show, done) { let op = this op.partial = "" show("") let onToken = token => { if !op.live() { return null } op.partial += toString(token) show(op.partial) } then(js.targaLLM.chat(messages, onToken, op.ctl.signal), _ => { if !op.live() { return null } done(op.partial.trim()) }, e => { let msg = jsError(e) // The model can vanish under memory pressure (the wasm worker // gets killed); bring it back once, quietly, before giving up. if !op.retried && op.live() && isNotLoaded(msg) { op.retried = true llmLoaded = "" op.ensure(true, () => { op.stream(messages, show, done) }, null) return null } op.fail(msg) }) } } // llmBegin starts an operation. onCancel runs after Ctrl+C, once the // animation is already torn down, and should commit whatever is worth keeping. fn llmBegin(onCancel) { llmGen += 1 let op = LlmOp(llmGen, js.AbortController.new()) beginAnim(() => { op.ctl.abort() onCancel(op) }, "ctrl-c") op } // isOutOfSpace recognises the browser giving up on storage or memory: the // DOMException wording ("unknown transient reason (e.g. out of memory)"), // quota errors and wasm memory failures. fn isOutOfSpace(msg) { let m = msg.lower() ["transient", "out of memory", "quota", "memory", "storage", "disk", "space", "allocate", "allocation"].some(w => m.contains(w)) } fn isNotLoaded(msg) { let m = msg.lower() m.contains("loadmodel()") || m.contains("not loaded") } // The ai command: one question, one answer. fn cmdAI(c) { let words = c.args.filter(a => a != "-y") let q = words.join(" ").trim() if q == "" && c.piped { q = c.stdin.trim() } if q == "" { return "ai: ask me something about Miguel. e.g. ai what do you work on\n" + dim("For a conversation, try: claude") } if isContactQuestion(q) { return contactInfo } if isInsult(q) { prank("") return "" } let op = llmBegin(op => { if op.partial != "" { terminalContent += escapeHtml(op.partial) + "\n" } terminalContent += "^C\n" }) animBuffer = dim("thinking...") + "\n" render() let show = partial => { if partial != "" { animBuffer = escapeHtml(partial) + "\n" render() } } op.ensure(c.has("-y"), () => { op.stream([{role: "system", content: askFacts}, {role: "user", content: q}], show, text => { let out = escapeHtml(text) if out == "" { out = dim("(no answer; try rephrasing)") } terminalContent += out + "\n" if !llmWarned { llmWarned = true terminalContent += dim("(" + llmName() + ", running locally in your browser. It will make things up.)") + "\n" } endAnim() }) }, () => { terminalContent += llmNotice() + " Continue with: " + yellow("ai -y " + escapeHtml(q)) + "\n" }) "" } fn llmNotice() { "ai: runs " + llmName() + ", a small language model, inside your browser.\n" + " First use downloads " + llmSize() + " (once; it stays in this browser's cache).\n" } // Small instruct models tend to deflect anything that looks like sharing // personal details, so the one question worth getting right bypasses them. fn isContactQuestion(q) { let l = q.lower() ["contact", "reach", "email", "e-mail", "mail", "linkedin", "github", "hire", "get in touch", "talk to"].some(w => l.contains(w)) } // then wires callbacks to a JS promise: ok gets its value, fail its reason fn then(p, ok, fail) { p.then(v => { ok(v) }, e => { fail(e) }) } // jsError is the message of a JS error or rejection reason fn jsError(e) { if type(e) == "string" { return e } if e != null { let m = try { e.message } catch err { null } if type(m) == "string" { return m } } "unknown error" } // ── chat.tg ───────────────────────────────────────────────────────────── // chat.tg — the claude command: a conversation with the model, dressed up as // the Claude Code CLI. The page switches to the chat look and back when the // visitor leaves. let chatHistory = [] // messages, {role, content} let chatConsent = false // waiting for y/N before a download let chatOnYes = null let chatOnNo = null let chatMode = 0 // Shift+Tab cycles: 0 default, 1 accept edits, 2 plan let chatVerbs = ["Whisking", "Baking", "Brewing", "Pondering", "Simmering", "Marinating", "Percolating", "Cogitating", "Noodling", "Reticulating splines", "Compiling", "Hallucinating"] let chatSpinner = ["·", "✢", "✳", "✶", "✻", "✽", "✻", "✶", "✳", "✢"] let chatTips = [ "Use /clear to start a fresh conversation", "Ask how to write a loop in TargaScript", "Everything runs in this tab; it works offline once downloaded", "Ctrl+C interrupts an answer, Ctrl+C again leaves", "The model has 0.6 billion parameters. Be gentle.", "Ask what Miguel works on, then ask a follow-up", ] const chatHelp = ` /help show this /model list models, /model to switch (downloads once) /clear clear the screen and forget the conversation /exit back to the shell (also Ctrl+C or Ctrl+D) Shift+Tab cycles modes: default, accept edits, plan Anything else is sent to the model. It remembers the last few turns.` fn chatWelcome() { `✻ Welcome to claude (home edition) ` + llmName() + ` running locally in your browser. No backend, nothing leaves this tab. /help for commands · shift+tab to cycle modes · /exit or Ctrl+C to leave` + "\n\n" } // chatStatus is the footer under the input, one line per mode like the real CLI fn chatStatus() { match chatMode { 1 => `⏵⏵ accept edits on` + dim(" (shift+tab to cycle) · there are no edits, but it feels good") 2 => `⏸ plan mode on` + dim(" (shift+tab to cycle) · answers come as a plan") _ => `⏵⏵ local model on` + dim(" (" + llmName() + " in your browser) · shift+tab to cycle · ctrl+c to leave") } } // chatCycleMode handles Shift+Tab fn chatCycleMode() { chatMode = (chatMode + 1) % 3 render() } fn chatEcho(raw) { `
> ` + escapeHtml(raw) + `
` + "\n" } // The chat look (boxed input, status line) is body.claude in index.html fn enterChat() { mode = "claude" chatConsent = false doc.body.classList.add("claude") } fn leaveChat() { mode = "shell" chatConsent = false chatOnYes = null chatOnNo = null doc.body.classList.remove("claude") } fn cmdClaude(c) { let q = c.args.join(" ").trim() enterChat() terminalContent = "" // takes over the screen, like the real one chatHistory = [] // and every session starts fresh chatMode = 0 let op = llmBegin(_ => { terminalContent += "^C\n" leaveChat() }) animBuffer = dim("starting...") + "\n" render() op.ensure(false, () => { endAnim() terminalContent += chatWelcome() render() if q != "" { chatInput(q) } }, () => { chatConsentFor(() => { terminalContent += chatWelcome() }, () => { terminalContent += dim("Maybe later. Back to the shell.") + "\n" leaveChat() }, null) }) "" } // chatConsentFor asks before a download. On y the selected model is loaded // and then loaded runs; anything else runs declined; failed runs if the // download breaks (may be null). fn chatConsentFor(loaded, declined, failed) { chatConsent = true terminalContent += "This downloads " + llmName() + " (" + llmSize() + ") once; it stays in this browser's cache. Continue? " + yellow("[y/N]") + "\n" chatOnYes = () => { let op = llmBegin(_ => { terminalContent += "^C\n" leaveChat() }) op.onFail = failed op.loadModel(() => { endAnim() loaded() render() }) } chatOnNo = declined } fn chatInput(raw) { raw = raw.trim() if chatConsent { terminalContent += chatEcho(raw) chatConsent = false let yes = chatOnYes let no = chatOnNo chatOnYes = null chatOnNo = null let l = raw.lower() if l == "y" || l == "yes" { yes() } else { no() render() } return null } if raw == "" { render() return null } terminalContent += chatEcho(raw) let l = raw.lower() if l in ["/exit", "/quit", "/q", "exit", "quit"] { terminalContent += dim("Goodbye!") + "\n" leaveChat() } else if l == "/clear" { terminalContent = "" chatHistory = [] } else if l in ["/help", "help", "?"] { terminalContent += chatHelp + "\n\n" } else if l in ["/model", "/models"] { chatListModels() return null } else if l.startsWith("/model ") { chatSwitchModel(raw.slice(7).trim()) return null } else if raw.startsWith("/") { terminalContent += dim("Unknown command " + escapeHtml(raw) + ". Try /help.") + "\n\n" } else { chatAsk(raw) return null } render() } // chatClock is "3:04 PM", the time in the footer of an answer fn chatClock() { let t = timeFormat(now(), "hh:mm A") if t.startsWith("0") { t.slice(1) } else { t } } // chatPast turns the verb into the footer's past tense: Baking -> Baked fn chatPast(verb) { let first = verb.split(" ")[0] if first.endsWith("ing") { first = first.slice(0, -3) } first + "ed" } fn chatAsk(q) { if isInsult(q) { prank("● ") return null } if isContactQuestion(q) { terminalContent += "● " + contactInfo + "\n\n" chatRemember(q, "You can reach Miguel at contact@targa.me, github.com/migueltarga or linkedin.com/in/migueltarga.") render() return null } let verb = chatVerbs[rand(chatVerbs.length)] let tip = chatTips[rand(chatTips.length)] let start = clock() let tokens = 0 let frame = 0 let partial = "" let op = llmBegin(op => { if op.partial != "" { terminalContent += "● " + escapeHtml(op.partial.trim()) + "\n" } terminalContent += dim("Interrupted · Ctrl+C again to leave") + "\n\n" chatRemember(q, op.partial) }) let draw = () => { let secs = itoa(toInt((clock() - start) / 1000)) let buf = "" if partial != "" { buf = "● " + escapeHtml(partial) + "\n\n" } buf += `` + chatSpinner[frame % chatSpinner.length] + ` ` + verb + "… " + dim("(" + secs + "s · ↓ " + itoa(tokens) + " tokens)") + "\n" + dim(" Tip: " + tip) + "\n" animBuffer = buf render() } every(120, () => { frame += 1 draw() }) let msgs = [{role: "system", content: askFacts}] + chatHistory let ask = q if chatMode == 2 { ask += "\n\nAnswer as a short numbered plan, one step per line." } msgs.insert({role: "user", content: ask}) op.stream(msgs, p => { if p != "" { tokens += 1 } partial = p draw() }, text => { let secs = itoa(toInt((clock() - start) / 1000)) let past = chatPast(verb) if text == "" { text = "(no answer; try rephrasing)" } let footer = "✻ " + past + " for " + secs + "s · done " + chatClock() if chatMode == 1 { footer += " · 0 edits accepted" } terminalContent += "● " + escapeHtml(text) + "\n\n" + dim(footer) + "\n\n" chatRemember(q, text) endAnim() }) } // chatListModels prints the registry, marking what is downloaded and current fn chatListModels() { let op = llmBegin(_ => { terminalContent += "^C\n\n" }) op.withScript(() => { op.cached(urls => { endAnim() let b = dim("Models (all run in your browser; /model to switch):") + "\n" repeat m in llmModels { let mark = " " let note = "" if m.id == llmCur.id { mark = `▸ ` note = dim(" current") } else if urls[m.url] == true { note = dim(" downloaded") } b += mark + yellow(pad(m.id, 13)) + pad(m.name, 14) + dim(pad(m.size, 8)) + note + "\n" } terminalContent += b + "\n" render() }) }) } // chatSwitchModel selects another model, downloading it after consent fn chatSwitchModel(id) { let m = findModel(id) if m == null { terminalContent += dim("Unknown model " + escapeHtml(id) + ". /model lists them.") + "\n\n" render() return null } if m.id == llmCur.id { terminalContent += dim("Already using " + m.name + ".") + "\n\n" render() return null } let prev = llmCur llmCur = m let switched = () => { chatHistory = [] terminalContent += dim("✻ Switched to " + m.name + ". The conversation starts over.") + "\n\n" } let op = llmBegin(_ => { llmCur = prev terminalContent += "^C\n\n" }) op.onFail = () => { llmCur = prev } animBuffer = dim("loading " + m.name + "...") + "\n" render() op.ensure(false, () => { endAnim() switched() render() }, () => { chatConsentFor(switched, () => { llmCur = prev terminalContent += dim("Kept " + prev.name + ".") + "\n\n" }, () => { llmCur = prev }) }) } // isInsult spots people being rude about Miguel to the model fn isInsult(q) { q = " " + q.lower() + " " let about = [ "miguel", "targa", " he ", "he's", " his ", " you", "this guy", "the owner", "this site", "this website", "the author", " ele ", " dele", "esse cara", "o dono", "você", "voce", "esse site", "o autor", ].some(w => q.contains(w)) if !about { return false } [ "stupid", "idiot", "dumb", "suck", "loser", "ugly", "hate", "terrible", "awful", "worst", "trash", "garbage", "lame", "incompetent", "useless", "fraud", "noob", "moron", " bad", "overrated", "boring", "fake", "scam", "clown", "fail", "pathetic", "gay", // pt-BR "burro", "idiota", "otário", "otario", "babaca", "imbecil", "lixo", "horrível", "horrivel", "péssimo", "pessimo", "feio", "merda", "bosta", "porra", "caralho", "viado", "bicha", "corno", "fdp", "filho da puta", "puta", "vagabundo", "incompetente", "inútil", "inutil", "ridículo", "ridiculo", "nojento", "chato", "odeio", "trouxa", "mané", "cuzão", "cuzao", "arrombado", "escroto", "palhaço", "palhaco", ].some(w => q.contains(w)) } // prank is what the rude get instead of an answer: a fake counter-hack with // the screen going red, ending with them thrown out to a Google search on // manners. prefix is "● " in chat, "" in the shell. The "locating" step is // real-ish: Cloudflare's /cdn-cgi/trace (same origin, no backend) tells us // the IP, country and nearest datacenter. fn prank(prefix) { let root = doc.documentElement.style let before = toString(root.getPropertyValue("--fg")) root.setProperty("--fg", "#ff4444") let restore = () => { root.setProperty("--fg", before) } terminalContent += prefix + "Wrong answer.\n" beginAnim(() => { terminalContent += "^C\n" restore() }, "ctrl-c") render() let started = false let start = where => { if started || !busy { return null } started = true sequence([ {delay: 500, text: `✻ Deploying countermeasures…` + "\n"}, {delay: 900, text: " ⎿ Locating you… " + where + "\n"}, {delay: 900, text: " ⎿ Scanning ports… " + dim("22/tcp open. Cute.") + "\n"}, {delay: 900, text: " ⎿ Reverse shell… " + dim("connected.") + "\n"}, {delay: 700, text: " ⎿ rm -rf ~/Documents … " + dim("done") + "\n"}, {delay: 500, text: " ⎿ rm -rf ~/Photos … " + dim("done") + "\n"}, {delay: 500, text: " ⎿ rm -rf ~/.ssh … " + dim("done (you didn't need those)") + "\n"}, {delay: 1000, text: " ⎿ Forwarding your browser history to your mother… " + dim("sent.") + "\n"}, {delay: 1200, text: prefix + "Come back when you've learned some manners.\n"}, {delay: 1200, text: "Connection to targa.me closed by remote host.\n"}, ], true, () => { inputLine.style.display = "none" // stays "closed" until the redirect after(2500, () => { js.location.href = "https://www.google.com/search?q=how+to+be+nice+to+people" }) }) let interrupted = onCancel // Ctrl+C must not leave the screen red onCancel = () => { interrupted() restore() } } let fallback = _ => { start(dim("found (somewhere on Earth)")) } after(2500, () => { fallback(null) }) then(js.fetch("/cdn-cgi/trace"), resp => { then(resp.text(), t => { let [ip, loc, colo] = traceFields(toString(t)) if ip == "" { fallback(null) return null } start(traceWhere(ip, loc, colo)) }, fallback) }, fallback) } // traceFields picks [ip, loc, colo] out of a /cdn-cgi/trace response fn traceFields(t) { let ip = "" let loc = "" let colo = "" repeat line in t.split("\n") { let i = line.indexOf("=") let k = if i < 0 { line } else { line.slice(0, i) } let v = if i < 0 { "" } else { line.slice(i + 1) } match k { "ip" => { ip = v } "loc" => { loc = v } "colo" => { colo = v } _ => null } } [ip, loc, colo] } // traceWhere is the "Locating you…" text: the IP, the country and the city // of the datacenter fn traceWhere(ip, loc, colo) { let where = yellow(escapeHtml(ip)) if loc != "" { where += ", " + escapeHtml(loc) } let city = if colos.has(colo) { colos[colo] } else { null } if city != null { where += ", near " + city } else if colo != "" { where += ", near " + escapeHtml(colo) } where } // Cloudflare datacenters (IATA codes) visitors are likely to hit let colos = { MIA: "Miami", ATL: "Atlanta", DFW: "Dallas", ORD: "Chicago", IAD: "Washington", EWR: "Newark", JFK: "New York", LAX: "Los Angeles", SJC: "San Jose", SEA: "Seattle", DEN: "Denver", PHX: "Phoenix", BOS: "Boston", YYZ: "Toronto", YVR: "Vancouver", GRU: "São Paulo", GIG: "Rio de Janeiro", CNF: "Belo Horizonte", BSB: "Brasília", CWB: "Curitiba", POA: "Porto Alegre", FOR: "Fortaleza", REC: "Recife", SSA: "Salvador", EZE: "Buenos Aires", SCL: "Santiago", LIM: "Lima", BOG: "Bogotá", MEX: "Mexico City", LHR: "London", CDG: "Paris", AMS: "Amsterdam", FRA: "Frankfurt", MAD: "Madrid", LIS: "Lisbon", MXP: "Milan", DUB: "Dublin", WAW: "Warsaw", STO: "Stockholm", ZRH: "Zurich", VIE: "Vienna", IST: "Istanbul", DXB: "Dubai", BOM: "Mumbai", DEL: "Delhi", SIN: "Singapore", HKG: "Hong Kong", NRT: "Tokyo", ICN: "Seoul", SYD: "Sydney", MEL: "Melbourne", AKL: "Auckland", JNB: "Johannesburg", CPT: "Cape Town", } // chatRemember keeps the last few turns so follow-up questions make sense fn chatRemember(q, a) { chatHistory.insert({role: "user", content: q}) chatHistory.insert({role: "assistant", content: a}) if chatHistory.length > 8 { chatHistory = chatHistory.slice(chatHistory.length - 8) } } // ── main.tg ───────────────────────────────────────────────────────────── // main.tg — the shell: parsing (quotes, $VARS, pipes, && and ;), history, // Tab completion, the keyboard and the boot. It comes last, after the files // it uses. let doc = js.document let terminal = null let inputLine = null let inputField = null let promptEl = null let statusEl = null let terminalContent = "" let cwd = home let history = [] let historyIndex = 0 let draft = "" let lastStatus = 0 let mode = "shell" // shell, vim, closed, targascript or claude let closedMessage = "" let env = { USER: "guest", HOME: home, SHELL: "/bin/tsh", TERM: "xterm-256color", LANG: "en_US.UTF-8", PATH: "/usr/local/bin:/usr/bin:/bin:/usr/games", EDITOR: "vim", HOSTNAME: "targa.me", PWD: home, } // --------------------------------------------------------------------------- // The context of a command and the command table // --------------------------------------------------------------------------- struct Ctx { args = [], stdin = "", piped = false, raw = "" // arg is the i-th argument that isn't a flag, or "" fn arg(i) { this.args.filter(a => !(a.startsWith("-") && a.length > 1))[i] ?? "" } // flag reports whether a short flag, like l in -la, was given fn flag(letter) { this.args.some(a => a.startsWith("-") && !a.startsWith("--") && a.slice(1).contains(letter)) } fn has(word) { word in this.args } // input is what a command reads: the file named by argument i, or what // came through the pipe. It returns [text, error]. fn input(cmd, i) { let p = this.arg(i) if p != "" { let path = resolve(p) let e = find(path) if e == null { return ["", errf(cmd, p + ": No such file or directory")] } if e.dir { return ["", errf(cmd, p + ": Is a directory")] } if denied(path, e) { return ["", errf(cmd, p + ": Permission denied")] } return [text(e), ""] } if this.piped { return [this.stdin, ""] } ["", errf(cmd, "missing file operand")] } } // A command is {name, usage, desc, cat, hidden, run}, with cat one of core, // files, text, system, network, fun or egg. Eggs are hidden: not in help, // completion, man or /bin. let commands = coreCommands + eggCommands let commandIndex = commands.map(c => [c.name, c]).toObject() let aliases = { ll: "ls -l", la: "ls -a", please: "sudo", cls: "clear", dir: "ls", matrix: "cmatrix", vi: "vim", nvim: "vim", more: "cat", less: "cat", printenv: "env", screenfetch: "neofetch", gpt: "ai", chatgpt: "ai", "ai,": "ai", "ai:": "ai", ip: "ifconfig", ss: "netstat", htop: "top", python3: "python", nodejs: "node", "apt-get": "apt", poweroff: "shutdown", halt: "shutdown", logout: "exit", "?": "help", tg: "targascript", ts: "targascript", tgs: "targascript", calc: "bc", expr: "bc", hexdump: "xxd", hd: "xxd", } // --------------------------------------------------------------------------- // Parsing: quotes, variables, pipes and separators // --------------------------------------------------------------------------- fn tokenize(s) { let out = [] let cur = "" let quote = "" let inToken = false repeat ch in s { if quote != "" { if ch == quote { quote = "" } else { cur += ch } } else if ch == "\"" || ch == "'" { quote = ch inToken = true } else if ch == " " || ch == "\t" { if inToken { out.insert(cur) cur = "" inToken = false } } else { cur += ch inToken = true } } if inToken { out.insert(cur) } out } fn isWordChar(c) { c == "_" || c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" } fn expandVars(s) { if !s.contains("$") { return s } let chars = s.chars() let out = "" let i = 0 repeat i < chars.length { if chars[i] != "$" || i == chars.length - 1 { out += chars[i] i += 1 continue } let j = i + 1 let braced = chars[j] == "{" if braced { j += 1 } let k = j repeat k < chars.length && (isWordChar(chars[k]) || k == j && chars[k] == "?") { k += 1 if chars[k - 1] == "?" { break } } let name = chars.slice(j, k).join("") if braced && k < chars.length && chars[k] == "}" { k += 1 } if name == "" { out += "$" i += 1 continue } out += lookupVar(name) i = k } out } fn lookupVar(name) { match name { "?" => toString(lastStatus) "RANDOM" => toString(rand(32768)) "PWD" => cwd _ => env[name] ?? "" } } // splitTop splits s at sep where it isn't inside quotes fn splitTop(s, sep) { let out = [] let quote = "" let start = 0 let i = 0 repeat i < s.length { let ch = s[i] if quote != "" { if ch == quote { quote = "" } } else if ch == "\"" || ch == "'" { quote = ch } else if s.slice(i).startsWith(sep) { out.insert(s.slice(start, i)) start = i + sep.length i = start continue } i += 1 } out.insert(s.slice(start)) out } // --------------------------------------------------------------------------- // Running commands // --------------------------------------------------------------------------- fn promptText() { match mode { "vim", "closed" => "" "targascript" => ">>" "claude" => ">" _ => display(cwd) + if root { " #" } else { " $" } } } // statusText is the Claude Code style line under the input, in the chat fn statusText() { if mode != "claude" { return "" } chatStatus() } fn echoLine(raw) { "" + promptText() + " " + escapeHtml(raw) + "\n" } // submit handles the Enter key fn submit(typed) { let raw = typed.trim() match mode { "closed" => { mode = "shell" terminalContent = banner render() return null } "vim" => { vimInput(raw) return null } "targascript" => { tgReplInput(raw) return null } "claude" => { chatInput(raw) return null } _ => null } terminalContent += echoLine(raw) if raw == "" { render() return null } // History expansion if raw == "!!" && history.length > 0 { raw = history.last() terminalContent += escapeHtml(raw) + "\n" } else if raw.startsWith("!") && raw.length > 1 { let n = try { toInt(raw.slice(1)) } catch e { 0 } if n >= 1 && n <= history.length { raw = history[n - 1] terminalContent += escapeHtml(raw) + "\n" } } history.insert(raw) historyIndex = history.length draft = "" let out = execLine(raw) if out != "" { terminalContent += out + "\n" } render() } // execLine runs a whole command line: a && b; c | d fn execLine(raw) { if raw.contains(":(){") { lastStatus = 1 return red("tsh: fork bomb detected. Nice try.") } let typed = raw.trim() if typed.startsWith("targa{") && sha256Hex(typed) == flagHash { return cmdFlagFound() } let out = [] repeat chunk in splitTop(raw, ";") { repeat segment in splitTop(chunk, "&&") { let seg = segment.trim() if seg == "" { continue } let r = execPipeline(seg) if r != "" { out.insert(r) } if lastStatus != 0 { break } } } out.join("\n") } fn execPipeline(seg) { if seg.contains(">") && !seg.contains("->") { lastStatus = 1 return red("tsh: cannot create file: Read-only file system") } let out = "" repeat i, stage in splitTop(seg, "|") { let args = tokenize(stage).map(expandVars) if args.length == 0 { lastStatus = 2 return red("tsh: syntax error near unexpected token '|'") } let name = (if args[0].startsWith("/") { args[0].slice(1) } else { args[0] }).lower() let alias = aliases[name] if alias != null { args = tokenize(alias) + args.slice(1) name = args[0] } let c = commandIndex[name] if c == null { lastStatus = 127 return red("tsh: " + escapeHtml(args[0]) + ": command not found") } lastStatus = 0 out = c.run(Ctx(args.slice(1), unescape(stripTags(out)), i > 0, stage)) } out } fn render() { terminal.innerHTML = terminalContent + animBuffer terminal.scrollTop = terminal.scrollHeight promptEl.textContent = promptText() promptEl.className = if root && mode == "shell" { "root" } else { "" } let status = statusText() statusEl.innerHTML = status statusEl.hidden = status == "" } // --------------------------------------------------------------------------- // The keyboard // --------------------------------------------------------------------------- fn selecting() { inputField.selectionStart != inputField.selectionEnd } fn handleKey(event) { let key = event.key let ctrl = event.ctrlKey let meta = event.metaKey || event.altKey if interrupt(key, ctrl) { if !(ctrl && key == "c") || !selecting() { event.preventDefault() } return null } // Typing anywhere on the page goes to the input if !js.Object.is(event.target, inputField) && !ctrl && !meta && key.length == 1 { inputField.focus() return null } if ctrl && key == "c" { if selecting() { return null } // copying selected text event.preventDefault() match mode { "vim" => { terminalContent += "Type :qa! and press to abandon all changes and exit Vim\n" } "targascript" => { terminalContent += ">> " + escapeHtml(inputField.value) + "^C\n" } "claude" => { terminalContent += chatEcho(inputField.value + "^C") + "\n" leaveChat() } _ => { terminalContent += echoLine(inputField.value + "^C") } } inputField.value = "" historyIndex = history.length render() } else if ctrl && key == "l" { event.preventDefault() terminalContent = if mode == "vim" { vimScreen() } else { "" } render() } else if ctrl && key == "d" { event.preventDefault() if mode == "targascript" { tgReplInput("exit") } else if mode == "claude" { chatInput("/exit") } else if mode == "shell" && inputField.value == "" { terminalContent += echoLine("") terminalContent += execLine("exit") + "\n" render() } } else if key == "Enter" { let raw = inputField.value inputField.value = "" submit(raw) } else if key == "ArrowUp" { event.preventDefault() if historyIndex > 0 { if historyIndex == history.length { draft = inputField.value } historyIndex -= 1 inputField.value = history[historyIndex] } } else if key == "ArrowDown" { event.preventDefault() if historyIndex < history.length { historyIndex += 1 inputField.value = if historyIndex == history.length { draft } else { history[historyIndex] } } } else if key == "Tab" { event.preventDefault() if mode == "shell" { complete() } else if mode == "claude" && event.shiftKey { chatCycleMode() } } } // complete does Tab completion of command names and paths fn complete() { let current = inputField.value // Only the last stage of a pipeline matters let stageStart = 0 repeat sep in ["|", "&&", ";"] { let i = lastIndex(current, sep) if i >= 0 && i + sep.length > stageStart { stageStart = i + sep.length } } let stage = current.slice(stageStart) let trimmed = stage.trimStart() let space = lastIndex(trimmed, " ") let matches = [] let shown = [] let prefix = current.slice(0, stageStart) + stage.slice(0, stage.length - trimmed.length) if space < 0 { repeat c in commands { if !c.hidden && c.name.startsWith(trimmed.lower()) { matches.insert(c.name + " ") shown.insert(c.name) } } } else { prefix += trimmed.slice(0, space + 1) let arg = trimmed.slice(space + 1) let [dirPart, base] = splitPath(arg) let dir = cwd if arg.contains("/") { if dirPart == "" { dirPart = "/" } dir = resolve(dirPart) if !dirPart.endsWith("/") { dirPart += "/" } } else if arg == "~" { matches.insert("~/") shown.insert("~/") } repeat e in children(dir) { if e.name.startsWith(base) && (base.startsWith(".") || !e.name.startsWith(".")) { if e.dir { matches.insert(dirPart + e.name + "/") shown.insert(e.name + "/") } else { matches.insert(dirPart + e.name + " ") shown.insert(e.name) } } } } if matches.length == 1 { inputField.value = prefix + matches[0] } else if matches.length > 1 { inputField.value = prefix + commonPrefix(matches) terminalContent += echoLine(current) + dim(shown.join(" ")) + "\n" render() } } fn commonPrefix(items) { let p = items[0] repeat s in items.slice(1) { repeat !s.startsWith(p) { p = p.slice(0, -1) } } p } // --------------------------------------------------------------------------- // Boot // --------------------------------------------------------------------------- fn mount() { let body = doc.body doc.getElementById("fallback")?.remove() terminal = doc.createElement("div") terminal.id = "terminal" terminal.setAttribute("aria-live", "polite") body.appendChild(terminal) inputLine = doc.createElement("div") inputLine.id = "input-line" promptEl = doc.createElement("label") promptEl.id = "prompt" promptEl.setAttribute("for", "input") inputLine.appendChild(promptEl) inputField = doc.createElement("input") inputField.id = "input" inputField.type = "text" inputField.setAttribute("autocomplete", "off") inputField.setAttribute("autocapitalize", "none") inputField.setAttribute("spellcheck", "false") inputField.setAttribute("aria-label", "Terminal input") inputLine.appendChild(inputField) body.appendChild(inputLine) statusEl = doc.createElement("div") statusEl.id = "status" body.appendChild(statusEl) doc.addEventListener("keydown", handleKey) // On phones the on-screen keyboard shrinks the visual viewport but not // 100dvh, hiding the input behind it: size the page to what is visible let viewport = js.visualViewport if viewport != null { let fit = () => { body.style.height = "${toInt(viewport.height)}px" js.scrollTo(0, 0) terminal.scrollTop = terminal.scrollHeight } viewport.addEventListener("resize", fit) viewport.addEventListener("scroll", fit) } // Coming back, as from the prank's redirect, must not bring the old // screen back from the back-forward cache: start over instead js.addEventListener("pageshow", event => { if event.persisted { js.location.reload() } }) // Clicking empty space focuses the input again, but never at the cost // of a text selection or a link click body.addEventListener("click", event => { if event.target.tagName == "A" { return null } if js.getSelection().toString() != "" { return null } inputField.focus() }) terminalContent = banner render() inputField.focus() } populateBin() mount()