Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ That answers with a relay at a memorable name, open to anyone for 14 days. Claim
- **Encrypted group transport.** Opt-in Marmot KeyPackages and opaque MLS group messages, with account admission and storage caps for ephemeral authors. Clients manage encryption and group membership.
- **A Git host.** Opt-in GRASP-01 Smart HTTP, backed by [ntig](https://github.com/FelineStateMachine/ntig). Signed NIP-34 state authorizes branch and tag changes; events wait for their Git objects before publication. Hosting has [explicit storage limits](docs/22-grasp-01-git-hosting.md#git-http), and the owner can request a bounded read-only inventory without authorizing deletion.
- **A file host.** Blossom and NIP-96 on the same bucket, with mirroring, pre-flight checks and file reports.
- **A console.** Nine tabs on the relay's own page, signed by a browser extension or a remote signer app on a phone. Presets set up an outbox, an inbox, a private relay, a chat, a search replica, a media host or a quiet relay with every costly feature off, in one click. The template collection also includes static sites, public or member-published Marmot transport and Git repositories.
- **A file.** Everything the console sets, as one document with a schema: check it offline, see what applying would change, apply it, pull it back. Keep your relay in your repository. The presets are the same files, in `relay-templates/`.
- **A console.** Ten tabs on the relay's own page, signed by a browser extension or a remote signer app on a phone. Presets set up an outbox, an inbox, a private relay, a chat, a search replica, a media host, a quiet relay with every costly feature off or a home with sites, files and Git on, in one click, with the app shortcuts to match. The template collection also includes static sites, public or member-published Marmot transport and Git repositories. The Connect fold shows those shortcuts to anyone who opens the page: the right app, the relay already attached, a QR for a phone.
- **A file.** Everything the console sets, the app shortcuts included, as one document with a schema: check it offline, see what applying would change, apply it, pull it back. Keep your relay in your repository. The presets are the same files, in `relay-templates/`, and the shortcuts they name are files in `connection-templates/`.
- **Jobs.** Pull another relay in, keep a standing mirror, fetch your own history from your relay list, rebroadcast to other relays, and dump everything to a file on a schedule.
- **Your own domain.** `wss://relay.example.com` onto your relay with one CNAME, or a custom hostname targeting one of your static sites.
- **Succession.** Name an heir. If your key goes silent, the relay warns you for a month and then hands itself over.
Expand Down Expand Up @@ -55,6 +55,7 @@ Site hosting and mirroring are on by default; Marmot and GRASP are off until ena
- [Hosting bind.ws](docs/11-hosting-bindws.md): run your own on your own domain, and watch it through logs, traces and metrics.
- [Hosting without Cloudflare](docs/16-hosting-without-cloudflare.md): the same Worker on celld, with your own bucket and proxy, and what "supported" promises.
- [Develop and extend](docs/12-develop-extend.md): layout, tests, adding methods and NIPs, the console, the templates.
- [Connection templates](docs/17-connection-templates.md): the app shortcuts on the Connect fold, the template file, its placeholders, the owner's list, `/connect.json`, and how to add one.
- [Costs and margins](docs/15-costs-and-margins.md): application budgets, retained storage, inventory interpretation and deployment margins.

### Draft NIPs
Expand Down
6 changes: 4 additions & 2 deletions STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ docs: main takes commits, not pull requests

- **The area** is the module, folder or concern the change belongs to:
`views`, `kinds`, `config`, `routes`, `console`, `docs`, `test`, `ci`,
`host`, `rules`, `relay-templates`, `scripts`, `tidy`.
`host`, `rules`, `relay-templates`, `connection-templates`, `connections`,
`scripts`, `tidy`.
- **No imperative mood and no Conventional Commits.** There is no `feat:`,
`fix:`, `chore:`, and no "add", "update" or "refactor" opening a subject.
- **Length is not a constraint.** The median subject is 75 characters and the
Expand All @@ -55,7 +56,7 @@ Each commit typechecks on its own.

## Documentation

- `docs/` is numbered with two digits: `00` to `05` for users, `10` to `16`
- `docs/` is numbered with two digits: `00` to `05` for users, `10` to `17`
for developers, `20` and up for draft NIPs the repository implements ahead
of upstream.
- Each file opens with frontmatter naming its `title` and its `audience`
Expand Down Expand Up @@ -164,6 +165,7 @@ file means adding its row.
| Catalog | Where | Enforced |
|---|---|---|
| one row per template | `relay-templates/README.md` | yes, the build fails without it |
| one row per connection template | `connection-templates/README.md` | yes, the build fails without it |
| one link per doc | `README.md` | by review |
| one entry per generated file | `src/gen/` staleness check | yes, in `npm run typecheck` |
| every door | `test/object/exposure.test.ts` | by review |
Expand Down
53 changes: 53 additions & 0 deletions connection-template.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://bind.ws/connection-template.schema.json",
"title": "bind.ws connection template",
"description": "One app shortcut for a relay's Connect fold: the app, what it does with this relay, and the links that hand a viewer over to it. Links carry {relay:*}, {owner:*}, {user:*} and {input:*} placeholders the relay fills in. The relay's parser (src/connections.ts) is the last word; this schema is for the editor.",
"type": "object",
"required": ["format", "title", "about", "app", "links"],
"additionalProperties": false,
"properties": {
"$schema": { "type": "string" },
"format": { "enum": ["bind.ws/connection-template/1"] },
"title": { "type": "string", "minLength": 1, "maxLength": 40, "description": "what the shortcut is for, in the owner's words: Repos, Notes, Photo library" },
"about": { "type": "string", "minLength": 1, "maxLength": 200, "description": "one sentence on what opens and what it does with this relay" },
"app": { "type": "string", "minLength": 1, "maxLength": 40, "description": "the app the shortcut hands over to" },
"where": { "type": "string", "maxLength": 40, "description": "where the app runs: web, phone, iPhone, Android, desktop, terminal" },
"icon": { "enum": ["notes", "blog", "bookmark", "photo", "site", "git", "chat", "person", "files", "key", "search", "feed", "lock", "app"], "description": "one of the console's icons" },
"feature": { "enum": ["search", "sync", "count", "discovery", "names", "files", "pages", "signer", "sites", "marmot", "grasp", "push"], "description": "the shortcut is shown only while this feature is on" },
"visibility": { "enum": ["public", "auth", "members", "owner"], "description": "who sees the shortcut when the owner adds it without saying: anyone, anyone signed in, members, the owner" },
"links": {
"type": "array",
"minItems": 1,
"maxItems": 6,
"items": {
"type": "object",
"required": ["label"],
"additionalProperties": false,
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 40 },
"href": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "a link to open: https://, or nostr: for the viewer's own app" },
"copy": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "text to put on the clipboard instead of a link" }
},
"oneOf": [{ "required": ["href"] }, { "required": ["copy"] }]
}
},
"qr": { "type": "string", "minLength": 1, "maxLength": 2000, "description": "what the QR code carries; the first link's href when left out" },
"inputs": {
"type": "array",
"maxItems": 4,
"items": {
"type": "object",
"required": ["name", "label"],
"additionalProperties": false,
"properties": {
"name": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{0,23}$", "description": "the {input:name} placeholder it fills" },
"label": { "type": "string", "minLength": 1, "maxLength": 60 },
"placeholder": { "type": "string", "maxLength": 200 },
"default": { "type": "string", "maxLength": 500, "description": "the value when the owner leaves it blank" },
"pattern": { "type": "string", "maxLength": 200, "description": "a regular expression the owner's value must match; a value that lands in a URL path or a shell command names one" }
}
}
}
}
}
15 changes: 15 additions & 0 deletions connection-templates/01-notes.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// The relay as a feed: everything posted here, in Jumble, which opens a
// relay by URL. The QR carries the same link for a phone.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Notes",
"about": "Everything posted on this relay as one feed.",
"app": "Jumble",
"where": "web",
"icon": "notes",
"visibility": "public",
"links": [
{ "label": "Open", "href": "https://jumble.social/?r={relay:url|enc}" }
]
}
19 changes: 19 additions & 0 deletions connection-templates/02-find-me.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Feed apps have no relay pages. They meet this relay through the owner's
// profile with the relay attached as the hint, and keep it once it is in
// the app's relay settings. The nostr: link opens whatever app the viewer
// has set for it; the web link is Primal.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Find me here",
"about": "The owner's profile with this relay attached, for feed apps.",
"app": "Primal",
"where": "web, phone",
"icon": "person",
"visibility": "public",
"links": [
{ "label": "Open", "href": "https://primal.net/p/{owner:nprofile}" },
{ "label": "Open in app", "href": "nostr:{owner:nprofile}" }
],
"qr": "nostr:{owner:nprofile}"
}
19 changes: 19 additions & 0 deletions connection-templates/03-group.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// The relay's NIP-29 group as a room: Flotilla opens the relay as a space
// on the web and on a phone, and any group-aware app takes the naddr. The
// QR carries the group address for a phone.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Group",
"about": "The relay as a space, with its group as a room.",
"app": "Flotilla",
"where": "web, phone",
"icon": "chat",
"visibility": "public",
"links": [
{ "label": "Open", "href": "https://app.flotilla.social/spaces/{relay:host|enc}" },
{ "label": "Open group in app", "href": "nostr:{relay:naddr}" },
{ "label": "Copy naddr", "copy": "{relay:naddr}" }
],
"qr": "nostr:{relay:naddr}"
}
20 changes: 20 additions & 0 deletions connection-templates/04-blog.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// The owner's long-form articles (kind 30023), in YakiHonne, which reads
// and writes them and opens a profile at /profile/<nprofile>, the pattern
// the console already uses. Habla has a /p/<nprofile> route in its source
// (verbiricha/habla.news src/pages/p/[nprofile].tsx) but habla.news
// answered 404 on 2026-09-05, so the second link is the nostr: handoff.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Blog",
"about": "The owner's articles, with this relay attached so a reader finds the rest of them.",
"app": "YakiHonne",
"where": "web, phone",
"icon": "blog",
"visibility": "public",
"links": [
{ "label": "Open", "href": "https://yakihonne.com/profile/{owner:nprofile}" },
{ "label": "Open in app", "href": "nostr:{owner:nprofile}" }
],
"qr": "nostr:{owner:nprofile}"
}
25 changes: 25 additions & 0 deletions connection-templates/05-repos.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// The relay's Git repositories: GitWorkshop has a relay page by host, the
// pattern the console already uses. The clone command names the signed-in
// viewer, since a repository here lives at <relay>/<npub>/<repo>.git
// (docs/22-grasp-01-git-hosting.md), so a visitor sees that link left out.
// The owner fills in the repository name when adding the shortcut; the
// input's pattern keeps it to letters, digits, dot, dash and underscore,
// since the value lands inside a quoted shell command and a URL path.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Repos",
"about": "This relay's Git repositories, and the clone command for yours.",
"app": "GitWorkshop",
"where": "web, terminal",
"icon": "git",
"feature": "grasp",
"visibility": "public",
"inputs": [
{ "name": "repo", "label": "Repository name", "placeholder": "my-project", "default": "<repo>", "pattern": "^[A-Za-z0-9][A-Za-z0-9._\\-]{0,63}$" }
],
"links": [
{ "label": "Open", "href": "https://gitworkshop.dev/relay/{relay:host|enc}" },
{ "label": "Copy clone command", "copy": "git clone '{relay:web}/{user:npub}/{input:repo}.git'" }
]
}
21 changes: 21 additions & 0 deletions connection-templates/06-sites.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// NIP-5A sites: a root site lives at https://<npub>.<domain>, so the
// owner's and the viewer's own are plain links (docs/20-nip-5a-static-websites.md).
// The publish command is nsyte's, with this relay as both the relay and
// the Blossom server; the -r/--relays and -s/--servers flags are read from
// sandwichfarm/nsyte src/commands/deploy.ts on 2026-09-05.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Sites",
"about": "Static sites this relay hosts, and the command that publishes one here.",
"app": "nsyte",
"where": "web, terminal",
"icon": "site",
"feature": "sites",
"visibility": "public",
"links": [
{ "label": "Open the owner's site", "href": "https://{owner:npub}.{relay:domain}" },
{ "label": "Open your site", "href": "https://{user:npub}.{relay:domain}" },
{ "label": "Copy publish command", "copy": "nsyte deploy ./dist --relays {relay:url} --servers {relay:web}" }
]
}
19 changes: 19 additions & 0 deletions connection-templates/07-bookmarks.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// The viewer's own bookmarks and lists (kinds 10003 and 30003), in Listr,
// whose user page takes an npub (erskingardner/listr src/params/user.ts and
// src/routes/(app)/[userId=user]/+page.svelte; both kinds are in
// src/lib/utils/lists.ts SUPPORTED_LIST_KINDS). noStrudel takes an nprofile
// at /bookmarks/<pointer> (hzrd149/nostrudel src/views/bookmarks/routes.tsx).
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Bookmarks",
"about": "Your bookmarks and lists in a list manager; add this relay in its settings so they land here.",
"app": "Listr",
"where": "web",
"icon": "bookmark",
"visibility": "auth",
"links": [
{ "label": "Open", "href": "https://listr.lol/{user:npub}" },
{ "label": "Open in noStrudel", "href": "https://nostrudel.ninja/bookmarks/{user:nprofile}" }
]
}
21 changes: 21 additions & 0 deletions connection-templates/08-photos.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// The owner's photos on this relay's Blossom store, in bouquet, a media
// manager for Blossom and NIP-96 servers (flox1an/bouquet README). No
// client here encrypts blobs, and the owner-only start hides only the
// shortcut: who may read the files is the relay's read rule, which a blob
// follows like an event. bouquet has no server URL parameter
// (src/main.tsx), so the store's address is a copy link.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Photo library",
"about": "The owner's photos on this relay's file store, in a media manager that takes the store as a server; the read rule says who may see the files.",
"app": "bouquet",
"where": "web",
"icon": "photo",
"feature": "files",
"visibility": "owner",
"links": [
{ "label": "Open", "href": "https://bouquet.slidestr.net/" },
{ "label": "Copy Blossom server URL", "copy": "{relay:web}" }
]
}
21 changes: 21 additions & 0 deletions connection-templates/09-files.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// The relay's Blossom store for members: bouquet browses, uploads and
// syncs across Blossom and NIP-96 servers (flox1an/bouquet README) once
// this relay is in its server list, which is why the server URL and the
// NIP-96 discovery document are copy links. The discovery path is the
// relay's own (src/nip96.ts).
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Files",
"about": "This relay's file store, for members, in a media manager that takes it as a server.",
"app": "bouquet",
"where": "web",
"icon": "files",
"feature": "files",
"visibility": "members",
"links": [
{ "label": "Open", "href": "https://bouquet.slidestr.net/" },
{ "label": "Copy Blossom server URL", "copy": "{relay:web}" },
{ "label": "Copy NIP-96 URL", "copy": "{relay:web}/.well-known/nostr/nip96.json" }
]
}
18 changes: 18 additions & 0 deletions connection-templates/10-dm.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Private messages (NIP-17): the owner in a DM app by the nostr: handoff,
// since DM apps open a profile and not a relay. 0xchat is the reference,
// on phone and desktop with NIP-17 (0xchat-app/0xchat-core README). The
// relay URL goes in the app's DM inbox list (kind 10050) so messages land here.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Messages",
"about": "Message the owner privately; put this relay in your DM inbox list (kind 10050) so replies land here.",
"app": "0xchat",
"where": "phone, desktop",
"icon": "chat",
"visibility": "public",
"links": [
{ "label": "Open in app", "href": "nostr:{owner:nprofile}" }
],
"qr": "nostr:{owner:nprofile}"
}
19 changes: 19 additions & 0 deletions connection-templates/11-marmot.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Marmot (MLS over Nostr) group chat: White Noise is the reference client,
// on iOS by TestFlight and Android by Zapstore or GitHub (whitenoise.chat
// and its /download page, checked 2026-09-05). The app has no relay URL
// form, so the links are its site and the relay URL for its settings.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Encrypted groups",
"about": "End-to-end encrypted group chat over this relay, in White Noise.",
"app": "White Noise",
"where": "phone",
"icon": "lock",
"feature": "marmot",
"visibility": "public",
"links": [
{ "label": "Open", "href": "https://whitenoise.chat/" },
{ "label": "Get the app", "href": "https://whitenoise.chat/download" }
]
}
16 changes: 16 additions & 0 deletions connection-templates/12-relay-page.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// The relay's own page in Coracle: its feed and its people, by host, the
// pattern the console already uses. The host is encoded as the console
// does, which a plain hostname passes through unchanged.
{
"$schema": "https://bind.ws/connection-template.schema.json",
"format": "bind.ws/connection-template/1",
"title": "Relay page",
"about": "This relay's page in Coracle: its feed and its people.",
"app": "Coracle",
"where": "web",
"icon": "feed",
"visibility": "public",
"links": [
{ "label": "Open", "href": "https://coracle.social/relays/{relay:host|enc}" }
]
}
Loading