eleven / messenger Public

bot: Eleven can explain Integrations — including today's live failures (#525)

The knowledge base's connected-services blurb predated the registry (it still
taught "/connect <token>" and wouldn't name services). Rewritten so Eleven can
answer "what happened?" for the paths a real member just walked: how
connecting works (authorize on the provider's site or paste a scoped token;
the key stays locked to the member), how bot proposals work (chips; the tap
is the consent; short-lived sealed grants; results are notices, data lives in
the service's app; chips don't render in the native feed yet), GitHub's
two-consent gotcha (authorize ≠ install, per account AND per org), and a
plain-language decoder for every common error — the 404-means-not-installed
trap chief among them.

DNSimple graduates from the knowledge leak-guards' banned-terms list (both
twins): it was listed as operator tooling, but it's now a public product
feature — the catalog names it, members see its app. hetzner/miren/1password
stay banned.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell c20195efa29de2c7ddb665c0484789b1462cf6bd parent da3c078
3 files changed, +45 −11
  • internal/bot/eleven_test.go +1 −1
  • internal/bot/knowledge.md +43 −9
  • test/knowledge.test.mjs +1 −1
diff --git a/internal/bot/eleven_test.go b/internal/bot/eleven_test.go
index f4e5213..09ed3c0 100644
--- a/internal/bot/eleven_test.go
+++ b/internal/bot/eleven_test.go
@@ -47,7 +47,7 @@ func TestElevenKnowledge(t *testing.T) {
forbidden := map[string]string{
`\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b`: "an IP address",
`(?i)run\.garden|elevenmessenger\.com|paulca`: "a real hostname/instance",
- `(?i)hetzner|\bmiren\b|dnsimple|1password`: "an operator/tooling name",
+ `(?i)hetzner|\bmiren\b|1password`: "an operator/tooling name",
`(?i)\.p8\b|admintok|LCHAT_ADMIN_TOKEN|Bearer\s+\w`: "a key/token",
`(?i)BEGIN (?:EC |RSA |OPENSSH )?PRIVATE KEY`: "a private key",
`(?i)ssh\s+root@|root@\d`: "an SSH target",
diff --git a/internal/bot/knowledge.md b/internal/bot/knowledge.md
index ee2d4d7..9752db5 100644
--- a/internal/bot/knowledge.md
+++ b/internal/bot/knowledge.md
@@ -457,15 +457,49 @@ A personal app-thread is presented one of two ways, and **the app chooses**:
it. Everything is encrypted into your own thread; the server never learns a URL,
a note, or who you saved it from.
-- **Connected services** (`/connect <service> <token>`, `/register <domain>`,
- `/list`) — an early feature for wiring up a third-party account (a domain
- registrar is the worked example) *without* handing the server a usable
- credential. Your token is encrypted to you (the server holds only ciphertext),
- and a bot that acts on your behalf gets only a **short-lived grant** sealed to
- it — never your long-lived secret at rest. It's honest about the limit: whoever
- holds a token *at the moment of a call* sees it, so this bounds the exposure
- window rather than achieving zero-knowledge. (This is a newer/limited feature;
- say so, and confirm before anything that spends money or changes the world.)
+- **Connected services (Integrations)** — wire up a third-party account
+ (GitHub, DNSimple, Stripe, HubSpot) *without* handing the server a usable
+ credential. Open **Integrations** (+ New chat → Integrations): connect by
+ authorizing on the provider's own site where offered (GitHub, DNSimple), or
+ by pasting a scoped token. Either way the key is locked to *you* — the
+ server stores a box only you can open. Once connected, a service can
+ install its own app (GitHub's repositories and issues; DNSimple's domains),
+ and the connected row's **Open** button goes straight there.
+
+ **How a bot acts on a connection:** you (Eleven) can *propose* an operation
+ — it appears as a **tappable chip** under your reply — but you can never
+ run one yourself, see the member's credential, or fetch anything directly.
+ The tap is what runs it: at that moment the member's device hands the bot a
+ **short-lived grant** sealed to it (minutes-long, single-purpose, read- or
+ write-scoped) — never the long-lived secret at rest. Anything that spends
+ money or changes the world carries its exact confirmation sentence on the
+ chip. Results come back as a short notice; the browsable data lives in the
+ service's own app. Be honest about the limit: whoever holds a token *at the
+ moment of a call* sees it, so this bounds the exposure window rather than
+ achieving zero-knowledge. (Chips currently render on the web and in webview
+ chats — not yet in the native Mac/iOS message feed.)
+
+ **GitHub specifically — the two-consent gotcha.** GitHub separates
+ *authorizing* (the member lets Eleven Messenger act with their identity —
+ that's the Connect button) from *installing* (choosing which repositories
+ the app may see — done at github.com/apps/eleven-messenger). Both are
+ needed, **per account or organization**: installing on a personal account
+ doesn't cover an org's repos — the org needs its own installation.
+
+ **What the common errors mean** (explain these plainly when asked):
+ - *"status 404: Not Found"* on a repo that definitely exists → the app
+ isn't installed on that repo's account or org. Fix: install (or edit repo
+ access) at github.com/apps/eleven-messenger, then try again.
+ - *"No repositories visible to this token"* → connected fine, but the app
+ is installed on no repositories yet — same fix.
+ - *"status 401"* / *"That authorization expired"* → the token expired or
+ was revoked; tokens usually refresh themselves mid-operation, so a
+ persistent 401 means reconnect via Integrations.
+ - *"That authorization is read-only"* → the tapped operation writes, but
+ the grant was read-scoped — propose it afresh so a new tap can consent.
+ - *"you haven't connected <service> yet"* → connect it in Integrations first.
+ - *"the bot didn't respond"* → the instance's helper isn't running; that's
+ one for the space's admin.
Members can build their own mini-apps — each is one self-contained file, no
build step — but that's a developer topic; point curious people at the fact that
diff --git a/test/knowledge.test.mjs b/test/knowledge.test.mjs
index c3a5aea..0e39fef 100644
--- a/test/knowledge.test.mjs
+++ b/test/knowledge.test.mjs
@@ -30,7 +30,7 @@ test("the knowledge base leaks NO operational secrets", () => {
const forbidden = [
[/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/, "an IP address"],
[/run\.garden|elevenmessenger\.com|paulca/i, "a real hostname/instance"],
- [/hetzner|\bmiren\b|dnsimple|1password|\bop\b\s+(?:read|item)/i, "an operator/tooling name"],
+ [/hetzner|\bmiren\b|1password|\bop\b\s+(?:read|item)/i, "an operator/tooling name"],
[/\.p8\b|J5NDF5|ZKYQ9|9X6NKTF|admintok|LCHAT_ADMIN_TOKEN|Bearer\s+\w/i, "a key/token"],
[/BEGIN (?:EC |RSA |OPENSSH )?PRIVATE KEY/i, "a private key"],
[/ssh\s+root@|root@\d/i, "an SSH target"],