@useguano/guano
v0.1.13
Published
Self-hosted visual website builder: live canvas, layers tree, components, CMS collections, static publishing.
Downloads
1,383
Readme
guano
Self-hosted visual website builder: build pages on a live canvas with a layers tree, style with Tailwind classes, add interactions, reusable components, CMS collections and locales — then publish a fully static site (plain HTML + one CSS file + a ~1.5 KB runtime) served by the built-in server, downloaded as a zip, or pushed to GitHub.
npm create @useguano # scaffolds a folder and installs
cd my-site && npm run dev
# or, without a project folder:
npx -y @useguano/guano devOpen http://localhost:4174/admin, create the first (admin) account, build.
Your published site is at http://localhost:4174/.
CLI
guano dev— start the server for local editingguano start— production server (NODE_ENV=production)guano build [--out dir]— export the current project as static files (default./dist-site)guano mcp— run the MCP server (stdio) so AI agents can edit the site (see MCP)guano connect— issue an API token to a local instance, forguano mcp
Environment
| Variable | Default | Purpose |
|---|---|---|
| PORT | remembered, else 4174 | server port. Without it the port this data dir last landed on is preferred, so two sites on one machine keep their own addresses |
| GUANO_DATA_DIR | ./data under node_modules, else server/data | users, media, drafts, published site — backup = copy this dir; on ephemeral hosts point it at a volume |
| COOKIE_SECURE | 1 | session cookie Secure flag; set 0 only for plain-HTTP LAN setups |
| PUBLISH_TOKEN | unset | when set, allows token-authenticated CI publishes |
| MEDIA_QUOTA | 2 GiB | media library ceiling, bytes |
| STORE_QUOTA | 512 MiB | project storage ceiling, bytes |
| TRUST_PROXY | unset | set 1 only behind a reverse proxy — every rate limit then keys on the last X-Forwarded-For hop instead of the socket address |
| PORT_STRICT | unset | 1 refuses to walk to the next free port when PORT is busy |
| LOG_LEVEL | info | debug | info | warn | error; warn drops the per-request access line |
| LOG_JSON | unset | 1 emits one JSON object per line, for a log shipper |
| SHUTDOWN_GRACE_MS | 0 | how long to keep failing health checks before the socket closes, so a load balancer can drain first |
| GUANO_URL | http://localhost:4174 | guano mcp only — the running instance to connect to |
| GUANO_TOKEN | unset | guano mcp only — an API token (see MCP) |
| GUANO_INSTANCE | unset | guano mcp only — which project this entry was connected to. guano connect writes it; a mismatch against the server's own id is refused by name instead of failing with a 401 |
The admin needs a persistent Node process with a writable disk (VPS, Railway, Fly.io, Render…). It cannot run on Vercel/Netlify — but your published site is fully static and can be hosted anywhere.
Updating
A site is an ordinary npm project, so the thing you update is the package:
cd my-site
npm update @useguano/guano # within the pinned range: ^0.1.0 is 0.1.x
npm start # the running process keeps serving until you restart it
guano --version # what you are now onnpm update @useguano does nothing — @useguano is the npm scope, not a
package name. To cross a minor (0.1.x → 0.2.0), widen the range yourself with
npm install @useguano/guano@latest.
Your site is not in the package: it lives in $GUANO_DATA_DIR (./data),
which npm never touches. Two things to do around an update:
- Back up
data/first, or take a snapshot in Settings → Snapshots. The server brings every project blob up to the current schema at boot, and a schema change is one-way. - Publish once afterwards. What visitors get is the HTML already on disk in
data/site, exported by the version you just replaced, and the runtimes it loads (/assets/script.js,motion.js,slider.js) are copied out of the package at export time. Until you publish again, a fix in the exporter or in a runtime reaches nobody.
MCP (AI agents)
guano mcp exposes a running Guano instance to AI agents over the
Model Context Protocol. The agent edits your
site through the same authenticated HTTP API the editor uses and shares the
editor's own document logic, so structure edits preserve element identity,
styling, and interactions exactly as if you'd typed them.
Claude Desktop: already done by the scaffolder
npm create @useguano asks whether to connect Claude Desktop and the two
permission questions a connected agent needs answered — may it edit the live
project (Main)? and may it publish? — and does the connect right there,
before the server has ever run: it mints an API token into ./data, writes the
permissions beside it, and writes the mcpServers entry into Claude Desktop's
config. The token is pending until you create the admin account at
/admin, which binds it; the setup form shows the permissions you chose
instead of asking again. Quit Claude Desktop before scaffolding (the app
overwrites its config from memory on quit) — the scaffolder waits for you if
it is open.
To connect later, or from another machine running the instance:
npx guano connect # or `npm run connect` in a scaffolded projectThe guano binary is a project dependency, not a global install, so it is
reached through npx (or an npm script) rather than typed bare. It mints the
token (through the running server, or straight into the data dir when the
server is down — --offline never asks the network), asks the same two
questions unless --main / --publish answer them (--yes skips them and
leaves the stored policy alone), and writes Claude Desktop's config. When Main
is allowed it also sets GUANO_MCP_TARGET=main, so the agent starts working
instead of asking which target to use. Change either permission later in
Settings → MCP → Agent permissions. guano connect --print emits the JSON
snippet instead, for any other MCP client or a remote instance.
Manual setup (other clients / remote instances)
1. Create an API token — in the editor: My account → API tokens →
create. Copy the guano_… token (shown once). It carries your role — admin
or editor only (contributors can't).
2. Register the server — it talks to your instance over HTTP; point it at the URL and token. For example, with Claude Code:
claude mcp add guano -- npx -y @useguano/guano mcpthen set the environment in your MCP client config:
{
"mcpServers": {
"guano-my-site": {
"command": "npx",
"args": ["-y", "@useguano/guano", "mcp"],
"env": {
"GUANO_URL": "http://localhost:4174",
"GUANO_TOKEN": "guano_your_token_here"
}
}
}
}Use your deployed URL (e.g. https://studio.example.com) for a hosted instance.
One entry per site. guano connect names it guano-<slug> for exactly
this reason: a single shared key means connecting a second site overwrites the
first one's entry, and since both would point at the same default port the
survivor talks to whichever instance booted first — which answers its token
with a 401. Give each site its own PORT and its own entry. guano connect
also writes GUANO_INSTANCE, which lets guano mcp tell you that another
project is holding the port instead of failing with "not connected"; add it by
hand (the instance field of GET /api/health) if you write the config
yourself.
3. Main vs. a draft — the agent asks you first
Writes go to a target you choose: Main (the live project) or a draft
(a safe branch you later apply from the editor's Drafts panel). The store is
latest-wins, so editing Main while you're also in the editor can clobber work —
drafts are the safe mode. The agent must call set_target before any write,
and its tool prompt tells it to ask you which to use. Creating a draft over MCP
produces exactly the shape the editor opens, and never changes which branch your
editor is viewing.
Tools
get_guide (the agent handbook — also served as MCP instructions) · get_status,
set_target · list_pages, get_page, set_page_html, edit_structure,
create_page, delete_page, set_page_seo · edit_elements (batch: classes, text
content, media src/background, attributes, html id) · list_components,
create_component, update_component, duplicate_component, detach_instance,
delete_component, set_component_variants, create_components ·
get_settings, update_settings (design tokens, SEO
defaults, fonts, custom head) · list_interactions, create_interactions,
update_interaction, delete_interaction,
bind_interaction, unbind_interaction · list_animations, create_animations,
update_animation, delete_animation · list_collections, get_collection,
create_collection, update_collection, delete_collection, upsert_entries,
delete_entry · get_translation_worklist, set_translations · list_media,
upload_media, list_icons · list_comments, create_comment,
reply_to_comment · list_form_submissions, list_integrations · preview,
publish, update_page.
There is no bundled component library: every component is the project's own,
made in the editor or by an agent with create_component.
Pages are read and written as HTML — a strict subset where <Card> is a
component instance and data-ref is a stable address, so one write carries
structure, classes and text together. edit_structure changes part of a page
(insert/replace/move/remove/wrap) without resending it.
Page/element edits are guarded by a version hash from get_page — a stale write
(the page changed underneath) is rejected rather than clobbering. Markup that
does not parse is refused with a line:col; a document that parses but has a
problem (an unknown collection, a duplicate ref) saves and reports it.
