@melker/melker
v2026.3.11
Published
*Run text with meaning*
Readme
Melker
Run text with meaning
Website: melker.sh
A TUI framework for apps you want to share safely.
Melker apps are documents you can read before you run them. Share via URL, declare permissions in a policy, inspect with Dev Tools.
Read the Manifesto for the philosophy behind this approach.
Read the FAQ for common questions.
Why Melker?
| Feature | Melker | Other TUI Frameworks | |------------------------------|:------:|:--------------------:| | Run from URL | Y | - | | Permission sandbox | Y | - | | App approval system | Y | - | | Inspect policy before run | Y | - | | Dev Tools (source, policy, inspect) | Y | - | | Literate UI (Markdown) | Y | - | | No build step | Y | Some |
See full comparison with Ink, Textual, Bubble Tea, Ratatui, and others.
Installation
Requirements
- Deno 2.5+ or Node.js 25+ (experimental)
- ANSI-compatible terminal
- Nerd Fonts (recommended for graphics) - Melker uses sextant characters for canvas/image rendering. On macOS, install a Nerd Font and configure your terminal to use it. For terminals without Unicode support, use
--gfx-mode=block(colored spaces),--gfx-mode=pattern(ASCII spatial), or--gfx-mode=luma(ASCII brightness).
Option 1: JSR (Deno)
deno install -g -A jsr:@melker/melker
melker app.melker-A grants permissions to the launcher — apps run sandboxed with only the permissions they declare.
Option 2: Try Without Installing (Deno)
deno x jsr:@melker/melker app.melkerOption 3: Clone and Run (Deno)
git clone https://github.com/wistrand/melker.git
cd melker
./melker.ts examples/basics/hello.melkerOption 4: Global Install via Symlink (Deno)
# Clone to a permanent location
git clone https://github.com/wistrand/melker.git ~/melker
# Create symlink (ensure ~/.local/bin is in your PATH)
ln -s ~/melker/melker.ts ~/.local/bin/melker
# Run from anywhere
melker app.melkerThe CLI is symlink-safe - it resolves its real path before importing dependencies.
Option 5: Node.js (Experimental)
Requires Node.js 25+. Apps run in a sandboxed subprocess with Node's --permission model. The binary is named melker-node.
npm install -g @melker/melker
melker-node app.melkerReleases
Melker uses CalVer (YYYY.MM.PATCH). List available releases:
git tag --list 'v*' | sort -VCheckout a specific release:
git checkout v2026.03.1Not Just Trust - Real UI
Melker apps look and feel like apps:
- Flexbox layout, not just text dumps
- CSS-like styling, 16M colors, theme auto-detection
- Mouse and keyboard throughout
- Tables with sorting and selection
- Dialogs, menus, prompts
Showcase
A system monitor: View source

Quick Start
Create hello.melker:
<melker>
<policy>
{
"name": "Hello App",
"permissions": {
"env": ["TERM"]
}
}
</policy>
<container style="border: thin; padding: 1;">
<text style="font-weight: bold; color: cyan;">Hello, Terminal!</text>
<button label="Exit" onClick="$melker.exit()" />
</container>
</melker>Run it:
# Via the launcher
./melker.ts hello.melker
# Or via deno run
deno run --allow-all melker.ts hello.melker
# Or make the .melker file itself executable (add shebang as first line)
chmod +x hello.melker
./hello.melkerWhy
--allow-all? The launcher (melker.ts) needs full permissions to parse, bundle, and spawn your app. But your app runs in a subprocess with only the permissions declared in its<policy>. The launcher is trusted; the app is sandboxed.Minimal launcher permissions:
--allow-read --allow-write=/tmp --allow-env --allow-run
Before running, you can see:
- The policy declares only
env: TERMpermission - The UI has one text element and one exit button
- The handler calls
$melker.exit(), nothing else
Press F12 to open Dev Tools to view source, policy, document tree, and system info.
For tutorials, see agent_docs/getting-started.md and the step-by-step tutorial.
Key Concepts
1. Document-First
Apps are documents, not opaque processes. A .melker file is readable markup with:
- Declared permissions (
<policy>) - Visible structure (HTML-like elements)
- Inspectable handlers (
onClick="...")
2. Explicit Policy
Permissions are document metadata, visible before execution:
<policy>
{
"permissions": {
"read": ["./data"],
"write": ["./output"],
"net": ["api.example.com"]
}
}
</policy>Run --show-policy to inspect without running:
./melker.ts --show-policy app.melker3. Run from URL
Share apps via URL:
# Run directly from URL
./melker.ts https://example.com/app.melker
# Remote apps require explicit policy (enforced)
# First run prompts for approval (hash-based)4. Three Abstraction Levels
Programmatic - TypeScript API:
const btn = createElement('button', { label: 'Click', onClick: () => count++ });Declarative - .melker files:
<button label="Click" onClick="count++" />Literate - .melker.md Markdown with embedded UI:
# My App
Documentation and UI in the same file:
<button label="Click" onClick="count++" />5. Dev Tools
Press F12 to open Dev Tools:
- View source, inspect policy, see system info
- Inspect tab: Interactive element inspector with tree view, property/style editing, and live layout bounds
- Config viewer, log viewer, actions panel
Features
Components
Core - no external dependencies:
| Category | Components | |-------------|---------------------------------------------------| | Layout | container, flexbox, tabs | | Text | text, markdown | | Input | input, textarea, checkbox, radio, slider | | Navigation | button, command-palette | | Data | table, data-table, list | | Dropdowns | combobox, select, autocomplete, command-palette | | Dialogs | dialog, alert, confirm, prompt | | Feedback | progress | | Files | file-browser |
Advanced - for specific needs:
| Component | Purpose | Requires | |---------------|--------------------------|-----------------------| | canvas | Pixel graphics, shaders | - | | img | Image display | - | | video | Media playback | FFmpeg | | oauth | Auth flows | net permission |
Layout
Flexbox with full support:
<container style="display: flex; flex-direction: row; gap: 2;">
<text style="flex: 1;">Left</text>
<text style="flex: 1;">Right</text>
</container>Styling
CSS-like inline styles:
<container style="border: rounded; padding: 1; color: cyan; background-color: #222;">Or <style> tags with selectors:
<style>
button { background-color: blue; color: white; }
#title { font-weight: bold; }
.highlight { color: yellow; }
</style>Themes
Auto-detects terminal capabilities by default. Manual override via CLI flag or env var:
./melker.ts --theme fullcolor-dark app.melker
# or
MELKER_THEME=fullcolor-dark ./melker.ts app.melkerAvailable: auto, bw-std, bw-dark, gray-std, gray-dark, color-std, color-dark, fullcolor-std, fullcolor-dark
Advanced Features
Canvas and Graphics
Pixel graphics using sextant characters (2x3 pixels per cell):
<canvas id="c" width="60" height="30" onPaint="draw(event.canvas)" />function draw(canvas) {
canvas.setPixel(x, y, 0xFF0000FF); // RGBA packed
canvas.line(0, 0, 59, 29, color);
canvas.rect(10, 10, 20, 15, color);
}Features: true color, auto-dither, retained mode with onPaint callback.
Video Playback
<video src="./video.mp4" width="80" height="24" autoplay="true" />Uses FFmpeg for decoding, dithering for display.
Tables with Sorting and Selection
<table style="width: fill; height: 10;">
<thead>
<tr><th>Name</th><th>Role</th></tr>
</thead>
<tbody style="overflow: scroll" selectable="single">
<tr data-id="1"><td>Alice</td><td>Admin</td></tr>
<tr data-id="2"><td>Bob</td><td>User</td></tr>
</tbody>
</table>Click headers to sort. Arrow keys to navigate. Full keyboard and mouse support.
OAuth Built-in
<oauth
wellknown="https://auth.example.com/.well-known/openid-configuration"
clientId="my-app"
scopes="openid profile"
onToken="handleToken(event.token)"
/>Handles browser redirect, token exchange, and secure storage.
State Persistence
MELKER_PERSIST=true deno run --allow-all melker.ts app.melkerElement state persists across runs using XDG state directory.
Running Apps
# Via the launcher
./melker.ts app.melker
# Or via deno run
deno run --allow-all melker.ts app.melker
# Or make the .melker file itself executable
# (add #!/usr/bin/env -S melker as first line, then chmod +x)
./app.melker
# Run from URL
./melker.ts https://example.com/app.melker
# Show policy without running
./melker.ts --show-policy app.melker
# Trust mode (for CI/scripts - bypasses approval prompt that would hang)
./melker.ts --trust app.melker
# Watch mode (auto-reload on changes)
./melker.ts --watch app.melker
# Enable server
./melker.ts --server-port 8080 app.melkerDeno Flags
These Deno flags are forwarded to the app subprocess:
./melker.ts --reload https://example.com/app.melker # Reload remote modules
./melker.ts --no-lock app.melker # Disable lockfile
./melker.ts --no-check app.melker # Skip type checking
./melker.ts --quiet app.melker # Suppress diagnostic output
./melker.ts --cached-only app.melker # Offline modeRunning melker.ts from a remote URL: --reload before the URL is auto-detected and forwarded to the app subprocess — no need to pass it twice:
deno run --allow-all --reload https://melker.sh/melker.ts app.melkerTypeScript API
For programmatic use, import from mod.ts:
import { createElement, createApp } from './mod.ts';
const ui = createElement('container', {
style: { border: 'thin', padding: 2 }
},
createElement('text', { text: 'Hello!' }),
createElement('button', { label: 'OK', onClick: () => app.exit() })
);
const app = await createApp(ui);Or with template literals:
import { melker, createApp } from './mod.ts';
const ui = melker`
<container style=${{ border: 'thin', padding: 2 }}>
<text>Hello!</text>
<button label="OK" onClick=${() => app.exit()} />
</container>
`;
const app = await createApp(ui);Note: melker.ts is the CLI entry point only. All library exports are in mod.ts.
Development
deno task check # Type check
deno task test # Run testsConfiguration
Configuration can be set via CLI flags (highest priority), environment variables, or config file (~/.config/melker/config.json). Priority order: default < policy < file < env < cli
# View current config with sources
./melker.ts --print-config| Option | CLI Flag | Env Variable | Purpose |
|----------------|------------------|--------------------------------|--------------------------------------------------|
| Theme | --theme | MELKER_THEME | Theme selection (default: auto) |
| Graphics mode | --gfx-mode | MELKER_GFX_MODE | sextant (default), block, pattern, luma |
| Server port | --server-port | MELKER_SERVER_PORT | Enable server |
| Headless | --headless | MELKER_HEADLESS | Headless mode for testing |
| Alt screen | --no-alt-screen | MELKER_NO_ALTERNATE_SCREEN | Disable alternate screen buffer |
| Persist | --persist | MELKER_PERSIST | Enable state persistence |
| Log file | --log-file | MELKER_LOG_FILE | Log file path |
| Log level | --log-level | MELKER_LOG_LEVEL | DEBUG, INFO, WARN, ERROR |
Project Structure
melker.ts # CLI entry point (symlink-safe)
mod.ts # Library entry point (exports)
melker-launcher.ts # Policy enforcement, subprocess spawning
src/
melker-runner.ts # .melker file runner
engine.ts # Application engine
layout.ts # Flexbox layout
rendering.ts # Render pipeline
template.ts # .melker file parsing
policy/ # Permission system
bundler/ # Runtime bundler
components/ # UI components
video/ # Video processing
ai/ # AI accessibility
examples/
melker/ # .melker file examples
ts/ # TypeScript examples
docs/
netlify/ # Netlify Edge Functions
edge-functions/
melker.ts # Dynamic launcher (/melker.ts)Examples
# Counter with state
./melker.ts examples/basics/counter.melker
# Markdown viewer (pass file as argument)
./melker.ts examples/showcase/markdown-viewer.melker README.md
# Dialog system
./melker.ts examples/basics/dialog-demo.melker
# Canvas animation
./melker.ts examples/canvas/analog-clock.melker