feat(tui): integrations hub - #328
Conversation
Adds an `Integrations` tab: the single place an operator puts third-party credentials. Composio is its first tenant; Telegram and Discord follow in their own PRs. Before this, every integration grew its own surface -- Telegram had a tab, LLM providers had a wizard, Composio had nothing -- so "where do I put my key" required already knowing which kind of thing a given service was. An integration declares itself as a descriptor: id, label, summary, docs URL, credential fields, and a status() projection. The hub renders any descriptor without a bespoke pane, so adding one is a descriptor file plus a registry line rather than a new TUI slice. Reachable as the `integrations` Manage tab, `/integrations`, or ctrl+g y (`i` was already Import's chord). Design notes: - Credentials live in <stateDir>/.env via setDotenvKey (0600, atomic), never in config.json, and process.env is updated in the same breath so the running process sees a new key without a restart. - A secret is never rendered in the clear except in the edit buffer being typed: values are masked and length-capped, starting an edit opens an empty buffer rather than seeding the stored value, and the buffer is cleared when the action settles. - Edit mode swallows the whole keyboard. `d`, `e` and `r` are bindings on this tab; inside the editor they are key material, and a paste that silently triggered "clear field" halfway through would be both baffling and destructive. - A re-sync clamps the cursor instead of resetting it, so a background refresh cannot move the operator's place mid-edit. - Changing the Composio key drops the cached tool-router session: a session belongs to the key that created it, and reusing it across a key swap would keep talking to the old account. - Field env vars are pinned to the shape setDotenvKey accepts, which would otherwise throw in front of the operator at save time. Slash-palette ranks from 26 up shift by one to seat `/integrations` after `/mcp`; the pinned palette and menu-children lists move with it. Verified against a live Composio key: with no key the tab reads "not configured" and the runtime has no MCP servers; saving the key mounts the server and flips the badge to "connected" with the value masked, no restart; a non-ASCII key is refused at entry with a readable message; clearing unmounts the server and returns the tab to "not configured".
Registering a MenuPlaceNode with a `slash` puts the command in the palette and binds its ctrl+g chord, but execution is a separate switch: without a case, typing /integrations answered "command /integrations not yet implemented" while still advertising itself in the palette. Found by driving the real TUI rather than by a test, so this also adds the generic guard that would have caught it: every command SLASH_COMMANDS advertises is dispatched and none falls through to "not yet implemented".
|
Added a commit after driving the real TUI: Registering a So the fix comes with the generic guard: every command Screenshot of the working tab (real TUI, 120×40 PTY, live Composio key): and the detail view, key masked: |
What
A new Integrations tab — the single place an operator puts third-party credentials. Composio is its first tenant; Telegram and Discord follow in their own PRs.
Reachable as the
integrationsManage tab,/integrations, or ctrl+g y (iwas already Import's chord).Why a hub rather than a Composio pane
Every integration so far grew its own surface: Telegram has a tab, LLM providers have a wizard, Composio had nothing. "Where do I put my key" required already knowing which kind of thing a given service was.
An integration now declares itself as a descriptor — id, label, summary, docs URL, credential fields, and a
status()projection. The hub renders any descriptor without a bespoke pane, so adding an integration is a descriptor file plus one line in the registry, not a new TUI slice.Design notes
<stateDir>/.env, viasetDotenvKey(0600, atomic), never inconfig.json— andprocess.envis updated in the same breath, so the running process sees a new key without a restart. Field env vars are pinned to the shapesetDotenvKeyaccepts, which would otherwise throw in front of the operator at save time.d,e,rare bindings on this tab; inside the editor they are key material. A paste that silently triggered "clear field" halfway through would be both baffling and destructive.IntegrationsOrchestratoris the only module touching credential storage or the liveMcpManager; the reducer and component are pure.Slash palette
/integrationsseats after/mcp, so ranks from 26 up shift by one. The pinned palette list and the two menu-children lists move with it — the repo's own uniqueness invariants (chord, rank, slash name) caught the initiali/26 collisions.Testing
npm run lintandnpm testgreen (7205 tests). 48 new tests across six files: secrets round-trip, registry invariants, the Composio descriptor, the reducer, key bindings, and panel render.Verified against a live Composio key on a scratch state dir, driving the real orchestrator against a real runtime:
not configured— "no key — Composio tools are not loaded"[]connected, value shown as•••…["composio"]ak_“fancy”not configured[]All of it live — no restart at any point.