@supercmdk/react
v0.4.1
Published
A React command palette with scoped commands and an on-device tool-calling Agent.
Maintainers
Readme
SuperCmdK
SuperCmdK adds a cmdk command palette to React applications. You can register commands and typed JavaScript tools at the app or route level. Agents, voice clients, accessibility controls, and automation adapters can invoke the same tools through one policy and validation layer.
The React package includes:
- cmdk primitives and a styled
CommandPalette; - global and route-scoped command registration;
- a React-free tool registry with JSON Schema validation;
- a model-independent Agent API;
- a Cactus Needle adapter that runs inference in a Web Worker.
The optional @supercmdk/needle package supplies the pinned model and WASM files.
Install
bun add @supercmdk/reactYour app must provide React and React DOM 18 or 19. SuperCmdK uses ESM. Import the optional stylesheet once:
import "@supercmdk/react/styles.css";Quickstart with Needle
Install the React library and the optional package that contains the pinned Needle model and WASM runtime:
bun add @supercmdk/react @supercmdk/needleThe Needle package is about 14 MB on disk. It keeps the model out of @supercmdk/react and out of your initial JavaScript bundle. Your bundler emits the model as a separate asset, and SuperCmdK fetches it during browser idle time.
import { CommandPalette, SuperCmdKProvider, type Tool } from "@supercmdk/react";
import { createNeedleEngine } from "@supercmdk/needle";
import "@supercmdk/react/styles.css";
const tools: Tool[] = [
{
name: "create_task",
description: "Create a task in the current project",
parameters: {
type: "object",
properties: {
title: { type: "string" },
},
required: ["title"],
additionalProperties: false,
},
execute: ({ title }) => tasks.create({ title: String(title) }),
},
];
export function App() {
return (
<SuperCmdKProvider
tools={tools}
agent={{ engine: createNeedleEngine }}
>
<YourRoutes />
<CommandPalette
onAgentResult={(result) => console.log(result)}
onError={(error) => console.error(error)}
/>
</SuperCmdKProvider>
);
}createNeedleEngine supplies package-relative URLs for needle.js, needle.wasm, and needle2.cact. Vite, webpack, and other bundlers that support new URL(..., import.meta.url) copy those assets into the build automatically. Configure your host to serve .wasm as application/wasm. The Worker fetches the full model, so byte-range support is not required.
SuperCmdK loads and compiles Needle in a Web Worker during browser idle time. Set agent={{ engine: createNeedleEngine, preload: false }} to defer that work until the first Agent request.
The companion package includes unmodified Needle artifacts from a checksum-verified revision. Its adapter is MIT; the bundled model and WASM artifacts are Apache-2.0. The package includes the upstream license and revision metadata.
Command palette
Mount the provider and palette near your app root. Commands on the provider remain available across routes.
import { CommandPalette, SuperCmdKProvider } from "@supercmdk/react";
import "@supercmdk/react/styles.css";
export function App() {
return (
<SuperCmdKProvider
commands={[
{
id: "home",
label: "Go home",
group: "Navigation",
keywords: ["dashboard"],
shortcut: ["⌘", "H"],
run: () => location.assign("/"),
},
]}
>
<Routes />
<CommandPalette />
</SuperCmdKProvider>
);
}Press Cmd+K on macOS or Ctrl+K on other platforms. Use open and onOpenChange when your application owns the palette state.
Route-scoped commands
useCommandChoice and useCommandChoices register commands for the lifetime of a component. Pass dependencies as you would to useEffect.
import { useCommandChoice, useCommandChoices } from "@supercmdk/react";
function CustomerPage({ customerId }: { customerId: string }) {
useCommandChoice(
{
id: "archive-customer",
label: "Archive this customer",
group: "Customer",
run: async ({ close }) => {
await archiveCustomer(customerId);
close();
},
closeOnSelect: false,
},
[customerId],
);
useCommandChoices(
[{
id: "copy-id",
label: "Copy customer ID",
run: () => navigator.clipboard.writeText(customerId),
}],
[customerId],
);
return <CustomerDetails id={customerId} />;
}A route command overrides a provider command with the same id. SuperCmdK restores the provider command when the component unmounts.
Build a custom menu with the re-exported Command, flat Command* primitives, defaultFilter, and useCommandState APIs from cmdk.
Tools
Tools do not depend on the palette or an Agent. Register app-wide tools on SuperCmdKProvider. Register route tools with useTool or useTools.
import { useTools, type Tool } from "@supercmdk/react";
const messagingTools: Tool[] = [
{
name: "find_contact",
description: "Find a contact by name",
parameters: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
additionalProperties: false,
},
execute: ({ name }, { signal, source }) =>
contacts.findByName(String(name), { signal, source }),
},
];
function MessagingPage() {
useTools(messagingTools, []);
return <Inbox />;
}A route tool overrides an app-wide tool with the same name. SuperCmdK restores the app-wide tool on unmount.
Invoke tools
useSuperCmdK exposes the current tool snapshot and the shared invocation path:
const { tools, invokeTool } = useSuperCmdK();
const result = await invokeTool(
"find_contact",
{ name: "Ada" },
{
source: "voice",
metadata: { transcript: "Find Ada" },
},
);SuperCmdK validates arguments against each tool's JSON Schema without coercion. Invalid arguments do not reach the handler.
Policies and confirmation
Set one provider policy for Agent, voice, and application calls:
<SuperCmdKProvider
tools={globalTools}
toolPolicy={{
authorize: ({ tool, context }) =>
permissions.canUse(tool.name, context.source),
confirm: ({ tool }) => window.confirm(`Allow ${tool.name}?`),
}}
>
<App />
</SuperCmdKProvider>Tools can set annotations for readOnly, destructive, idempotent, and requiresConfirmation. SuperCmdK rejects a tool marked requiresConfirmation when you omit toolPolicy.confirm. Your confirm callback owns the approval UI, so it can use a browser prompt, an application modal, or a server-side approval flow.
The registry controls which handlers clients can call. It does not sandbox handler code. A handler can use the same browser credentials and capabilities as the rest of your application.
Use tools outside React
Import the React-free registry from @supercmdk/react/tools:
import { createToolRegistry } from "@supercmdk/react/tools";
const registry = createToolRegistry({ tools: globalTools });
const unsubscribe = registry.subscribe(() => {
voice.setTools(registry.getSnapshot());
});
const result = await registry.invokeTool(
"find_contact",
{ name: "Ada" },
{ source: "voice" },
);Pass the registry to <SuperCmdKProvider toolRegistry={registry}> when a voice client, Worker, or automation adapter needs the route-scoped tools that React components register. The /tools entry point imports neither React nor Needle.
Agent
The AgentEngine interface separates tool orchestration from model inference. @supercmdk/needle supplies the bundled Cactus Needle adapter; you can instead provide any engine that implements the interface.
import { createNeedleEngine } from "@supercmdk/needle";
<SuperCmdKProvider
agent={{
engine: createNeedleEngine,
systemPrompt: () =>
`date: ${new Date().toISOString()}; locale: en-US`,
}}
>
<App />
<CommandPalette
agentRunOptions={{ maxSteps: 8, confidenceThreshold: 0.75 }}
onAgentResult={(result) => console.log(result)}
/>
</SuperCmdKProvider>SuperCmdK schedules engine preload after page load during browser idle time. NeedleWasmEngine downloads and compiles Needle inside needle.worker.js, away from React's thread. A prompt submitted during preload waits for the same request.
Set preload: false to load the engine on demand:
<SuperCmdKProvider agent={{ engine: createEngine, preload: false }}>
<App />
</SuperCmdKProvider>Call preloadAgent() from useSuperCmdK when user intent gives you a better preload signal.
Chain tools
The Agent receives the current tool schemas and invokes each handler through the tool registry. It can feed one result into the next call. For example, a request to “find Ada and tell her hello” can call find_contact, then pass the returned contact ID to send_message.
import { useTools } from "@supercmdk/react";
function MessagingPage() {
useTools(
[
{
name: "find_contact",
description: "Find a contact by name",
parameters: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
additionalProperties: false,
},
execute: ({ name }) => contacts.findByName(String(name)),
},
{
name: "send_message",
description: "Send a message to a contact ID",
parameters: {
type: "object",
properties: {
contactId: { type: "string" },
body: { type: "string" },
},
required: ["contactId", "body"],
additionalProperties: false,
},
annotations: { destructive: true, requiresConfirmation: true },
execute: ({ contactId, body }) =>
messages.send(String(contactId), String(body)),
},
],
[],
);
return <Inbox />;
}SuperCmdK runs calls in order and caps a chain at eight turns unless you set maxSteps.
Run without the palette
const { runAgent } = useSuperCmdK();
const result = await runAgent("dim the living room lights", {
confidenceThreshold: 0.8,
systemPrompt: "date: 2026-08-13; locale: en-US",
maxSteps: 4,
});Use provider toolPolicy.confirm for confirmation shared across clients. AgentRunOptions.confirm remains available for code written against 0.1. SuperCmdK returns unknown tools, invalid arguments, denied calls, and handler failures to the Agent as normalized errors. Pass an AbortSignal to cancel a run.
Lower-level API
Import the engine adapter and chain runner from the Agent entry point:
import { NeedleWasmEngine, runAgentChain } from "@supercmdk/react/agent";NeedleWasmEngine implements preload, initialize, complete, reset, and dispose. runAgentChain(engine, input, tools, options) accepts Needle or another AgentEngine implementation.
Needle's WASM ABI supports one session per Worker. SuperCmdK serializes runs within each provider and sends the active tool schemas before each run.
Performance
SuperCmdK keeps the Agent runtime in a separate chunk. The Worker handles model download, WASM compilation, model loading, and inference. Palette-only applications do not load the Agent code.
Tool handlers run in your application context. Move CPU-heavy work into a Worker or server API. When a command closes the palette, SuperCmdK waits for the close to paint before it calls the command handler.
Migrating from 0.1
Version 0.2 keeps the 0.1 Agent tool names as deprecated aliases:
| 0.1 API | 0.2 API |
| --- | --- |
| AgentTool | Tool |
| AgentToolContext | ToolContext |
| AgentToolSchema | ToolSchema |
| useAgentTool | useTool |
| useAgentTools | useTools |
Provider tools, runAgent, runAgentChain, AgentEngine, and @supercmdk/react/agent imports still work. Version 0.2 rejects invalid tool arguments before it calls a handler.
Demo
Open https://nicholaszolton.github.io/SuperCmdK/ to try the palette and Needle tool chain. Use Test approval or ask the Agent to “delete production” to run a simulated destructive tool. Approving or denying it changes only the demo activity log. The demo source lives in this repository.
Run it on your machine with Tilt and portless:
mise trust
mise install
portless trust # one-time machine setup
mise run devThe demo resolves Needle from the local @supercmdk/needle workspace package, so it needs no separate model download. Tilt prints the demo URL, often https://web.supercmdk.localhost:1355, and cleans up when you press Ctrl-C.
Development
bun install
bun run check
bun run test
bun run buildReleases
Release Please reads Conventional Commits on main and updates one release PR:
fix:requests a patch release.feat:requests a minor release.feat!:or aBREAKING CHANGE:footer requests a major release.
Merge the release PR to update package.json, packages/needle/package.json, bun.lock, and CHANGELOG.md. Release Please then creates the vX.Y.Z GitHub Release. .github/workflows/publish.yml publishes @supercmdk/react and @supercmdk/needle to npm through OIDC trusted publishing. Keep version edits and release tags in this flow.
