index ↓

publishing apps

apps.bridgething.com is the default source. anyone can host another.

start with the scaffold

bun create bridgething does all of this for you. it asks for a source name, a github repo and a first app, and writes a repo that holds your webapps and publishes them. run bun run new <app> to add another, then push to main. github actions builds each app, hosts the bundles on github pages with the right headers, and publishes the catalog.

a source is a url

publish a catalog.v1 document at an https url. users add it by pasting the url.

{
  "schema": "catalog.v1",
  "updated_at": "2026-07-24T00:00:00Z",
  "repo": {
    "name": "my apps",
    "description": "webapps I publish for bridgething.",
    "homepage": "https://example.com",
    "icon": "https://example.com/icon.svg"
  },
  "apps": [ /* AppEntry… */ ],
  "recommended_sources": []
}

app entries

id is a uuidv7. keep it stable across versions, and give every app in your catalog a different one.

{
  "id": "019e6701-13f8-71b5-ba04-85d326630e98",
  "name": "Calendar",
  "description": "Upcoming events from an iCalendar feed.",
  "author": "you",
  "icon": "https://example.com/icons/calendar.svg",
  "screenshots": ["https://example.com/shots/calendar-1.png"],
  "homepage": "https://example.com",
  "source": "https://github.com/you/calendar",
  "versions": [
    {
      "version": "0.1.0",
      "released_at": "2026-07-24T00:00:00Z",
      "download": {
        "url": "https://example.com/r/<id>/0.1.0.zip",
        "size": 402118,
        "sha256": "…"
      },
      "settings": {
        "url": "https://example.com/s/<id>/0.1.0.html",
        "size": 26909,
        "sha256": "…"
      },
      "permissions": ["net.fetch"],
      "role": "launcher",
      "provides_overlay": true,
      "extension": { "desktop": true, "permissions": ["all"] },
      "min_libbridgething_version": "0.12.0",
      "changelog": "Initial release."
    }
  ]
}

the store checks download.sha256 before installing.

role, provides_overlay, and extension are optional and mirror the bundle's manifest. see launchers and overlays.

icon can be any image format.

screenshots is up to six https urls of the app running on a device, 800x480. the store card uses the first one, and the listing shows the rest. with the thing plugged in, chromium answers CDP at bridgething.local:9222, so Page.captureScreenshot against the kiosk target gives you an exact 800x480 png. the scaffold wraps that as bun run shot <app>.

min_libbridgething_version is dotted semver, no leading v. the store installs the newest version the connected device can run.

hosting the settings page

settings is optional and points at the same settings page the bundle ships, hosted on its own. a companion that has it fetches the page over the internet instead of pulling it off the device, which matters most on a slow bluetooth link.

sha256 must be the digest of the bytes in the bundle. the device reports that digest itself, and the companion only uses your hosted copy when the two agree, so a mismatch is not a trust problem: it just falls back to the device. that also means a bundle pushed straight to a device during development always uses the link, which is what you want while iterating.

native extensions

a bundle with an extension declares it in manifest.json:

{
  "extension": {
    "entry": "extension/desktop.mjs",
    "permissions": ["all"],
    "api": 1
  }
}

and the catalog entry carries a copy:

{
  "extension": {
    "desktop": true,
    "permissions": ["all"]
  }
}

desktop is always true. permissions here are the deno descriptors from the bundle manifest.

an app with any version that declares extension must set source to a github.com repo url people can open. a catalog that breaks the rule fails to load.

permissions

app permissions go in versions[].permissions. declare every one you use. geo gates geo.watch and geo.getOnce, and net.proxy gates the socks proxy. the store shows the rest on the install screen.

the desktop app shows the catalog copy at install and refuses a bundle that asks for anything outside the set the user confirmed. all and a bare kind widen, so net covers net:discord.com.

keep the catalog list a superset of the bundle manifest's. the desktop app launches deno with the descriptors from the bundle manifest.

serve it with cors

your catalog url and every download.url must send Access-Control-Allow-Origin: *.

Access-Control-Allow-Origin: *
Accept-Ranges: bytes
Access-Control-Expose-Headers: content-length, content-range, etag, accept-ranges

github raw and github pages already send it.

if you are writing a client

emit versions[] newest-first by released_at. sort before you pick.

// correct: sort, then pick
const newest = entry.versions
  .filter(v => compatible(v.min_libbridgething_version, device.libbridgethingVersion))
  .sort((a, b) => Date.parse(b.released_at) - Date.parse(a.released_at))[0];

// wrong: trusts the order the source emitted
const newest = entry.versions[0];

getting listed

the directory is how people find you without the url.

submit yours on the apps page. the url must be reachable over https, parse as catalog.v1, and send Access-Control-Allow-Origin.

a new source is unreviewed: named under a collapsed section, apps hidden. a reviewer moves it to listed, which shows its apps under community apps and offers it in the phone app. vouched for puts your apps next to the official ones.

a source that goes down is reported and stays in the directory. apps installed from it keep working.

two sources listing the same app

an installed app takes updates from the source it came from. another catalog listing the same id shows as "also available from".