How Kimi Code CLI Discovers and Activates Plugins via the `/plugins` Endpoint
Kimi Code CLI discovers plugins by scanning the .opencode/plugins directory for .mjs files, dynamically importing each module to validate the exported plugin contract, and registering the command handlers into the CLI dispatcher.
The ayghri/i-have-adhd repository demonstrates the complete plugin lifecycle for the Kimi Code CLI runtime. Understanding this convention-based system allows developers to extend the CLI with custom commands by placing properly structured JavaScript modules in the correct directory.
Plugin Discovery Mechanism in Kimi Code CLI
The discovery phase begins when the CLI initializes and resolves the repository root containing the top-level configuration files.
Scanning the .opencode/plugins Directory
Kimi Code CLI maps the virtual /plugins endpoint to the physical .opencode/plugins directory within the project root. During startup, the runtime performs a filesystem scan of this specific path to locate candidate plugin files.
File Convention for Plugin Modules
The loader filters directory contents by extension, treating only files ending with .mjs as valid plugin candidates. This EcmaScript module requirement ensures that the runtime can use native import() statements for loading. The discovery logic specifically excludes other JavaScript extensions (.js, .ts) or directories, focusing exclusively on .mjs files for security and consistency.
Plugin Activation and Validation
Once a candidate file is identified, the CLI moves to the activation phase, which involves dynamic loading and contract verification.
Dynamic Module Import
For each discovered .mjs file, the CLI executes a dynamic import() call (or require() in Node.js builds) against the absolute path. The runtime loads the module in isolation and extracts the default export. This occurs in .opencode/plugins/i-have-adhd.mjs for the ADHD plugin, where the module exports a configuration object defining the plugin's behavior.
The Plugin Contract
The imported module must export a default object conforming to the Kimi plugin contract. The runtime validates the presence of four required fields:
name: The plugin identifier used in logging and error messagesversion: Semantic versioning string for compatibility checksdescription: Human-readable summary of plugin functionalitycommands: A map object linking command names to handler functions
If validation succeeds, the plugin state transitions to active and proceeds to registration. Missing required fields result in silent skipping or error reporting depending on the CLI's strict mode configuration.
Command Registration and Execution
With the plugin activated, the CLI integrates its functionality into the command dispatcher.
The commands map structure keys command names (such as i-have-adhd) to asynchronous handler functions. Kimi registers each handler with its internal dispatcher, making commands available under the standard CLI syntax kimi <command>. When a user invokes kimi i-have-adhd next-action, the dispatcher routes the request to the specific handler defined in the plugin's commands object, forwards parsed arguments, and returns the result to the terminal.
Configuration Files and Project Structure
Several files collaborate to define the plugin metadata and documentation:
kimi.plugin.json: The top-level manifest that declares plugin metadata and points the CLI to the skills directory. This file sits in the repository root alongside the source code..opencode/plugins/i-have-adhd.mjs: The actual plugin implementation containing the exported default object withname,version,description, andcommands..opencode/command/i-have-adhd.md: Markdown documentation displayed when users runkimi help i-have-adhd, providing human-readable command descriptions and usage examples.plugin.json: A generic runtime contract used across multiple AI coding assistants (Claude, Codex, etc.), separate from the Kimi-specific configuration.
The following example shows the minimal structure required for a valid plugin module placed in .opencode/plugins/example.mjs:
export default {
name: "example",
version: "1.0.0",
description: "A demo plugin for Kimi Code CLI",
commands: {
greet: async ({ args }) => {
const name = args[0] ?? "world";
return `Hello, ${name}!`;
},
},
};
The internal loading logic roughly follows this pattern:
import { readdir } from "fs/promises";
import path from "path";
async function loadPlugins(root) {
const pluginsDir = path.join(root, ".opencode", "plugins");
const files = await readdir(pluginsDir);
for (const file of files.filter(f => f.endsWith(".mjs"))) {
const modulePath = path.join(pluginsDir, file);
const { default: plugin } = await import(modulePath);
// Validate required fields
if (plugin.name && plugin.commands) {
registerPlugin(plugin);
}
}
}
Users interact with registered plugins through standard CLI invocations:
$ kimi i-have-adhd next-action
# → "🟢 Next step: …"
Summary
- Discovery: Kimi Code CLI scans
.opencode/plugins/for.mjsfiles when the virtual/pluginsendpoint is accessed during startup. - Activation: Each module undergoes dynamic import and validation against the plugin contract requiring
name,version,description, andcommandsfields. - Registration: Valid handlers from the
commandsmap are wired into the CLI dispatcher, making them available viakimi <command>syntax. - Configuration: The
kimi.plugin.jsonfile provides top-level metadata, while individual.mjsfiles in.opencode/plugins/contain the executable logic.
Frequently Asked Questions
How does Kimi Code CLI locate the plugins directory?
The CLI resolves the repository root by locating kimi.plugin.json, then constructs the path to .opencode/plugins/ which serves as the physical location for the virtual /plugins endpoint. This convention hardcodes the directory structure, requiring plugins to reside in this specific subdirectory to be discovered automatically.
What file extension must plugins use to be recognized?
Plugins must use the .mjs extension to enable EcmaScript module loading via dynamic import(). The discovery mechanism explicitly filters for this extension and ignores .js, .ts, or other file types, ensuring consistent ES module behavior across the runtime.
Can a plugin expose multiple commands?
Yes. The commands property in the exported plugin object is a map where each key represents a distinct command name and each value is the corresponding handler function. A single .mjs file in .opencode/plugins/ can register multiple discrete commands under the same plugin namespace.
What happens if a plugin module is missing required fields?
If the imported module lacks the required name or commands fields, the validation step fails and the CLI skips registration for that specific module. Depending on the CLI configuration, this may result in silent omission from the command list or a warning message indicating which file failed validation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →