| name | easl |
| description | Publishes content as shareable web pages via the easl hosting platform. Supports CSV, Markdown, JSON, HTML, SVG, Mermaid diagrams, PDFs, and images — each rendered with an interactive viewer. Use when the user wants to share, publish, or host generated content as a web page, create a shareable URL for data analysis results, reports, dashboards, tables, charts, or diagrams, or when the user mentions easl. Pages are public by default; gate them behind a password (anonymous-publishable) and/or make them account-private (requires sign-in). Available as CLI (`easl`), MCP server (`@easl/mcp`), or HTTP API. |
| metadata | {"author":"easl","version":"0.3"} |
easl
Agent-native hosting: content goes in, shareable URL comes out. easl auto-detects the content type and renders it with the right interactive viewer.
Content types
Text (inline or file)
These work with the single-file shorthand ({content, contentType}) and MCP publish_content:
| Type | Content-Type | Viewer |
|---|
| CSV | text/csv | Sortable, filterable table |
| Markdown | text/markdown | Rendered document with syntax highlighting |
| JSON | application/json | Collapsible tree explorer |
| HTML | text/html | Served as-is (passthrough) |
| SVG | image/svg+xml | Rendered graphic |
| Mermaid | text/x-mermaid (use .mmd extension) | Rendered diagram |
Binary (file only)
These require MCP publish_file / publish_site, or the files[] API with "encoding": "base64". The single-file shorthand corrupts binary data.
| Type | Content-Type | Viewer |
|---|
| PDF | application/pdf | Embedded viewer |
| Images | image/png, image/jpeg, image/gif, image/webp | Image viewer |
For CSV: always include a header row. For multi-file sites with an index.html: easl serves the HTML as-is. Without one, easl auto-generates a navigation page.
Publishing via CLI
The @easl/cli package provides the easl command. Install with curl -fsSL https://easl.dev/install.sh | sh or npm i -g @easl/cli.
Agent protocol
The CLI auto-detects non-TTY environments and outputs JSON — no --json flag needed.
Rules for agents:
- Supply ALL required flags. The CLI will NOT prompt when stdin is not a TTY.
- Pass
--quiet (or -q) to suppress spinners and status messages.
- Exit
0 = success, 1 = error.
- Error JSON:
{"error":{"message":"...","code":"..."}}
- All
delete/rm commands require --yes in non-interactive mode.
Commands
| Command | Description |
|---|
easl publish <path> | Publish a file or directory |
easl publish --content "..." --type markdown | Publish inline content |
cat file | easl publish --type csv | Publish from stdin |
easl list | List sites published from this machine |
easl get <slug> | Get site metadata |
easl delete <slug> --yes | Delete a site |
easl open [slug] | Open site in browser |
easl doctor | Check CLI version, API, and config |
Global flags
| Flag | Description |
|---|
--json | Force JSON output (auto in non-TTY) |
-q, --quiet | Suppress spinners (implies --json) |
--api-url <url> | Override API URL (or set EASL_API_URL) |
Publish flags
| Flag | Description |
|---|
--type <type> | Content type hint: markdown, csv, html, json, svg, mermaid, or MIME type |
--title <title> | Page title |
--template <tpl> | minimal, report, or dashboard |
--slug <slug> | Custom slug (lowercase alphanumeric + hyphens, 3-48 chars) |
--ttl <seconds> | Time to live in seconds |
--private | Account-private — only you (signed in) can view. Requires easl login (on a headless/remote machine: easl login --device). Combine with a password flag to require both gates |
--password <pw> | Password-protect the page with a value you choose. Works with or without --private. Mutually exclusive with --generate-password |
--generate-password | Password-protect the page with a strong password easl generates and shows once. Works with or without --private. Mutually exclusive with --password |
--open | Open in browser after publishing |
--copy | Copy URL to clipboard |
Publish output
{
"url": "https://calm-river.easl.dev",
"slug": "calm-river",
"claimToken": "...",
"expiresAt": "2025-01-14T00:00:00.000Z"
}
The claimToken is stored locally in ~/.config/easl/sites.json for later deletion.
Common patterns
easl publish report.md --title "Q4 Report" --open
cat data.csv | easl publish --type csv --title "Results"
easl publish ./my-site/
easl publish chart.svg --slug my-chart
easl publish board-update.md --generate-password
easl publish board-update.md --password "spring-harbor-77"
easl publish board-update.md --private
easl delete my-chart --yes
easl publish output.json --quiet
Authentication
Only the account gate needs auth — public and password-protected pages publish anonymously. For --private (CLI) / private: true (MCP):
- Unattended (agents, CI): set a pre-provisioned
EASL_API_KEY (easl_…) in the environment. No interactive step, nothing to approve — this is the right path when no human is at a browser.
- Interactive (a human is present):
easl login (browser), or easl login --device on a headless/remote box (SSH, container). --device prints a URL + code and then blocks polling until a human approves in a browser (~10 min timeout) — do NOT run it unattended; it will hang waiting for an approval that never comes.
Get an EASL_API_KEY by running either login flow once and copying the key from ~/.config/easl/credentials.json.
When to use easl
Reach for easl whenever generated content needs to be viewable in a browser or shared via URL:
- Data analysis output (CSV tables, JSON results, charts)
- Reports and documentation (Markdown, HTML)
- Diagrams and visuals (Mermaid, SVG, images)
- Multi-file sites (dashboards, static apps)
- Any artifact that benefits from a shareable link with a proper viewer
Publishing via MCP server
The @easl/mcp server provides six tools:
publish_content — Publish inline text (text types only). Fastest path: pass content and contentType, get a URL back.
publish_file — Publish a single file from disk by path. Works with any file type including binary (PDF, images).
publish_site — Publish an entire directory as a multi-file site. Works with any file types.
create_share_link — Mint a signed, time-limited share URL for an account-private site (requires EASL_API_KEY). Pass slug and optional expiresIn seconds.
list_sites — List sites published in this session.
delete_site — Delete a site by slug (session sites only).
All publish tools accept optional title and template parameters, plus two independent privacy gates: password (string) password-protects the page (works anonymously), and private (boolean) makes it account-private (requires EASL_API_KEY in the server env). To have the server pick a password, omit password and set generatePassword: true — the generated password comes back in the response under password; surface it to the user, it is shown only once.
Publishing via HTTP API
Endpoint: POST https://api.easl.dev/publish
Single-file shorthand (text types only)
{
"content": "# Hello\n\nThis is a published page.",
"contentType": "text/markdown",
"title": "My Page",
"template": "minimal"
}
Multi-file
{
"files": [
{
"path": "index.html",
"content": "<h1>Hello</h1>",
"contentType": "text/html"
},
{
"path": "data.csv",
"content": "bmFtZSxhZ2UKQWxpY2UsMzAKQm9iLDI1",
"contentType": "text/csv",
"encoding": "base64"
}
],
"title": "My Site"
}
Request fields
| Field | Required | Description |
|---|
content | Yes* | Raw content string (text types only — single-file shorthand) |
contentType | Yes* | MIME type of the content |
files | Yes* | Array of {path, content, contentType, encoding?}. Use "encoding": "base64" for binary files. |
title | No | Page title |
template | No | minimal, report, or dashboard |
slug | No | Custom slug (lowercase alphanumeric + hyphens, 3-48 chars) |
private | No | true for an account-private page (requires auth). Independent of password |
password | No | Password gate (4–128 chars). Works alone or stacked with private |
generatePassword | No | true (with no password) to have the server mint a password, returned once |
*Provide either {content, contentType} or {files}.
Response
{
"url": "https://calm-river.easl.dev",
"slug": "calm-river",
"claimToken": "ct_...",
"ogImage": "https://calm-river.easl.dev/_easl/og.png",
"qrCode": "https://calm-river.easl.dev/_easl/qr.svg",
"embed": "<iframe src=\"https://calm-river.easl.dev?embed=1\" width=\"100%\" height=\"500\" frameborder=\"0\"></iframe>",
"expiresAt": "2025-01-14T00:00:00.000Z",
"anonymous": true
}
The claimToken is needed to delete the site later. The embed snippet provides a ready-to-use iframe.
Delete a site
DELETE https://api.easl.dev/sites/{slug}
Header: X-Claim-Token: {claimToken} // or Authorization: Bearer <owner API key>
Mutating endpoints (DELETE, PATCH .../privacy) authorize via the owner's session/Authorization: Bearer key OR the X-Claim-Token header. The claim token works only while the site is still unowned — once it's claimed into an account, only the owner's session/key can mutate it.
Private easls
Two independent, composable gates protect an easl: a password gate (password, anonymous-publishable) and an account gate (private: true, requires auth). Set either, both, or neither. For the password gate, supply your own password, or set generatePassword: true to have the server mint one:
{ "content": "# Confidential", "contentType": "text/markdown", "generatePassword": true }
The publish response then includes the password (the one you supplied, or the generated one):
{ "url": "https://cool-maze.easl.dev", "slug": "cool-maze", "visibility": "public", "password": "dust-arch-fern-dark-1181", ... }
How it behaves:
- Visitors hit a password gate; after unlocking, a signed cookie keeps them in for 30 days.
- The password is shown once — there is no recovery. The owner can rotate it (below).
- Private pages are never cached, never indexed, and skip OG-image generation. No
ogImage/qrCode in the response.
- Via the CLI, the password is also saved to
~/.config/easl/sites.json and surfaced by easl open <slug>.
Toggle privacy / rotate the password
PATCH https://api.easl.dev/sites/{slug}/privacy
Header: X-Claim-Token: {claimToken} // or Authorization: Bearer <owner API key>
Body: { "private": true, "password": "new-pass" } // { "private": false } makes it public + drops the password
private: true sets the account gate; an authenticated caller also becomes/stays the owner, while a claim-token-only caller gets a pure password gate. password may only be sent with private: true — omitting it on the claim-token path mints one (returned once), and on the owner path leaves the password gate off. Rotating the password immediately invalidates everyone's existing unlock sessions.
Limits
- 50 files max per site
- 200 MB total size per site
- 7-day TTL for anonymous sites
- OG image, QR code, and embeddable iframe are auto-generated for every published site