_ _
__ __ _____| |__ __| |_
\ V V / -_) '_ (_-< ' \
\_/\_/\___|_.__/__/_||_|
Websh is a browser-native filesystem shell for personal archives. It exposes a canonical / tree through a terminal and explorer UI, with the primary runtime mount backed by the repository content/ directory.
Content is loaded from content/manifest.json and declared runtime mounts, then assembled into a single GlobalFs view at boot. Some files are access-restricted to listed recipients; those entries are filtered from the UI for other visitors. The underlying storage is public and no cryptographic confidentiality is provided in the current release.
- Unix-like Commands: Full CLI with
ls,cd,pwd,cat,whoami,id, and more - Pipe Operations: Chain commands with
|using filters (grep,head,tail,wc) - Smart Autocomplete: Tab completion for commands and file paths with ghost text hints
- Command History: Navigate previous commands with arrow keys (↑/↓)
- Environment Variables: Persistent
export/unsetstored in localStorage
- Canonical Filesystem: Navigate one hierarchical
/tree assembled from runtime mounts - Markdown Rendering: View
.mdfiles with full HTML rendering - XSS Protection: Content sanitization with ammonia
- Remote Content: Dynamic loading from remote storage
- Wallet-based Identity (EIP-1193): Connects Ethereum wallets to establish identity and sign operations.
- Access Filter: Files may designate a recipient list; the UI hides access-restricted entries from visitors whose wallet address is not on that list. This is an advisory filter — content on public storage is not cryptographically protected.
- Data Integrity: Uses wallet signatures to verify authorship of published content.
- ENS Resolution: Native resolution of ENS names for user identification and profile mapping.
- Static Hosting: Optimized for serverless hosting using purely static assets.
- Zero-Backend: Executes all system logic client-side via WebAssembly, requiring no traditional backend.
- Decoupled Content: Separates application logic from data. Content is dynamically fetched from remote repositories without the need to redeploy the core shell.
- WASM Runtime: Compiled from Rust to an optimized WebAssembly binary for consistent performance and security across any hosting environment.
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add WASM target
rustup target add wasm32-unknown-unknown
# Install Trunk (WASM bundler)
cargo install trunk
# Install Stylance CLI (CSS modules)
cargo install stylance-cli# Clone the repository
git clone https://github.com/0xwonj/websh.git
cd websh
# Start development server
trunk serve
# Open http://127.0.0.1:8080Put public content files under content/, then run:
cargo run --bin websh-cli -- attestThat single command runs the same manifest builder as websh-cli content manifest,
scans the content tree, refreshes page subjects, and writes
assets/crypto/attestations.json. If
content/keys/wonjae.asc exists, it also asks local gpg to create verified PGP
detached signatures with Wonjae Choi <wonjae@snu.ac.kr> for the subjects.
To refresh only the filesystem manifest without signing:
cargo run --bin websh-cli -- content manifest# Build optimized WASM
trunk build --release
# Output in ./dist directorycargo run --bin websh-cli -- deploy pinataThe deploy command refreshes content attestations, builds the release bundle,
uploads dist/ to Pinata, writes the CID to .last-cid, and prints an
ipfs://... contenthash that can be copied directly into the ENS records page.
ls [dir]- List directory contentscd <dir>- Change directory (supports.,..,~, absolute paths)pwd- Print working directorycat <file>- View file contents (opens reader)
whoami- Display user profileid- Show current session infohelp- Show this help message
clear- Clear terminal screenecho <text>- Display text
export- Show user variablesexport KEY=value- Set variable (stored in localStorage)unset KEY- Remove variablecat .profile- View all localStorage data
login- Connect MetaMask walletlogout- Disconnect wallet
grep <pattern>- Filter lines matching patternhead [-n]- Show first n lines (default: 10)tail [-n]- Show last n lines (default: 10)wc- Count lines
Admins (wallets listed in the allowlist) can edit ~ and commit atomically to GitHub.
Commands:
touch <path>,mkdir <path>,rm [-r] <path>,rmdir <path>,edit <path>echo "body" > <path>— write-or-replace file contentsync status— show drafted changessync commit <msg>— push staged changes atomicallysync refresh— reload the runtime from configured storage backendssync auth set <github_pat>/sync auth clear— session-scoped token
Drafts persist in IndexedDB across reloads. Commits use GraphQL
createCommitOnBranch with expectedHeadOid compare-and-swap, so if the
remote moved since you started drafting, the commit fails with
"remote changed — run sync refresh" rather than clobbering.
Security caveat: the GitHub PAT is sensitive browser runtime state. The
terminal redacts sync auth set <token> and keeps it out of command history;
still keep mounted content sanitized, use minimum token scopes, and enforce
deployment CSP headers before wider admin rollout.
src/
├── app.rs # Root component, AppContext, TerminalState
├── config.rs # Configuration constants and embedded assets
├── main.rs # Entry point
├── core/ # Pure logic
├── models/ # Data structures
├── components/ # Leptos UI components
└── utils/ # Utilities
- Language: Rust compiled to WebAssembly
- Framework: Leptos 0.8 - Fine-grained reactive UI
- Styling: Stylance - Type-safe CSS modules over a 3-tier design token system
- Build Tool: Trunk - WASM application bundler
- Wallet: secp256k1 signatures via EIP-1193
CSS uses a 3-tier token model. Component *.module.css files reference Tier 2
semantic tokens only — never raw px, hex, or duration literals. This is
enforced by stylelint (just lint-css).
assets/tokens/primitive.css Tier 1 — raw scale (--space-*, --font-size-*,
--leading-*, --weight-*, --radius-*,
--duration-*, --z-*)
assets/tokens/semantic.css Tier 2 — role aliases (--pad-card, --motion-hover,
--content-width-*, --z-modal …)
assets/tokens/typography.css @font-face + --font-mono
assets/themes/<theme>.css Tier 2 — color tokens, one file per theme
(--bg-*, --text-*, --terminal-*, --accent)
assets/base.css global reset, ::selection, scrollbar utility,
blink keyframe
src/components/**/*.module.css Tier 3 — only var(--semantic-*) references
Themes are switched by setting data-theme on the <html> element. The
inline bootstrap script in index.html does this synchronously before
hydration, so there is no flash of unstyled content. Adding a new theme is
one additional file under assets/themes/.
Component-scoped responsiveness uses container queries — see
src/components/explorer/file_list.module.css and
src/components/terminal/terminal.module.css for the pattern. Page-level
breakpoints (chrome, sidebars) continue to use @media.
Mobile-tuned token overrides live inside an @media block at the bottom of
assets/tokens/primitive.css; consumers don't change, the value just shrinks
below 768px.
- HTML sanitization with ammonia
- Markdown content cleaned before rendering
- No inline script execution
// Only allowed redirect domains
ALLOWED_REDIRECT_DOMAINS = [
"github.com",
"twitter.com",
"etherscan.io",
// ...
]- User variables prefixed with
user. - System data separated from user data
- Wallet session managed securely
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License.