MoonshotAI / MoonshotAI/kimi-cli
Feature Request: Extensibility APIs for Community-Built Statusline / HUD Plugins
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 11.4k
- Forks
- 1.3k
- Avg merge
- 9h 47m
- Merged PRs (30d)
- 2
Description
Feature Request: Extensibility APIs for Community-Built Statusline / HUD Plugins
I want to build a HUD plugin myself (like claude-hud). This issue asks for the extension interfaces that would make it possible — not for the Kimi team to build or maintain a HUD.
Motivation
Claude Code's claude-hud shows a persistent status bar at the bottom of the terminal with context usage, live tool activity, running agents, and todo progress. I want to build a similar plugin for Kimi Code CLI.
After reading the source, I found that the current plugin and hooks architecture cannot support this. The HUD needs three things Kimi CLI does not yet provide:
- A persistent-process plugin that lives for the whole session.
- A continuous data stream of internal state (context, tools, agents, todos).
- A way to render text into the bottom toolbar.
This issue proposes concrete API changes to enable community plugins like mine. The HUD itself (colors, layout, progress bars, config UI) stays entirely in userland.
How claude-hud Works
Claude Code --stdin JSON--> claude-hud process --stdout--> terminal statusline
\-- transcript JSONL (tools, agents, todos)
- Plugin runs as a persistent process for the entire session.
- Claude Code pushes JSON to the plugin's stdin (~300 ms interval) with context usage, rate limits, and transcript events.
- Plugin prints formatted text to stdout; Claude Code renders it in the statusline area.
- Plugin registers slash commands like
/claude-hud:configfor user interaction.
What's Missing in Kimi CLI
| Missing | Why it blocks a HUD plugin |
|---|---|
| No persistent-process plugin type | Current plugins spawn a subprocess per AI tool call (PluginTool.__call__ creates asyncio.create_subprocess_exec, runs once, exits). A HUD needs a long-running process. |
| No continuous data stream to plugins | Hooks fire events, but each invocation is an independent shell command with stdin JSON + exit code. No channel streams StatusUpdate-like payloads to a running plugin. |
| No toolbar rendering extension point | _render_bottom_toolbar in prompt.py:2099 is hard-coded. Plugins cannot inject text into the terminal status bar. |
| No plugin-registered slash commands | plugin.json only declares tools (for AI to call). Cannot declare user-facing slash commands. |
| No in-flight tool list | PostToolUse fires after completion. No event signals "tool X is currently executing". |
| No todo state exposure | SetTodoList stores todos in SessionState.todos (session_state.py:44), but this state is not emitted to plugins. |
| No usage/rate-limit data | Searching rate_limit, cost.total_cost_usd in the codebase yields nothing. Not a blocker for a basic HUD, but limits feature parity. |
Proposed API Changes
1. Extend plugin.json schema
Current PluginSpec (plugin/__init__.py:30) only supports "tools".
Proposed additions:
{
"name": "kimi-hud",
"version": "1.0.0",
"type": "statusline",
"command": ["node", "dist/main.js"],
"config_file": "config.json",
"inject": { "api_key": "api_key" },
"slash_commands": [
{ "name": "kimi-hud:config", "description": "Configure HUD" }
]
}
New fields:
"type": enum["tool" (default), "statusline"]."slash_commands": array of{name, description}. Only meaningful for statusline plugins.
2. Persistent-process lifecycle
Current (plugin/tool.py:80):
proc = await asyncio.create_subprocess_exec(...)
stdout, stderr = await proc.communicate(input=params_json.encode())
Process exits after one invocation.
Required: CLI starts the process once per session during KimiCLI.create() (app.py:274, where plugin configs are already refreshed), keeps stdin open, writes JSON Lines on state changes, reads stdout as statusline text, and kills the process on shutdown (shutdown_background_tasks() or similar).
Suggested: new StatuslinePlugin class in src/kimi_cli/plugin/statusline.py.
3. Toolbar rendering hook (prompt.py)
Current (prompt.py:1521):
self._session = PromptSession[str](
...,
bottom_toolbar=self._render_bottom_toolbar,
)
_render_bottom_toolbar (line 2099) is entirely hard-coded.
Options (pick what fits best):
- A — Safest: Allow
Shellto accept an optionalstatusline_provider: Callable[[], str]._render_bottom_toolbarappends the returned string as an extra line below the existing two. Default toolbar untouched. - B — Minimal: Let a plugin override the right span of line 2 (currently
format_context_status(...)). - C — Full delegation: If a statusline plugin is active, skip
_render_bottom_toolbarentirely and render only the plugin's stdout. Maximum control, but plugin must re-implement git/branch/tips if wanted.
I prefer A for backward compatibility.
4. Data stream schema
CLI writes a JSON Line to the plugin's stdin whenever state changes:
{
"event": "statusline_tick",
"session_id": "abc-123",
"cwd": "/home/user/project",
"model": "kimi-for-coding",
"mode": "agent",
"flags": {
"yolo": false,
"afk": false,
"plan_mode": false,
"thinking": true
},
"context": {
"usage_ratio": 0.45,
"tokens_used": 45000,
"tokens_max": 100000
},
"git": {
"branch": "main",
"dirty": true,
"ahead": 0,
"behind": 0
},
"background_tasks": { "bash": 0, "agent": 1 },
"mcp": { "loading": false, "servers": 3 },
"tools_in_flight": [
{ "name": "ReadFile", "call_id": "tc-1", "target": "src/auth.ts" }
],
"agents": [
{ "name": "explore", "subagent_type": "explore", "status": "running", "elapsed_s": 135 }
],
"todos": [
{ "title": "Fix auth bug", "status": "in_progress" },
{ "title": "Update docs", "status": "pending" }
]
}
5. Internal state exposure map
| Data field | Source in codebase | Exposure needed | Effort |
|---|---|---|---|
session_id |
Runtime.session.id (agent.py) |
Include in tick | Trivial |
model |
KimiSoul.model_name (kimisoul.py) |
Read property | Trivial |
mode |
Shell._mode (ui/shell/__init__.py) |
Pass into tick | Trivial |
flags.* |
StatusSnapshot (soul/__init__.py:86) |
Serialize existing struct | Trivial |
context.* |
StatusSnapshot (soul/__init__.py:86) |
Serialize existing struct | Trivial |
git.* |
_get_git_branch() / _get_git_status() (prompt.py:913/954) |
Extract into reusable helper | Small |
background_tasks |
BgTaskCounts (prompt.py:1184) |
Pass through | Trivial |
mcp |
MCPStatusSnapshot (wire/types.py:166) |
Already in StatusSnapshot |
Trivial |
tools_in_flight |
KimiToolset._current_step_calls (toolset.py:135) |
New — expose current step's calls | Medium |
agents |
Runtime.subagent_store (agent.py:189) |
New — emit running instances | Medium |
todos |
SessionState.todos (session_state.py:44) |
New — read root/subagent state | Medium |
usage_limits |
Not implemented | Optional future | N/A |
6. Plugin slash commands
Current: KimiSoul._build_slash_commands() (kimisoul.py:731) iterates soul_slash_registry plus skills. No plugin hook.
Proposed: Also iterate statusline plugins that declared slash_commands. Create SlashCommand wrappers that forward to the plugin's stdin:
for plugin in statusline_plugins:
for cmd in plugin.spec.slash_commands:
commands.append(SlashCommand(
name=cmd.name,
func=plugin.forward_slash(cmd.name),
description=cmd.description,
))
IPC format:
{"event": "slash_command", "command": "kimi-hud:config", "args": "theme dark"}
Plugin responds via stdout (captured and shown in chat) or updates its statusline output.
7. In-flight tools
Current: HookEngine has PreToolUse (can block) and PostToolUse (after success). No "tool is running" signal.
Options:
- Add a
ToolBeginhook/event, fired attoolset.py:292right after thePreToolUsecheck and beforeawait tool.call(arguments). - Or simpler: read
KimiToolset._current_step_calls(toolset.py:135) and_current_step_tasks(toolset.py:137) when building the statusline tick payload.
Why Workarounds Don't Work
- Wire mode (
kimi --wire): Replaces the entire Shell UI. Not a plugin; it's a full client rewrite. - Hooks-only: Hooks are fire-and-forget shell commands, not a structured stream. And there is still no way to render into the bottom toolbar.
Environment
- Kimi Code CLI version: latest (source inspected around 2026-05)
- OS: Windows / macOS / Linux
I'm happy to prototype the plugin side and test early implementations once a direction is chosen.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading plugin/init.py, plugin/tool.py, app.py, prompt.py, and kimisoul.py to map the existing plugin lifecycle, toolbar, and slash-command paths. The issue proposes several API and state-exposure changes, so a maintainer must first choose and scope one implementation path. Done means the selected extensibility slice has an agreed API and works across its described lifecycle or rendering behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, developer-experience
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100