# How Kimi Code CLI Discovers and Activates Plugins via the `/plugins` Endpoint

> Learn how Kimi Code CLI discovers and activates plugins. It scans your .opencode/plugins directory for .mjs files and dynamically imports them.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: internals
- Published: 2026-09-10

---

**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 messages
- **`version`**: Semantic versioning string for compatibility checks
- **`description`**: Human-readable summary of plugin functionality
- **`commands`**: 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`](https://github.com/ayghri/i-have-adhd/blob/main/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 with `name`, `version`, `description`, and `commands`.
- **[`.opencode/command/i-have-adhd.md`](https://github.com/ayghri/i-have-adhd/blob/main/.opencode/command/i-have-adhd.md)**: Markdown documentation displayed when users run `kimi help i-have-adhd`, providing human-readable command descriptions and usage examples.
- **[`plugin.json`](https://github.com/ayghri/i-have-adhd/blob/main/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`:

```javascript
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:

```javascript
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:

```bash
$ kimi i-have-adhd next-action

# → "🟢 Next step: …"

```

## Summary

- **Discovery**: Kimi Code CLI scans `.opencode/plugins/` for `.mjs` files when the virtual `/plugins` endpoint is accessed during startup.
- **Activation**: Each module undergoes dynamic import and validation against the plugin contract requiring `name`, `version`, `description`, and `commands` fields.
- **Registration**: Valid handlers from the `commands` map are wired into the CLI dispatcher, making them available via `kimi <command>` syntax.
- **Configuration**: The [`kimi.plugin.json`](https://github.com/ayghri/i-have-adhd/blob/main/kimi.plugin.json) file provides top-level metadata, while individual `.mjs` files 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`](https://github.com/ayghri/i-have-adhd/blob/main/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.