npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stevysmith/agentk

v0.3.1

Published

A command palette for the agentic web — forked from cmdk with WebMCP primitives

Downloads

336

Readme

agentk

A command palette for the agentic web. Extends cmdk with tool execution, auto-generated forms, WebMCP registration, and human-in-the-loop agent mode.

Define your tools once as JSON Schema. Users browse and execute them from a command palette. AI agents discover them via WebMCP. Both share the same interface — with the human always in control.

Install

npm install @stevysmith/agentk

Use

import { Command } from '@stevysmith/agentk'

const tools = [
  {
    name: 'search',
    label: 'Search Products',
    description: 'Find products by keyword',
    inputSchema: {
      type: 'object',
      properties: {
        query: { type: 'string', description: 'Search query' },
      },
      required: ['query'],
    },
  },
]

async function executeTool(name: string, params: Record<string, any>) {
  // your app logic
}

const App = () => {
  const [open, setOpen] = React.useState(false)

  return (
    <Command.Dialog
      open={open}
      onOpenChange={setOpen}
      tools={tools}
      onToolExecute={executeTool}
      label="Command Menu"
    >
      <Command.Input placeholder="Type a command or search..." />
      <Command.List>
        <Command.Group heading="Tools">
          {tools.map((tool) => (
            <Command.Tool key={tool.name} tool={tool} />
          ))}
        </Command.Group>
        <Command.Empty>No results found.</Command.Empty>
      </Command.List>
      <Command.ToolForm />
      <Command.ToolResult />
    </Command.Dialog>
  )
}

When a user selects a tool, agentk transitions through a built-in lifecycle: browse → form → executing → result. Forms are generated automatically from inputSchemaenum fields render as dropdowns, number fields with minimum/maximum render as sliders, and string fields render as text inputs.

With agent mode

Add the agent prop and three more primitives to enable natural language intent → tool execution:

<Command.Dialog
  open={open}
  onOpenChange={setOpen}
  tools={tools}
  onToolExecute={executeTool}
  agent={{
    provider: 'anthropic',
    endpoint: '/api/agent',
    requireApproval: true,
  }}
  label="Command Menu"
>
  <Command.Input placeholder="Type a command or search..." />
  <Command.List>
    <Command.Group heading="Tools">
      {tools.map((tool) => (
        <Command.Tool key={tool.name} tool={tool} />
      ))}
    </Command.Group>
    <Command.Empty>No results found.</Command.Empty>
  </Command.List>
  <Command.AgentHint />
  <Command.Approval />
  <Command.ToolForm />
  <Command.ToolResult />
  <Command.ActivityFeed />
</Command.Dialog>

When the user types a query that doesn't match any tool, AgentHint appears prompting them to press Enter. The query is sent to the LLM, which returns a plan of tool calls. The user reviews and approves the plan in Approval before any tools execute.

Parts and styling

All parts forward props and refs to an appropriate element. Each part has a specific data-attribute that can be used for styling.

Command [cmdk-root]

Root component. Inherits the full cmdk API and adds the following props:

| Prop | Type | Description | |------|------|-------------| | tools | AgentKToolDef[] | Tool definitions to register in the palette | | onToolExecute | (name, params) => Promise<string \| Record> | Called when a tool is executed. Return a string for text display, an object for JSON. | | onToolResult | (name, result) => void | Called after successful execution | | onToolError | (name, error) => void | Called on execution failure | | onModeChange | (mode) => void | Called when mode changes | | agent | AgentKAgentConfig | LLM agent configuration (omit to disable) | | onAgentPlan | (plan) => void | Called when the LLM returns a plan | | onAgentApprove | (plan) => void | Called when user approves a plan | | onAgentReject | (plan) => void | Called when user rejects a plan |

All standard cmdk props (value, onValueChange, filter, shouldFilter, loop, label) are also supported.

The root element exposes data-agentk-mode reflecting the current state machine mode. Use it for mode-aware styling:

/* Hide tool list when a form, result, or execution is active */
[data-agentk-mode="form"] [cmdk-list]      { display: none; }
[data-agentk-mode="executing"] [cmdk-list]  { display: none; }
[data-agentk-mode="result"] [cmdk-list]     { display: none; }
[data-agentk-mode="planning"] [cmdk-list]   { display: none; }
[data-agentk-mode="approval"] [cmdk-list]   { display: none; }

Possible values: browse, form, executing, result, planning, approval. Works alongside data-agentk-entering and data-agentk-exiting for transition animations.

Dialog [cmdk-dialog] [cmdk-overlay]

Composes Radix UI's Dialog. Props are forwarded to Command.

<Command.Dialog open={open} onOpenChange={setOpen}>
  ...
</Command.Dialog>

Input [cmdk-input]

Search input. All props forwarded to the underlying input element.

<Command.Input placeholder="Search tools..." />

List [cmdk-list]

Contains Tool items and groups. Animate height using the --cmdk-list-height CSS variable.

[cmdk-list] {
  height: var(--cmdk-list-height);
  transition: height 100ms ease;
}

Tool [cmdk-item] [data-agentk-tool]

Renders a selectable tool item. Selecting it transitions to the form view.

// Default rendering: icon + name + description
<Command.Tool tool={tool} />

// Custom rendering: children fully replace the default layout
<Command.Tool tool={tool}>
  <MyCustomIcon />
  <div>
    <strong>{tool.label}</strong>
    <p>{tool.description}</p>
  </div>
</Command.Tool>

When children are omitted, the default layout renders data-agentk-tool-icon, data-agentk-tool-name, and data-agentk-tool-description elements for styling. When children are provided, only your children render.

The tool prop is an AgentKToolDef:

type AgentKToolDef = {
  name: string
  label?: string           // Falls back to humanized name
  description?: string
  inputSchema?: {          // JSON Schema for parameters
    type: 'object'
    properties: Record<string, {
      type: string
      description?: string
      enum?: string[]      // → renders dropdown
      minimum?: number     // → renders slider (with maximum)
      maximum?: number
      default?: any
    }>
    required?: string[]
  }
  icon?: React.ReactNode
  keywords?: string[]      // Aliases for fuzzy matching
}

ToolForm [data-agentk-form]

Auto-generates a parameter form from the active tool's inputSchema. Renders when mode is form.

// Default: auto-generated fields
<Command.ToolForm />

// Custom field renderer
<Command.ToolForm
  renderField={(name, schema, value, onChange) => (
    <MyCustomInput value={value} onChange={onChange} />
  )}
/>

// Custom action buttons (Cancel + Execute)
<Command.ToolForm
  renderActions={({ cancel, submit, canSubmit }) => (
    <div>
      <button onClick={cancel}>Cancel</button>
      <button onClick={submit} disabled={!canSubmit}>Run it</button>
    </div>
  )}
/>

Schema type mapping:

| Schema | Rendered as | |--------|-------------| | type: 'string' | Text input | | type: 'string', enum: [...] | Select dropdown | | type: 'number', minimum, maximum | Range slider | | type: 'number' | Number input | | type: 'boolean' | Checkbox |

ToolResult [data-agentk-result]

Displays the result after tool execution. Renders when mode is result.

The return value of onToolExecute controls what is displayed:

onToolExecute={async (name, params) => {
  const data = await myApi(name, params);
  // String → rendered as text in <span data-agentk-result-data="">
  return `Found ${data.length} results`;
  // Object → rendered as formatted JSON in <pre data-agentk-result-data="">
  // return { count: data.length, items: data };
}}
// Default display
<Command.ToolResult />

// Custom result renderer
<Command.ToolResult
  renderResult={(execution) => (
    <div>
      {execution.error
        ? <span>Error: {execution.error}</span>
        : <span>Done in {((Date.now() - execution.startedAt) / 1000).toFixed(1)}s</span>
      }
    </div>
  )}
/>

// Custom dismiss button
<Command.ToolResult
  renderDismiss={({ dismiss }) => (
    <button onClick={dismiss}>Got it</button>
  )}
/>

// Auto-dismiss successful results after 6s
<Command.ToolResult autoDismissAfterMs={6000} />

autoDismissAfterMs only fires for successful results — errors stay visible until the user dismisses them manually. Combine with onModeChange if you need side effects (e.g. navigation) when the panel closes.

The execution object:

type ToolExecution = {
  toolName: string
  parameters: Record<string, any>
  result?: any
  error?: string
  startedAt: number
}

AgentHint [data-agentk-agent-hint]

Appears when the search query doesn't match any tool but an agent is configured. Interactive — clicking, pressing Enter, or pressing Space triggers sendIntent with the current search query. Renders with role="button" and tabIndex={0} for accessibility.

// Default: "Ask the agent" with sparkle icon
<Command.AgentHint />

// Custom content
<Command.AgentHint>
  <span>Let AI handle this</span>
</Command.AgentHint>

Set data-agentk-hint on [cmdk-root] is toggled automatically when the hint is visible, useful for styling the input border:

[cmdk-root][data-agentk-hint] [cmdk-input] {
  border-bottom-color: var(--accent);
}

Approval [data-agentk-approval]

Renders the agent's plan for user review before execution. Shows each proposed tool call with parameters. The user can approve or reject.

// Default display
<Command.Approval />

// Custom renderers
<Command.Approval
  renderSummary={(plan) => <p>{plan.summary}</p>}
  renderCall={(call, index) => (
    <div>{call.toolName}({JSON.stringify(call.parameters)})</div>
  )}
  renderActions={({ approve, reject }) => (
    <div>
      <button onClick={reject}>Cancel</button>
      <button onClick={approve}>Run plan</button>
    </div>
  )}
/>

ActivityFeed [data-agentk-activity]

Shows a timeline of agent activity: intent detection, planning, tool execution, results.

<Command.ActivityFeed maxEntries={20} />

IntentTrigger [data-agentk-intent-trigger]

A Command.Item that triggers sendIntent when selected, instead of the default tool-selection behaviour. Renders identically to other items — same styling, same keyboard navigation.

<Command.IntentTrigger query="summer programs in europe">
  Search Europe
</Command.IntentTrigger>

Must be rendered inside a Command.List. Style with [data-agentk-intent-trigger].

Note: Since IntentTrigger is a Command.Item, it counts as a matching item for filtering. If you want AgentHint to appear when the user types a custom query, place IntentTrigger items alongside other items so they get filtered out by cmdk's fuzzy matching. For agent-only search (no items), use AgentHint directly without IntentTrigger.

Empty [cmdk-empty]

Renders when there are no results. Automatically hidden when AgentHint is visible.

Group [cmdk-group]

Groups items with a heading. Same as cmdk.

<Command.Group heading="Actions">
  <Command.Tool tool={tool} />
</Command.Group>

Separator, Loading

Same as cmdk. See cmdk documentation.

Hooks

useAgentK()

Access agentk state from within the Command tree.

function MyComponent() {
  const ak = useAgentK()

  // Read state
  ak.state.mode        // 'browse' | 'form' | 'executing' | 'result' | 'planning' | 'approval'
  ak.state.activeTool  // current tool or null
  ak.state.parameters  // current form values
  ak.state.execution   // current execution or null
  ak.agentHintVisible  // true when agent hint is showing

  // Actions
  ak.selectTool(tool)
  ak.setParameter('key', value)
  ak.execute()
  ak.reset()
  ak.sendIntent('natural language query')
  ak.approvePlan()
  ak.rejectPlan()
}

useWebMCPTools()

Discover tools registered by other apps on the page via WebMCP.

const { tools, available, refresh, executeTool } = useWebMCPTools()
// tools: AgentKToolDef[] — discovered tools
// available: boolean — whether WebMCP API is present
// refresh: () => void — re-scan for tools
// executeTool: (name, params) => Promise<any>

useCommandState(state => state.field)

Same as cmdk. Access the underlying combobox state.

Agent configuration

The agent prop accepts:

type AgentKAgentConfig = {
  provider: 'anthropic' | 'openai' | 'google' | 'custom'
  apiKey?: string         // For development only — warns in browser
  endpoint?: string       // Proxy URL for production
  model?: string          // Defaults: claude-sonnet-4-20250514, gpt-4o, gemini-2.0-flash
  systemPrompt?: string   // Override the built-in system prompt
  requireApproval?: boolean  // Show Approval before executing (default: false)
  maxCalls?: number       // Max tool calls per plan
  providerFn?: AgentKProvider  // Custom provider function
}

For production, proxy through your own server using the endpoint prop instead of exposing API keys client-side:

agent={{
  provider: 'anthropic',
  endpoint: '/api/agent',
  requireApproval: true,
}}

Custom provider

agent={{
  provider: 'custom',
  providerFn: async (prompt, tools, config) => {
    const res = await fetch('/my-api', {
      method: 'POST',
      body: JSON.stringify({ prompt, tools }),
    })
    return res.json() // { calls: AgentKToolCall[], summary: string }
  },
}}

WebMCP registration

agentk tools use the same JSON Schema format as WebMCP. Register them so AI agents can discover your app. Note that Chrome 150 moved the API from navigator.modelContext to document.modelContext; feature-detect both:

useEffect(() => {
  const mc = document.modelContext ?? navigator.modelContext
  if (!mc) return

  for (const tool of tools) {
    mc.registerTool({
      name: tool.name,
      description: tool.description,
      inputSchema: tool.inputSchema,
      execute: async (params) => {
        const result = await executeTool(tool.name, params)
        return { content: [{ type: 'text', text: JSON.stringify(result) }] }
      },
    })
  }

  return () => {
    for (const tool of tools) {
      mc.unregisterTool(tool.name)
    }
  }
}, [])

Define once, use everywhere — the same tool definitions power the palette UI, the agent, and WebMCP discovery.

Recipes

Consumer search (agent-only, no tool list)

For customer-facing search where users type natural language and get results — no tool browsing needed:

<Command.Dialog
  open={open}
  onOpenChange={setOpen}
  tools={tools}
  onToolExecute={handleExecute}
  agent={{ provider: 'anthropic', endpoint: '/api/agent' }}
>
  <Command.Input placeholder="What are you looking for?" />
  <Command.List>
    {/* No Command.Tool items — agent-only */}
    <Command.AgentHint />
    <Command.Empty>Type to search with AI</Command.Empty>
  </Command.List>
  <Command.ToolResult />
</Command.Dialog>

Use data-agentk-mode to hide the list during execution:

[data-agentk-mode="executing"] [cmdk-list] { display: none; }
[data-agentk-mode="result"] [cmdk-list]    { display: none; }

Add suggested queries with IntentTrigger:

<Command.List>
  <Command.Group heading="Suggestions">
    <Command.IntentTrigger query="popular items">Popular items</Command.IntentTrigger>
    <Command.IntentTrigger query="deals under $50">Deals under $50</Command.IntentTrigger>
  </Command.Group>
  <Command.AgentHint />
</Command.List>

Data-attribute reference

Every part exposes data-attributes you can target from CSS without bundling styles. The cmdk-prefixed attrs come from cmdk; everything data-agentk-* is introduced by agentk.

Root and dialog

| Selector | Where | |---|---| | [cmdk-root] | Top-level element (Command / Command.Dialog) | | [cmdk-root][data-agentk-mode="<mode>"] | Reflects the current state machine mode (browse, form, executing, result, planning, approval) | | [cmdk-root][data-agentk-hint] | Present when AgentHint is showing | | [cmdk-overlay] | Dialog backdrop | | [cmdk-dialog] | Dialog surface | | [cmdk-input] | Search input | | [cmdk-list] | List wrapper (animatable via --cmdk-list-height) | | [cmdk-group], [cmdk-group-heading] | Group + its heading | | [cmdk-empty] | Empty state |

Tool item

| Selector | Where | |---|---| | [cmdk-item][data-agentk-tool="<name>"] | The tool list item | | [data-agentk-tool-icon] | Default icon span (only when tool.icon is set) | | [data-agentk-tool-name] | Default label span | | [data-agentk-tool-description] | Default description span | | [data-agentk-intent-trigger] | A Command.IntentTrigger item |

ToolForm

| Selector | Where | |---|---| | [data-agentk-form] | Form container | | [data-agentk-form-invalid] | Set on the form when validation has errors | | [data-agentk-form-heading] | Header row (icon + title + description) | | [data-agentk-form-title] | Form title | | [data-agentk-form-description] | Form description | | [data-agentk-form-fields] | Wrapper around the field list | | [data-agentk-form-field] | Wrapper around a single field | | [data-agentk-form-field][data-agentk-field-error] | Field whose value is invalid | | [data-agentk-form-label] | Default field label | | [data-agentk-required] | Required-field marker (default *) | | [data-agentk-form-hint] | Field description / hint | | [data-agentk-field-error-message] | Inline validation message | | [data-agentk-form-actions] | Cancel + submit row | | [data-agentk-form-cancel] | Cancel button | | [data-agentk-form-submit] | Submit button |

ToolResult

| Selector | Where | |---|---| | [data-agentk-result] | Result container | | [data-agentk-result][data-agentk-executing] | Set during the executing phase | | [data-agentk-result-loading] | Spinner row during execution | | [data-agentk-progress] | "Step N of M" indicator during a chained plan | | [data-agentk-result][data-agentk-success] | Set when a successful result is shown | | [data-agentk-result][data-agentk-error] | Set when an error is shown | | [data-agentk-result-heading] | Result title row | | [data-agentk-result-body] | Result content wrapper | | [data-agentk-result-data] | The result value (string <span> or JSON <pre>) | | [data-agentk-result-error] | The error message <pre> | | [data-agentk-result-meta] | Meta row (e.g. duration) | | [data-agentk-result-dismiss] | Default dismiss button |

AgentHint

| Selector | Where | |---|---| | [data-agentk-agent-hint] | Hint container (clickable) | | [data-agentk-agent-hint-icon] | Default sparkle icon | | [data-agentk-agent-hint-content] | Label + query wrapper | | [data-agentk-agent-hint-label] | "Ask the agent" label | | [data-agentk-agent-hint-query] | The current search text in quotes | | [data-agentk-agent-hint-kbd] | Default kbd glyph |

Approval

| Selector | Where | |---|---| | [data-agentk-approval] | Approval container | | [data-agentk-approval-summary] | Plan summary line | | [data-agentk-approval-calls] | List of planned tool calls | | [data-agentk-approval-call] | A single planned tool call | | [data-agentk-approval-call-icon] | Tool icon (only when set) | | [data-agentk-approval-call-name] | Tool name | | [data-agentk-approval-call-params] | Parameter chips wrapper | | [data-agentk-approval-param] | Single parameter chip | | [data-agentk-approval-param-value] | Stringified parameter value | | [data-agentk-approval-actions] | Reject + approve row | | [data-agentk-approval-reject] | Reject button | | [data-agentk-approval-approve] | Approve button |

Planning / spinner

| Selector | Where | |---|---| | [data-agentk-planning] | Planning indicator container | | [data-agentk-planning-text] | "Thinking…" label | | [data-agentk-spinner] | The animated spinner element (also reused inside [data-agentk-result-loading]) |

ActivityFeed

| Selector | Where | |---|---| | [data-agentk-activity] | Feed container | | [data-agentk-activity][data-agentk-activity-expanded] | Set when the feed is expanded | | [data-agentk-activity-toggle] | Expand/collapse button | | [data-agentk-activity-status] | Latest status text | | [data-agentk-activity-chevron] | Chevron icon (with data-expanded when open) | | [data-agentk-activity-entry] | A feed entry | | [data-agentk-activity-entry][data-agentk-activity-type="<type>"] | Entry type (tool_start, tool_complete, tool_error, etc.) | | [data-agentk-activity-icon] | Per-entry icon | | [data-agentk-activity-message] | Per-entry message |

FAQ

cmdk compatible? Yes. agentk is a superset of cmdk. Existing cmdk code works unchanged — add tool props when you're ready.

Unstyled? Yes. All components expose data-attributes for styling. No CSS is bundled.

Which LLM providers? Anthropic, OpenAI, and Google (Gemini) are built in. Use provider: 'custom' with providerFn for anything else.

What is WebMCP? A browser API (document.modelContext, formerly navigator.modelContext) for registering tools that AI agents can discover. Chrome has it in origin trial. agentk makes your app WebMCP-ready.

Do I need WebMCP to use agentk? No. The command palette and tool execution work without it. WebMCP registration is opt-in.

Do I need an LLM to use agentk? No. Without the agent prop, agentk is a command palette with tool forms and execution — no AI required.

Auto-generated forms? Yes. inputSchema defines the form. enum → dropdown, number with min/max → slider, string → text input. Override with renderField for custom fields.

Human-in-the-loop? Set requireApproval: true and render <Command.Approval />. The agent's plan is shown to the user before any tools execute.

React 18+ only? Yes. Uses useId and useSyncExternalStore.

React server component? No, it's a client component.

Acknowledgements

Built on cmdk by Paco Coursey. Uses Radix UI primitives.