@kontourai/station-sdk
v0.4.1
Published
SDK for building Station workspace plugins
Readme
@kontourai/station-sdk
Station is Kontour's local-first agent workspace: you direct agent work, and the gate verdicts, evidence, and trust state stay in the same context as the work. Plugins are how the workspace is extended — a plugin contributes layouts, agents, MCP integrations, providers, and knowledge namespaces to the Station shell.
This package is the client-side SDK those plugins build against: theme-aware UI components, React hooks for agents, chat, navigation and notifications, and typed access to the Station host API.
Installation
npm install @kontourai/station-sdkRequires a bundler (ships TypeScript source)
This package publishes raw TypeScript. Every entry in exports points at a
.ts file under src/; there is no compiled dist/. That is deliberate — the
supported consumer is a Station plugin, whose npm run build calls
buildPlugin() from @kontourai/station-shared/build and bundles the plugin
with esbuild, which reads .ts from node_modules directly.
- Supported: esbuild, Vite, webpack, Rollup, or any TS-aware loader/runtime
(
tsx,ts-node, Bun, Deno). - Not supported today: plain-Node
require()/importof this package without a TS-aware step.
This is a disclosed constraint, not an accident. If you need a precompiled build for a non-bundled runtime, open an issue.
Start from npm
A plugin needs nothing but npm and these two packages — no Station checkout.
mkdir hello-station && cd hello-station
npm init -y
npm pkg set type=module
npm pkg set scripts.build="tsx build.ts"
npm pkg set scripts.dev="tsx build.ts --dev"
npm install @kontourai/station-sdk @kontourai/station-shared
npm install -D tsx @types/react
mkdir srcplugin.json — the manifest Station reads:
{
"name": "hello-station",
"version": "1.0.0",
"sdkVersion": "^0.4.0",
"displayName": "Hello Station",
"description": "A Station layout plugin",
"entrypoint": "src/index.tsx",
"capabilities": ["navigation"],
"permissions": ["navigation.dock"],
"layout": { "slug": "hello-station", "source": "./layout.json" }
}layout.json — the layout the manifest points at:
{
"name": "Hello Station",
"slug": "hello-station",
"icon": "👋",
"tabs": [{ "id": "home", "label": "Home", "component": "hello-station-home" }]
}src/index.tsx — the entrypoint. Export a components map keyed by the tab
component ids in layout.json:
import { type LayoutComponentProps, useNavigation } from '@kontourai/station-sdk';
function Home({ onShowChat }: LayoutComponentProps) {
const { setDockState } = useNavigation();
return (
<section style={{ padding: '1.5rem' }}>
<h1>Hello Station</h1>
<button type="button" onClick={() => { setDockState(true); onShowChat?.(); }}>
Open Chat
</button>
</section>
);
}
export const components = { 'hello-station-home': Home };
export default Home;build.ts — the build. buildPlugin is the same function Station itself runs;
tsx is what lets Node import it from the TypeScript source these packages
ship:
import { buildPlugin } from '@kontourai/station-shared/build';
const mode = process.argv.includes('--dev') ? 'dev' : 'production';
const result = await buildPlugin(process.cwd(), mode);
if (!result.built) console.log('No entrypoint in plugin.json — nothing to bundle.');
else console.log(`Built ${result.bundlePath}`);Then build it:
npm run build # production bundle at dist/bundle.js
npm run dev # dev bundle with inline sourcemapsReact, @tanstack/react-query, and this SDK are supplied by the Station host at
runtime, so buildPlugin externalizes them instead of bundling them.
Load the bundle
Point a running Station instance at the plugin directory:
curl -X POST http://localhost:3141/api/plugins/install \
-H 'Content-Type: application/json' \
-d "{\"source\": \"$PWD\"}"Station copies the plugin to ~/.station/plugins/<name>/, rebuilds it, and
registers its layout; the layout is then available to add to a project. Active
or trusted permissions come back as pendingConsent for you to approve.
UI Components
The SDK provides pre-built, theme-aware components for consistent styling across workspaces.
Button
import { Button } from '@kontourai/station-sdk';
function MyComponent() {
return (
<>
<Button variant="primary" onClick={handleClick}>
Primary Action
</Button>
<Button variant="secondary" size="sm">
Secondary
</Button>
<Button variant="success" loading={isLoading}>
Save Changes
</Button>
<Button variant="ghost" disabled>
Disabled
</Button>
</>
);
}Props:
variant:'primary' | 'secondary' | 'success' | 'ghost'(default:'primary')size:'sm' | 'md' | 'lg'(default:'md')loading:boolean- Shows loading state- All standard button HTML attributes
Pill
import { Pill } from '@kontourai/station-sdk';
function MyComponent() {
return (
<>
<Pill variant="primary">Active</Pill>
<Pill variant="success">Completed</Pill>
<Pill variant="warning">Pending</Pill>
<Pill variant="error">Failed</Pill>
<Pill
variant="default"
removable
onRemove={() => console.log('removed')}
>
Removable Tag
</Pill>
</>
);
}Props:
variant:'default' | 'primary' | 'success' | 'warning' | 'error'(default:'default')size:'sm' | 'md'(default:'md')removable:boolean- Shows remove buttononRemove:() => void- Called when remove button is clicked- All standard span HTML attributes
Hooks
Agent Management
import { useAgents, useAgent } from '@kontourai/station-sdk';
const agents = useAgents();
const agent = useAgent('my-agent');Chat Operations
import { useSendMessage, useCreateChatSession } from '@kontourai/station-sdk';
const sendMessage = useSendMessage();
const createSession = useCreateChatSession();
// Send a message
sendMessage('Hello, agent!');
// Create a new chat session
createSession('my-agent');Navigation
import { useNavigation, useDockState } from '@kontourai/station-sdk';
const { setDockState } = useNavigation();
const [isDockOpen] = useDockState();
// Open chat dock
setDockState(true);Notifications
import { useToast, useNotifications } from '@kontourai/station-sdk';
const { showToast } = useToast();
const { notify } = useNotifications();
showToast('Success!', 'success');
notify({ title: 'New message', message: 'You have a new message' });Tool Invocation
import { callTool, invokeAgent } from '@kontourai/station-sdk';
// Call an MCP tool directly
const result = await callTool('my-agent', 'tool-name', { param: 'value' });
// Invoke agent silently
const response = await invokeAgent('my-agent', 'Do something');Layout Navigation
import { useLayoutNavigation } from '@kontourai/station-sdk';
const { getTabState, setTabState } = useLayoutNavigation();
// Save state
setTabState('my-tab', 'key=value&other=data');
// Restore state
const state = getTabState('my-tab');Theme Variables
All components use CSS variables for theming:
--color-primary- Primary brand color--color-success- Success state color--color-warning- Warning state color--color-error- Error state color--color-bg- Background color--color-bg-secondary- Secondary background--color-text- Primary text color--color-text-secondary- Secondary text color--color-border- Border color
Components automatically adapt to light/dark mode.
Related packages
@kontourai/station-shared—buildPlugin, manifest parsing, and the other runtime helpers.@kontourai/station-contracts— the TypeScript contracts both packages are typed against.
License
Apache-2.0 — see LICENSE.
