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
-
-
-
-
-
-
-
-
-
-
-
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