Skip to content

Volcanex/gseed-app

Repository files navigation

seed

Seed is a template, not a project. Clone or fork it to start a new site. Once you do, rename the project (root README.md, root CLAUDE.md, anywhere "seed" appears in headings or prose) to whatever you're actually building. Don't try to run a long-lived deployment "as seed" — that just creates a project called "seed" that confuses every future agent that touches it.

A static-first web framework. HTML fragments get compiled into a shared shell. APIs are auto-discovered FastAPI routers. Claude maintains its own documentation via an auto-indexed CLAUDE.md tree.

Good for: marketing sites, dashboards, small SaaS, internal tools, client work where you want a proven shape without a heavyweight framework.

Starting a new project from seed

git clone https://github.com/Volcanex/seed.git my-site
cd my-site
rm -rf .git && git init      # detach from upstream; new history
# Rename the project: edit README.md (title + prose), CLAUDE.md
# (heading), pages/home/config.json (title), and the docker compose
# service name if you want.
pip install -r requirements.txt

python3 compile.py          # Build pages into output/
python3 server.py           # Serve on http://127.0.0.1:8080

(If you just want to try seed without renaming, the commands above work — but rename it before adding real content. The framework leans on CLAUDE.md files for orientation, and "this project is called seed" is a misleading first thing for an agent to read in a project that isn't actually seed.)

Open http://127.0.0.1:8080/ — the home page is served from pages/home/. Visit /api/_routes to see every mounted endpoint.

The local server binds to loopback (127.0.0.1) by default so a casual run doesn't expose anything on the LAN. Set HOST=0.0.0.0 to bind all interfaces. The Docker image already does this.

With Docker

Two modes, one compose file. Pick the one that matches your host.

Behind an existing reverse proxy (host nginx, Cloudflare tunnel, etc.):

cp .env.example .env
# set HOST_PORT to a free loopback port, e.g. 5002
docker compose up -d

The app binds to 127.0.0.1:${HOST_PORT}. Point your reverse proxy at that. Nothing is exposed to the public internet directly.

Standalone (this container owns 80/443, automatic Let's Encrypt TLS via Caddy):

cp .env.example .env
# set DOMAIN=yourdomain.com and EMAIL=you@example.com
docker compose --profile standalone up -d

Dev mode (live source mounting + uvicorn --reload):

cp docker-compose.override.yml.example docker-compose.override.yml
docker compose up -d

Compose auto-merges the override; edits to pages/, core/, and server.py take effect without a rebuild.

Layout

CLAUDE.md              — Root project docs (auto-indexed)
server.py              — FastAPI; auto-discovers api.py files
compile.py             — Builds pages/* into output/*.html
core/
  templates/shell.html — Shared HTML shell (wraps every page)
  static/base.css      — Baseline CSS; copied to output/css/
pages/
  home/config.json     — Page metadata
  home/content.html    — Page body fragment
  hello/api.py         — Example auto-mounted router
scripts/
  compile_docs.py      — Rebuilds CLAUDE.md auto-index
.claude/
  rules/               — Path-scoped rules (auto-activate in Claude Code)
  commands/            — Slash commands (/new-page, /new-api, /compile)
tests/test_smoke.py    — pytest smoke suite
Dockerfile, docker-compose.yml, Caddyfile

Conventions

Pages. Each page is pages/{slug}/ with config.json + content.html. Optionally api.py (auto-mounted at /api/{slug}). Optionally CLAUDE.md (indexed automatically).

APIs. Any *.py that exports router = APIRouter() is found and mounted at startup. See .claude/rules/api-conventions.md.

Docs. Every non-obvious directory gets a CLAUDE.md. No other .md files. Root CLAUDE.md holds an auto-generated index between <!-- DOCS:START --> and <!-- DOCS:END -->. Run python3 scripts/compile_docs.py after edits.

Extending

When a project grows to host multiple independent products on one infra, introduce top-level product directories alongside pages/ (see root CLAUDE.md for the core/product split). Most projects never need this — start without it.

Safety guarantees

Small framework, small surface — but the static-file server and the build step both have to stay honest:

  • Output-dir confinement. The catch-all route resolves every URL path under output/ and rejects anything that escapes (e.g. .. segments). The tree-walk fallback uses the same check.
  • Slug sanitisation. compile.py rejects any slug that contains .., leading /, or \. A bad slug fails the build with a non-zero exit code.
  • Escape-by-default rendering. Shell substitutions are HTML-escaped ({{ title }}); use {{! key }} only for already-rendered HTML.
  • Output is wiped on every build. Deleted pages don't linger as serveable URLs.
  • Loopback by default. python3 server.py binds 127.0.0.1 unless HOST=0.0.0.0 is set explicitly.
  • Admin login hardened. Constant-time password compare, rate-limited (5 failures per IP per 60s), Secure cookies, and a loud startup warning if ADMIN_PASSWORD is unset or still the default. The auth itself is a placeholder — replace before shipping anywhere public.
  • Build is fail-loud. Invalid config.json, unsafe slugs, and malformed manifest entries all exit non-zero.

Why this exists

Three separate web projects I was running all converged on the same shape: pages/{slug}/{config.json, content.html} + an auto-router FastAPI/Flask server + a compile step for HTML. This repo is that shape, distilled, so future projects start from something already proven.

The Claude docs system (auto-indexed CLAUDE.md files + path-scoped rules) is the other half — it means Claude stays oriented as the project grows, without anyone hand-writing an onboarding doc that rots.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors