The public registry of extensions for Tend, the self-hosted server panel.
Every extension lives here as plain source, in its own folder, next to the community App Store recipes. A pull request adds one or updates one. On merge, CI builds every package, signs the index, and publishes a release that every Tend panel in the world can verify and install from, with no panel update in between.
Tend is a self-hosted control panel for the servers you own. It deploys apps and services from Git, runs them in containers, watches their health, and wraps the whole thing in a calm, desktop-like shell: windows, a menu bar, a dock, widgets on a home shelf, and a small set of built-in apps. Extensions are how that shell grows. A calculator, a note-taking tool, a game for a coffee break, a dashboard for something you run: each one is a small ES-module package that the panel mounts in its own window and talks to through a narrow host API.
Tend is available as self-hosted (you run the panel) and as hosted panels at Tend. Both read this registry the same way.
An extension is a folder with an extension.json manifest (schema 2) and the ES modules it declares.
The panel loads the entry module, calls activate(host), and mounts what it returns in a
tool window or as a full shell app. Through host an extension gets exactly what it declared
in permissions and nothing else: key-value storage, notifications, the current theme and
user, and, for extensions that declare them, files, media, documents, sites, and the read-only ondacast catalog.
There is no direct outbound network from an extension: anything external goes through a panel capability.
Every file an extension ships is pinned in the manifest's integrity map (SHA-256). The panel refuses a package whose bytes disagree with its manifest, and runs every package through its installer, its integrity verifier, and its web application firewall before a single module executes.
// extensions/host.tend.calculator/extension.json (abridged) { "schema": 2, "id": "host.tend.calculator", "name": "Calculator", "version": "3.0.1", "author": "Tend", "description": "Seven-mode calculator …", "icon": "icon.svg", // deprecated: only for panels older than themed icons "glyph": "glyph.svg", // required for every non-theme package "ui": { "module": "index.js", "mount": "tool-window", "size": { "w": 460, "h": 700 } }, "permissions": ["storage"], "integrity": { "index.js": "sha256-…", "engine/parser.js": "sha256-…" } }
glyph.svg is the one-colour icon the panel draws on a tile coloured from the active theme; no colour of
yours is ever shown. The registry refuses a non-theme package without a valid one, and the rules live in one
place, docs/icons.md. icon.svg is deprecated and kept only for panels older than the
glyph release. Theme packs carry neither.
Next to the manifest, listing.json carries what the store shows but the runtime does not need:
publisher, category, feature bullets, requirements, and the release notes for the current version.
| Extension | Version | Category | What it is |
|---|---|---|---|
Calculator (host.tend.calculator) |
3.0.1 | productivity | Standard, scientific, graphing, programmer, statistics, financial and converter modes, a real expression engine, a history tape, full keyboard control |
Calendar (host.tend.calendar) |
1.5.1 | productivity | Month, week, day and agenda views with local events, reminders and ICS import/export |
Developer Tools (host.tend.devtools) |
1.0.1 | developer-tools | JSON, Base64, URL, JWT, hash, UUID, timestamp and guarded regex tools in one offline window |
Tend Player (host.tend.media) |
1.7.1 | media | Radio, podcasts and audiobooks from OndaCast with subscriptions, a queue, per-show speed, sleep timer, transcripts and clips to Notes |
Riftwing: Skybound (com.tendstack.riftwing) |
4.1.2 | games | A fast, replayable sky-runner with eight worlds and nine pilots |
2048 Odyssey (host.tend.2048) |
2.0.2 | games | Merge mastery across an endless target ladder |
Breakout Odyssey (host.tend.breakout) |
2.2.1 | games | Campaign Breakout with boss walls and live relayout |
Cookie Empire Odyssey (host.tend.cookie) |
2.2.1 | games | An active-idle bakery with upgrades and prestige |
Fruit Slash Odyssey (host.tend.fruitninja) |
2.2.1 | games | Fruit-slicing arcade action |
Gem Crush Odyssey (host.tend.gemcrush) |
3.0.5 | games | An 80-stage match-three campaign |
Match Puzzle: Odyssey (host.tend.matchpuzzle) |
2.1.2 | games | A polished match puzzle adventure |
Memory Match Odyssey (host.tend.memory) |
2.2.1 | games | A 50-stage memory campaign |
Minesweeper Odyssey (host.tend.minesweeper) |
3.0.3 | games | A no-guess Minesweeper expedition |
Neon Cyber-Man (host.tend.neon-cyber-man) |
1.0.2 | games | A cyberpunk maze-chase arcade game with adaptive ghost AI |
Peggle Odyssey: Arc Light (host.tend.peggle) |
3.0.7 | games | Arcade ricochet with fever shots and star-rated stages |
Simon Odyssey: Neural Pulse (host.tend.simon) |
3.0.4 | games | A 60-session brain-training campaign |
Snake Odyssey: Neon Wilds (host.tend.snake) |
3.1.2 | games | Modern Snake with endless classic growth |
Solitaire Odyssey: Starlight Circuit (host.tend.solitaire) |
3.0.2 | games | Klondike with fluid card dragging and 36 stages |
Sudoku (host.tend.sudoku) |
1.1.1 | games | Classic 9×9 Sudoku |
Tetris (host.tend.tetris) |
3.1.1 | games | The classic, rebuilt with keyboard and touch controls |
Word Guess (host.tend.wordle) |
1.1.1 | games | Guess the hidden word in six tries |
Nebula (host.tend.theme.nebula) |
1.0.1 | themes | Theme pack: orchid starlight and teal dust across a violet spiral galaxy |
Synthwave (host.tend.theme.synthwave) |
1.0.1 | themes | Theme pack: gold contrails over a neon wireframe horizon |
Inkwell (host.tend.theme.inkwell) |
1.0.2 | themes | Theme pack: cyan, cobalt and gold ink blooming beneath rippling water |
Prism (host.tend.theme.prism) |
1.0.1 | themes | Theme pack: a tangerine cube and amber light beams in a quiet concrete gallery |
Skyline (host.tend.theme.skyline) |
1.0.1 | themes | Theme pack: sapphire night over a riverside city, window light glowing gold |
The table is a snapshot; dist/registry.json built from main is always the authority.
Not every extension is code. A theme pack is an extension with category: "themes" that carries only
a declarative theme object (colours, a wallpaper, a few shell settings) and its images. It has no
scripts and no permissions, and every colour passes a closed grammar, so installing one can only change
how the panel looks. Enabled packs appear in the wallpaper picker, and each account tunes its own.
Five first-party packs ship here (Nebula, Synthwave, Inkwell, Prism, Skyline). To make your own, copy
templates/theme-pack and follow docs/themes.md.
The registry also carries App Store recipes: small, reviewed descriptions of how a panel installs a
self-hosted app from a container image (image, port, environment hints, volumes, databases). Each lives in
recipes/<slug>/, and the merged set is published as a signed Tend Community catalog, listed by default in
panels as "Community · reviewed by Tend". "Reviewed" means a person read the recipe's text; it is never
tested or certified, which only Tend's first-party catalog can claim. To propose one, copy
templates/recipe/example-notes and follow docs/recipes.md.
flowchart LR
A[Contributor opens a pull request<br/>extensions/<id>/] --> B[CI validates the manifest,<br/>rebuilds the integrity map,<br/>builds a deterministic ZIP,<br/>runs it through the real panel installer]
B --> C[Merge to main]
C --> D[CI publishes the exact commit here<br/>and signs registry.json]
D --> E[GitHub release registry-N<br/>every ZIP + registry.json + signed index]
E --> F[Panels fetch the newest release,<br/>verify the signature and sequence]
F --> G[Store shows the extension,<br/>installed ones get an Update badge]
- The index is signed.
tend-extension-registry-v1.jsonis an Ed25519-signed envelope. Panels pin the registry's public key and refuse anything unsigned, tampered, expired, or older than what they last verified (sequenceonly ever increases). - Every package is pinned. The signed index carries each ZIP's name, size, and SHA-256. Download URLs are never in the file; a panel derives them from the release tag, so the signature always guards the bytes it will install.
- Builds are reproducible.
tools/build.pyproduces byte-identical ZIPs from the same source, and CI refuses a pull request whose package the panel's own installer would reject. - Panels stay in control. The registry is opt-in per panel (Settings → Extensions), checks every six hours by default, and only ever offers an update. Installing still runs the panel's integrity verifier and firewall, and a rollback is one click away.
Open Settings → Extensions in your Tend panel. Leave the registry enabled and the panel checks for new extensions and updates on its own, notifying you once per new set. "Check now" asks immediately. Installed extensions with a newer registry version show Update available in the store, with Update all beside it. If you would rather freeze what you have, turn the registry off; the extensions bundled with your panel keep working exactly as before.
We would love your extension here. The full guide is in CONTRIBUTING.md; the short version:
Add a new extension
- Fork this repository and create
extensions/<your.extension.id>/. Ids are reverse-DNS (com.example.weather);host.tend.*andcom.tendstack.*are reserved for first-party work. - Start from
templates/extension. Writeextension.json(schema 2) and your ES modules. Declare only the permissions you use. - Draw
glyph.svgand declare"glyph": "glyph.svg"(docs/icons.md). - Write
listing.json: publisher, category, three to six feature bullets, requirements, release notes. - Build locally and let the tooling rebuild your integrity map:
pip install -r tools/requirements.txt python tools/build.py
- Open a pull request. A public check validates the manifest and rebuilds your package on a GitHub-hosted
runner (no secrets), with hints that point the reviewer at risky code. A maintainer reviews the code, tries it
in a panel and approves that exact commit; the maintainers' pipeline then imports it, runs your package
through the real panel installer and releases it. Your extension ships in the next
registry-Nrelease. How that works, and what stays maintainer-only, is in docs/community-review.md.
Add an App Store recipe: copy templates/recipe/example-notes to
recipes/<slug>/, pin an exact image version, and run python tools/validate_recipe.py recipes/<slug>. The rules
are in docs/recipes.md; one recipe per pull request.
Update an extension you maintain
- Change the code, bump
versioninextension.json(semantic versioning,x.y.z), and writerelease_notesinlisting.jsonfor that version. python tools/build.py, then open a pull request. Panels that have the older version get an Update available badge as soon as the release is out.
House rules
- One extension per folder, one folder per pull request.
- No outbound network, no remote scripts, no minified-only sources. Reviewers read what ships.
- Every code extension ships a one-colour
glyph.svg(docs/icons.md); theme packs ship none. - Never edit the
integritymap by hand; the build rebuilds it and CI compares. - Keep third-party code to what the panel allows as a runtime module; today that is
phaser@4. - Be kind in reviews and honest in release notes.
An optional developer kit for people and AI coding agents lives in this repository:
developers/ (AGENTS.md for the agent, plus a
skill for extensions, theme packs, Dockerfiles, compose files, recipes, deploying your own app,
Git and PRs, and the API). There is also a human guide, developers/README.md.
Read this before you use it. Every extension, theme pack, App Store recipe and listing is inspected by humans before it is published. The AI kit is a convenience, not the default. You are responsible for inspecting, testing, fixing and patching AI-generated code (and Dockerfiles, compose files and recipes) before you open a PR, and reviewers reject code its author can't explain. Registry PRs always get human review.
extensions/<id>/ one folder per extension (id = manifest id)
extension.json schema-2 manifest: id, name, version, ui, permissions, integrity
listing.json store listing: publisher, category, features, requirements, release notes
glyph.svg the themed icon shape (required unless category is themes)
README.md, *.js the extension itself; icon.svg only for panels older than glyphs
developers/ the developer kit: README, AGENTS.md for AI agents, skills/, guides/
templates/extension/ starter for a code extension, with a conforming glyph.svg (copy into extensions/)
templates/theme-pack/ starter for a gradient-based theme pack (copy into extensions/; never built as is)
templates/recipe/example-notes/ starter for a community recipe (copy to recipes/<slug>/; never built as is)
recipes/<slug>/ one community App Store recipe: recipe.json + listing.json (nothing else)
recipes/recipe.schema.json JSON Schema for recipe.json (tools/validate_recipe.py is the authority)
tools/
build.py validate, rebuild integrity maps, build deterministic ZIPs, write registry.json
sign.py sign registry.json into the published envelope (also --verify)
validate_with_panel.py run every built package through the panel's own installer
release.py create or update the registry-N GitHub release and upload its assets
validate_recipe.py validate recipes/ (public rules) and build the community catalog
sign_catalog.py sign the community catalog in the format panels verify
review_hints.py reviewer hints and the contributor-path check for a pull request
import_approved.py import pull requests the Tend Review App approved (Gitea, scheduled)
mcp_window.py derive the MCP runtime's core window, version and sequence from a core checkout
mcp_release.py publish mcp-runtime-<version> and move the mcp-runtime-latest alias
ipmeta_release.py fetch, manifest, sign and publish the ipmeta-<YYYY.MM> data release
requirements.txt the dependencies are `cryptography` and `Pillow`
tests/ tooling tests (reproducible builds, integrity, signing, panel validation)
scripts/publish-verified-main.py publishes the exact verified commit to this repository
.gitea/workflows/ci.yml the pipeline: build and validate → publish → sign and release
.gitea/workflows/import-approved.yml every 10 minutes: import approved community pull requests
.github/workflows/validate-pr.yml public pull-request checks (GitHub-hosted, no secrets, read-only token)
.gitea/workflows/mcp-runtime.yml build, sign and publish the optional MCP component runtime
.gitea/workflows/ipmeta.yml monthly: sign and publish the offline IP data (docs/ipmeta.md)
docs/icons.md the glyph rules and drawing guide (the one canonical icon page)
docs/themes.md how to build and publish a theme pack
docs/recipes.md the recipe rulebook, and how the signed community catalog is published
docs/community-review.md pull-request checks, approval and import, and the operator setup
docs/mcp-runtime.md what that runtime is, how it is signed, and how a core release re-signs it
dist/ is never committed. Everything a panel downloads is rebuilt by CI from the sources in main.
If you find a problem with the registry, its signing, or a published extension, please do not open a
public issue. Use Security → Report a vulnerability on this repository so the report stays private
until it is fixed. Every published package can be rebuilt from its source folder at the commit recorded
in the release's registry.json, so a reviewer can always check what a panel installed against what
was merged.
The registry tooling and the first-party extensions are released under the MIT License. Each extension folder may carry its own license file; when it does, that license applies to that folder.