Connect to other tools
Apps do not talk to each other directly. Each app talks to the hub page through the SDK, and the hub passes the message on. An app connects to other tools in three ways:
| Way | Direction | Example |
|---|---|---|
| Topics | One app to many | Kanban tells every app which session is open |
| Actions | One app to the one app that handles it | Kanban asks Cost to show the cost of a session |
| Capabilities | One app to the hub | Kanban gives the hub the project list |
The connection is between pages only. v1 has no channel between app servers.
Topics
Section titled “Topics”A topic is a fact that any number of apps can receive. Each topic is sticky: the hub keeps its last value and sends it to an app when the app connects. The hub publishes two topics itself:
| Topic | Payload |
|---|---|
project.changed |
{project, encoded, name} when the user picks a project, or null when the user clears it |
theme.changed |
{theme, colorTheme, vars?}. Use bindTheme in place of a subscription |
Apps publish the other topics. For the topics of the built-in tools, such as Kanban’s session.changed, see The built-in tools.
projectis an absolute path.encodedis the form that Claude Code uses for its folder names underprojects/. Use it as it is; do not encode a path yourself.- The hub sends
project.changedandtheme.changedto every app at once. It sends an app topic only to the app on screen; a hidden app gets the last value of each topic it missed when it comes on screen.
Receive a topic
Section titled “Receive a topic”const hub = ClaudeHub.connect();
hub.subscribe('project.changed', (p) => showProject(p?.project ?? null));
hub.subscribe('session.changed', (s) => { if (!s) return clearSession(); showSession(s.sessionId, s.projectName, s.gitBranch);});Subscribe at start, before the page’s load event.
Publish a topic
Section titled “Publish a topic”-
List the topic in the manifest:
"publishes": ["board.selected"]. The name is<noun>.<verb>in lowercase, with your app id as the noun when no other app uses it. -
Publish it:
hub.publish('board.selected', { sessionId, project });
The hub drops a topic that is not in the manifest, and does not send an event back to the app that published it. When the payload has a string project, the hub adds encoded and projectName. Keep the payload small: ids and names. The receiver fetches the rest.
Actions
Section titled “Actions”An action is a request that exactly one app handles. The caller names the action and does not know which app handles it. The hub switches to the handler’s tab and hands it the call.
For the actions of the built-in tools, session.cost, project.plugins and project.memory, see The built-in tools.
Call an action
Section titled “Call an action”if (hub.can('session.cost')) costButton.hidden = false;
costButton.onclick = async () => { const r = await hub.invoke('session.cost', { session: sessionId }); if (!r.ok) console.warn('session.cost', r.reason);};Show the control only when can is true: the handler can be turned off. The result comes when the hub has routed the call, not when the handler has finished.
When the app runs alone, give connect a standalone map so the same call opens the other tool in a new tab:
const hub = ClaudeHub.connect({ standalone: { 'session.cost': (p) => p.session && `http://localhost:3543/?view=detail&session=${p.session}` },});Handle an action
Section titled “Handle an action”-
Declare it in the manifest:
"actions": {"handles": {"board.open": { "params": { "session": "string" }, "url": "?session={session}", "mode": "message" }}} -
With
mode: "message", handle it in the page:hub.handle('board.open', ({ session }) => openSession(session));
When the page is live, the hub sends the call to handle. When it is not, the hub loads the url with the params filled in, so the app must also read them from its query string. With mode: "url", the hub loads the URL each time. When a param of the URL is missing, or the handler has closeGuard on, the hub only switches to the tab, because loading the URL reloads the app. The caller gets {ok: true} as soon as the hub has routed the call, before the handler has finished.
To take over a built-in action, declare the same name and put your app before the built-in tool, or turn the built-in tool off. The hub logs the conflict.
Capabilities
Section titled “Capabilities”A capability is a job for the hub that one app does. See Capabilities for the fields.
projects. The provider answersGET <path>with[{path, modifiedAt}]. The hub calls it for the project palette.terminal. The provider getsCCK_TERMINALandCCK_TERMINAL_TOKENin its environment, the token in its iframe URL as#t=<token>, and answers onhub.terminalToken(). It runs the terminal itself. ItsliveWorkroute answers{sessions: [...]}, and the hub keeps the app’s processes alive while the list is not empty.
The first enabled app in tab order that declares a capability is its provider. Today Kanban provides both. See The built-in tools.
Shortcuts
Section titled “Shortcuts”The SDK forwards the hub’s keys from your page: Ctrl+Alt+P, Ctrl+Alt+W, Ctrl+Alt+A, Ctrl+Alt+Left, Ctrl+Alt+Right and Alt+1 to Alt+N, one for each of the first nine tabs. Every other key stays in your app. Do not use the hub’s keys for your own commands. See Keyboard shortcuts.