From c39a2a5a6e21e973312e87feddb13bdc931759c8 Mon Sep 17 00:00:00 2001 From: Tobias Herber <22559657+herber@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:32:30 +0200 Subject: [PATCH] Clean up repo --- browser-shell/index.html | 27 --- browser-shell/src/main.ts | 277 ------------------------- browser-shell/style.css | 136 ------------ scripts/build-public.ts | 23 +- {cli.metorial.com => templates}/cli.md | 0 5 files changed, 17 insertions(+), 446 deletions(-) delete mode 100644 browser-shell/index.html delete mode 100644 browser-shell/src/main.ts delete mode 100644 browser-shell/style.css rename {cli.metorial.com => templates}/cli.md (100%) diff --git a/browser-shell/index.html b/browser-shell/index.html deleted file mode 100644 index e82f1c4..0000000 --- a/browser-shell/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - Metorial CLI Browser Shell - - - -
-
-
-

Metorial CLI

-

Browser Shell

-
-
Version __METORIAL_VERSION__
-
-
-
- - - -
-
- - - diff --git a/browser-shell/src/main.ts b/browser-shell/src/main.ts deleted file mode 100644 index d69095d..0000000 --- a/browser-shell/src/main.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { Volume, createFsFromVolume } from 'memfs'; - -type CommandResult = { - exitCode: number; - stdout: string; - stderr: string; -}; - -type BrowserRuntime = { - run(args: string[], env?: Record): Promise; -}; - -let STORAGE_KEY = 'metorial-cli-browser-fs'; -let HOME_DIR = '/home/web'; -let WORKSPACE_DIR = '/workspace'; - -let screen = document.querySelector('#screen'); -let form = document.querySelector('#prompt-form'); -let input = document.querySelector('#prompt-input'); -let versionNode = document.querySelector('#version'); - -if (!screen || !form || !input || !versionNode) { - throw new Error('Browser shell UI failed to initialize'); -} - -let version = versionNode.textContent || 'dev'; -let volume = createVolume(); -let fs = createFsFromVolume(volume) as any; -let cwd = WORKSPACE_DIR; - -bootstrapNodeCompat(fs); -await loadGoRuntime(); - -let runtime = (globalThis as typeof globalThis & { metorialBrowser?: BrowserRuntime }).metorialBrowser; -if (!runtime) { - throw new Error('Metorial browser runtime was not registered'); -} - -printEntry([ - `$ metorial version`, - `metorial ${version}`, - '', - 'Try commands like `version`, `providers list`, or `login`.', - 'Type `clear` to reset the screen.' -], ['command', 'muted', 'muted', 'muted', 'muted']); - -form.addEventListener('submit', async event => { - event.preventDefault(); - - let commandText = input.value.trim(); - if (!commandText) { - return; - } - - input.value = ''; - - if (commandText === 'clear') { - screen.innerHTML = ''; - return; - } - - let args = splitCommand(commandText); - printEntry([`$ metorial ${commandText}`], ['command']); - setPending(true); - - try { - let result = await runtime.run(args, { - HOME: HOME_DIR, - METORIAL_SKIP_UPDATE_CHECK: '1' - }); - - persistVolume(); - renderCommandResult(result); - } catch (error) { - let message = error instanceof Error ? error.message : String(error); - printEntry([message], ['stderr']); - } finally { - setPending(false); - input.focus(); - } -}); - -input.focus(); - -function createVolume() { - let stored = localStorage.getItem(STORAGE_KEY); - let snapshot = stored ? JSON.parse(stored) : {}; - let volume = Volume.fromJSON(snapshot, '/'); - - tryMkdir(volume, HOME_DIR); - tryMkdir(volume, `${HOME_DIR}/.metorial`); - tryMkdir(volume, `${HOME_DIR}/.metorial/cli`); - tryMkdir(volume, WORKSPACE_DIR); - - return volume; -} - -function bootstrapNodeCompat(fs: any) { - (globalThis as any).fs = fs; - (globalThis as any).process = { - env: { - HOME: HOME_DIR - }, - getuid() { - return -1; - }, - getgid() { - return -1; - }, - geteuid() { - return -1; - }, - getegid() { - return -1; - }, - getgroups() { - throw new Error('not implemented'); - }, - pid: 1, - ppid: 1, - umask() { - return 0; - }, - cwd() { - return cwd; - }, - chdir(nextDir: string) { - cwd = normalizePath(nextDir); - } - }; -} - -async function loadGoRuntime() { - await loadScript('./browser-shell-wasm_exec.js'); - - let GoConstructor = (globalThis as any).Go; - if (!GoConstructor) { - throw new Error('Go WASM runtime failed to load'); - } - - let go = new GoConstructor(); - let response = await fetch('./browser-shell-metorial.wasm'); - let bytes = await response.arrayBuffer(); - let result = await WebAssembly.instantiate(bytes, go.importObject); - void go.run(result.instance); -} - -function loadScript(source: string) { - return new Promise((resolve, reject) => { - let script = document.createElement('script'); - script.src = source; - script.onload = () => resolve(); - script.onerror = () => reject(new Error(`Failed to load ${source}`)); - document.head.appendChild(script); - }); -} - -function renderCommandResult(result: CommandResult) { - let lines: string[] = []; - let classes: string[] = []; - - if (result.stdout.trim()) { - for (let line of result.stdout.replaceAll('\r\n', '\n').split('\n')) { - if (!line) { - continue; - } - lines.push(line); - classes.push('stdout'); - } - } - - if (result.stderr.trim()) { - for (let line of result.stderr.replaceAll('\r\n', '\n').split('\n')) { - if (!line) { - continue; - } - lines.push(line); - classes.push('stderr'); - } - } - - if (lines.length === 0) { - lines.push(result.exitCode === 0 ? 'Command completed.' : `Command failed with exit code ${result.exitCode}.`); - classes.push('muted'); - } - - printEntry(lines, classes); -} - -function printEntry(lines: string[], classes: string[]) { - let entry = document.createElement('div'); - entry.className = 'shell__entry'; - - lines.forEach((line, index) => { - let row = document.createElement('div'); - row.className = `shell__line shell__line--${classes[index] || 'stdout'}`; - row.textContent = line; - entry.appendChild(row); - }); - - screen?.appendChild(entry); - screen?.scrollTo({ top: screen.scrollHeight }); -} - -function setPending(value: boolean) { - input.disabled = value; - let button = form.querySelector('button'); - if (button) { - button.disabled = value; - button.textContent = value ? 'Running...' : 'Run'; - } -} - -function persistVolume() { - localStorage.setItem(STORAGE_KEY, JSON.stringify(volume.toJSON())); -} - -function normalizePath(value: string) { - if (value.startsWith('/')) { - return value; - } - - return `${cwd.replace(/\/$/, '')}/${value}`.replace(/\/+/g, '/'); -} - -function tryMkdir(volume: Volume, dir: string) { - try { - volume.mkdirSync(dir, { recursive: true }); - } catch {} -} - -function splitCommand(input: string) { - let tokens: string[] = []; - let current = ''; - let quote = ''; - - for (let index = 0; index < input.length; index++) { - let char = input[index]; - - if (quote) { - if (char === quote) { - quote = ''; - continue; - } - - if (char === '\\' && index + 1 < input.length) { - current += input[index + 1]; - index += 1; - continue; - } - - current += char; - continue; - } - - if (char === '"' || char === "'") { - quote = char; - continue; - } - - if (/\s/.test(char)) { - if (current) { - tokens.push(current); - current = ''; - } - continue; - } - - current += char; - } - - if (current) { - tokens.push(current); - } - - return tokens; -} diff --git a/browser-shell/style.css b/browser-shell/style.css deleted file mode 100644 index 3c3e31b..0000000 --- a/browser-shell/style.css +++ /dev/null @@ -1,136 +0,0 @@ -:root { - color-scheme: dark; - --bg: #09111c; - --bg-elevated: #0f1b2b; - --panel: #132235; - --line: #24405e; - --text: #e8f2ff; - --muted: #90a7c4; - --accent: #62b0ff; - --accent-strong: #8bd3ff; - --danger: #ffb86b; -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; - min-height: 100vh; - background: - radial-gradient(circle at top left, rgba(98, 176, 255, 0.18), transparent 24%), - linear-gradient(180deg, #0a1220 0%, var(--bg) 100%); - color: var(--text); - font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace; -} - -.shell { - max-width: 1100px; - margin: 0 auto; - min-height: 100vh; - padding: 32px 20px; - display: grid; - grid-template-rows: auto 1fr auto; - gap: 18px; -} - -.shell__header, -.shell__screen, -.shell__prompt { - border: 1px solid var(--line); - background: rgba(15, 27, 43, 0.92); - box-shadow: 0 24px 80px rgba(3, 9, 18, 0.4); -} - -.shell__header { - border-radius: 22px; - padding: 22px 24px; - display: flex; - justify-content: space-between; - align-items: end; - gap: 16px; -} - -.shell__eyebrow { - margin: 0 0 6px; - color: var(--accent-strong); - text-transform: uppercase; - letter-spacing: 0.12em; - font-size: 12px; -} - -.shell__header h1 { - margin: 0; - font-size: clamp(28px, 4vw, 46px); - line-height: 1; -} - -.shell__meta { - color: var(--muted); -} - -.shell__screen { - border-radius: 28px; - padding: 20px; - overflow: auto; - white-space: pre-wrap; - line-height: 1.55; -} - -.shell__entry + .shell__entry { - margin-top: 18px; - padding-top: 18px; - border-top: 1px solid rgba(144, 167, 196, 0.16); -} - -.shell__line--command { - color: var(--accent-strong); -} - -.shell__line--stderr { - color: var(--danger); -} - -.shell__line--muted { - color: var(--muted); -} - -.shell__prompt { - border-radius: 18px; - padding: 14px; - display: grid; - grid-template-columns: auto 1fr auto; - gap: 12px; - align-items: center; -} - -.shell__prompt-label { - color: var(--accent-strong); - font-weight: 700; -} - -.shell__prompt input { - width: 100%; - background: transparent; - border: none; - color: var(--text); - font: inherit; - outline: none; -} - -.shell__prompt button { - appearance: none; - border: 1px solid rgba(139, 211, 255, 0.28); - background: linear-gradient(180deg, rgba(98, 176, 255, 0.22), rgba(98, 176, 255, 0.12)); - color: var(--text); - font: inherit; - border-radius: 999px; - padding: 9px 16px; - cursor: pointer; -} - -.shell__prompt button:disabled { - opacity: 0.65; - cursor: default; -} diff --git a/scripts/build-public.ts b/scripts/build-public.ts index 3ecb1fb..ebdfd0a 100644 --- a/scripts/build-public.ts +++ b/scripts/build-public.ts @@ -1,6 +1,6 @@ #!/usr/bin/env bun -import { cp, copyFile, mkdir, rm, writeFile } from 'node:fs/promises'; +import { copyFile, cp, mkdir, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -39,7 +39,7 @@ let scriptsDir = path.dirname(currentFile); let cliDir = path.resolve(scriptsDir, '..'); let publicDir = path.join(cliDir, 'public'); let installTemplatePath = path.join(cliDir, 'templates', 'install.sh'); -let cliMarkdownPath = path.join(cliDir, 'cli.metorial.com', 'cli.md'); +let cliMarkdownPath = path.join(cliDir, 'templates', 'cli.md'); let githubOwner = process.env.GITHUB_REPOSITORY_OWNER || 'metorial'; let githubRepo = process.env.GITHUB_REPOSITORY_NAME || 'cli'; @@ -81,7 +81,10 @@ for (let release of releases) { let browserFileName = asset.name.replace(/^browser-shell-/, ''); await copyFile(destinationPath, path.join(browserVersionDir, browserFileName)); if (browserFileName === 'index.html') { - await copyFile(destinationPath, path.join(browserVersionDir, 'browser-shell-index.html')); + await copyFile( + destinationPath, + path.join(browserVersionDir, 'browser-shell-index.html') + ); } } } @@ -123,9 +126,17 @@ await copyFile(installTemplatePath, path.join(publicDir, 'install.sh')); await copyFile(cliMarkdownPath, path.join(publicDir, 'cli.md')); if (latestBrowserShellDir) { - await rm(path.join(publicDir, 'metorial-cli-browser', 'latest'), { recursive: true, force: true }); - await cp(latestBrowserShellDir, path.join(publicDir, 'metorial-cli-browser', 'latest'), { recursive: true }); - await writeFile(path.join(publicDir, 'metorial-cli-browser', 'latest-tag'), `${latestRelease.tag_name}\n`); + await rm(path.join(publicDir, 'metorial-cli-browser', 'latest'), { + recursive: true, + force: true + }); + await cp(latestBrowserShellDir, path.join(publicDir, 'metorial-cli-browser', 'latest'), { + recursive: true + }); + await writeFile( + path.join(publicDir, 'metorial-cli-browser', 'latest-tag'), + `${latestRelease.tag_name}\n` + ); } await writeFile( diff --git a/cli.metorial.com/cli.md b/templates/cli.md similarity index 100% rename from cli.metorial.com/cli.md rename to templates/cli.md