eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

πŸ€– The integrations registry

One JSON manifest per third-party service. A manifest is the WHOLE of what an integration can do on an Eleven Messenger instance: how a member authenticates to the service, the one API host its operations may touch, and the closed list of operations β€” each marked read or write, writes each carrying the confirmation line a person taps before it runs. An operation that isn't in the manifest cannot be expressed, the same fail-closed palette rule as bot recipes (docs/bot-recipes.md).

This directory is destined to become its own public repository (elevenmessenger/integrations) so integrations can be added β€” and audited β€” without touching the messenger codebase: adding one is a PR with a manifest (plus, for OAuth services, registering the OAuth app on the connect broker, which is where client secrets live β€” docs/connected-services.md). Until that split, it lives here and is embedded into the binaries (registry.go), with the broker serving it at GET /registry so web clients read the same catalog.

Manifest schema (v1)

{
  "schema": 1,
  "id": "github",                    // lowercase slug, unique
  "name": "GitHub",
  "category": "development",         // catalog grouping
  "auth": {
    "kind": "pat",                   // "pat" | "oauth" | "both"
    "base": "https://api.github.com",// the ONLY host ops may reach (executor-pinned)
    "authorization": "Bearer",       // Authorization: <scheme> <token>; "none" sends the bare token (Linear)
    "headers": { "Accept": "…" },    // extra headers on every call
    "pat_help": "…",                 // where a member mints a scoped token
    "oauth": { "authorize_url": "…", "scopes": ["…"] }  // when the broker has an app
  },
  "ops": [
    {
      "id": "repos.list",            // namespace.verb, unique within the manifest
      "label": "List your repositories",
      "access": "read",              // "read" | "write" β€” grants are scoped to this
      "method": "GET",
      "path": "/user/repos?per_page=20",   // {arg} placeholders allowed
      "args": { "owner": { "required": true } },  // string args only; validated
      "body_json": { "title": "{title}" },        // JSON body template ({arg} in leaves)
      "encoding": "json",            // "json" (default) | "form" (Stripe-style)
      "confirm": "Open β€œ{title}”?",  // REQUIRED on writes: the consent chip's line
      "pick": {                      // response projection β€” what may flow BACK
        "root": "data",              // descend into an envelope first (optional)
        "fields": ["full_name", "user.login"]  // keep only these dot-paths per object
      }
    }
  ]
}

pick makes the response as reviewable as the request: provider APIs answer with far more than an app needs (a GitHub repo object is ~100 fields, several KB), and an op result travels inside one end-to-end-encrypted message with a hard size cap β€” so the manifest declares the fields that may flow back, and the executor drops everything else before the result ever leaves the sidecar. Applied to the object at root (or to each object of the array there); nested paths come back flattened ("user.login": "octocat"). An op without pick returns the raw body, still subject to the size cap.

Rules the loader enforces (fail closed β€” a bad manifest never half-loads):

  • id/op ids well-formed; op ids unique; access ∈ read/write; every write has a confirm template; base is https; methods ∈ GET/POST/PUT/PATCH/DELETE.
  • {arg} placeholders in path/body_json/confirm must name declared args.
  • Arg values are strings, bounded, and escaped into the URL β€” a value can never break out of its path segment or query slot.
  • pick (when present) names at least a root or one field, all well-formed dot-paths.

Trust model

The manifest is reviewed CODE (this registry), never user content. The executor pins every request to auth.base's host, so a manifest op can't be steered elsewhere. Member credentials stay client-sealed exactly as before (docs/connected-services.md): a bot receives a short-lived grant sealed to its own key, now carrying access: "read" | "write" β€” a read grant cannot run a write op, and grants are per-bot by construction (sealed to one keypair). Writes additionally require the member's tap on a confirmation chip naming the exact operation ("Register blip.com β€” this costs real money").