Skip to content
New Kodine v2 is now available

Plugins

Extend Kodine with custom plugins.

Plugins tap into Kodine’s event stream to reshape how it behaves — new features, third-party integrations, or tweaks to the defaults are all on the table.

The community has published plenty of plugins worth browsing.


Use a plugin

Plugins load from two kinds of sources.


From local files

Drop JavaScript or TypeScript files into a plugin directory.

  • .kodine/plugins/ — plugins scoped to the project
  • ~/.config/kodine/plugins/ — plugins available everywhere

Anything in these folders is picked up at startup.


From npm

Reference npm packages straight from the config file.

kodine.json
{
"$schema": "https://kodine.net/config.json",
"plugin": ["kodine-helicone-session", "kodine-wakatime", "@my-org/custom-plugin"]
}

Plain and scoped package names both work.

See what’s available in the ecosystem.


How plugins are installed

npm plugins install themselves via Bun on startup, with packages and their dependencies cached under ~/.cache/kodine/node_modules/.

Local plugins load straight from the plugin directory. External packages require a package.json inside your config directory (see Dependencies) — or publish the plugin to npm and add it to your config.


Load order

Every source is loaded, and all hooks fire in sequence. The order is:

  1. Global config (~/.config/kodine/kodine.json)
  2. Project config (kodine.json)
  3. Global plugin directory (~/.config/kodine/plugins/)
  4. Project plugin directory (.kodine/plugins/)

An npm package appearing twice with identical name and version loads only once. A local plugin and an npm plugin that merely share a similar name, however, are treated as two distinct plugins.


Create a plugin

At its core a plugin is a JavaScript/TypeScript module exporting one or more plugin functions. Each function takes a context object and hands back a hooks object.


Dependencies

External npm packages work in local plugins and custom tools alike. Declare what you need in a package.json inside your config directory.

.kodine/package.json
{
"dependencies": {
"shescape": "^2.1.0"
}
}

On startup Kodine runs bun install over that file, after which your plugins and tools can import the packages freely.

.kodine/plugins/my-plugin.ts
import { escape } from "shescape"
export const MyPlugin = async (ctx) => {
return {
"tool.execute.before": async (input, output) => {
if (input.tool === "bash") {
output.args.command = escape(output.args.command)
}
},
}
}

Basic structure

.kodine/plugins/example.js
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
console.log("Plugin initialized!")
return {
// Hook implementations go here
}
}

The plugin function is handed:

  • project: details about the active project.
  • directory: the working directory.
  • worktree: the git worktree location.
  • client: a Kodine SDK client for talking to the AI.
  • $: Bun’s shell API for running commands.

TypeScript support

TypeScript users can pull types from the plugin package:

my-plugin.ts
import type { Plugin } from "@kodine-ai/plugin"
export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
return {
// Type-safe hook implementations
}
}

Events

As the Examples section below shows, plugins can listen for events. The full catalog:

Command Events

  • command.executed

File Events

  • file.edited
  • file.watcher.updated

Installation Events

  • installation.updated

LSP Events

  • lsp.client.diagnostics
  • lsp.updated

Message Events

  • message.part.removed
  • message.part.updated
  • message.removed
  • message.updated

Permission Events

  • permission.asked
  • permission.replied

Server Events

  • server.connected

Session Events

  • session.created
  • session.compacted
  • session.deleted
  • session.diff
  • session.error
  • session.idle
  • session.status
  • session.updated

Todo Events

  • todo.updated

Shell Events

  • shell.env

Tool Events

  • tool.execute.after
  • tool.execute.before

TUI Events

  • tui.prompt.append
  • tui.command.execute
  • tui.toast.show

Examples

A few plugins to spark ideas.


Send notifications

Fire notifications when particular events occur:

.kodine/plugins/notification.js
export const NotificationPlugin = async ({ project, client, $, directory, worktree }) => {
return {
event: async ({ event }) => {
// Send notification on session completion
if (event.type === "session.idle") {
await $`osascript -e 'display notification "Session completed!" with title "kodine"'`
}
},
}
}

This leans on osascript, macOS’s AppleScript runner, to surface the notification.


.env protection

Stop Kodine from opening .env files:

.kodine/plugins/env-protection.js
export const EnvProtection = async ({ project, client, $, directory, worktree }) => {
return {
"tool.execute.before": async (input, output) => {
if (input.tool === "read" && output.args.filePath.includes(".env")) {
throw new Error("Do not read .env files")
}
},
}
}

Inject environment variables

Push environment variables into every shell invocation (AI tools and user terminals included):

.kodine/plugins/inject-env.js
export const InjectEnvPlugin = async () => {
return {
"shell.env": async (input, output) => {
output.env.MY_API_KEY = "secret"
output.env.PROJECT_ROOT = input.cwd
},
}
}

Custom tools

Plugins can register brand-new tools too:

.kodine/plugins/custom-tools.ts
import { type Plugin, tool } from "@kodine-ai/plugin"
export const CustomToolsPlugin: Plugin = async (ctx) => {
return {
tool: {
mytool: tool({
description: "This is a custom tool",
args: {
foo: tool.schema.string(),
},
async execute(args, context) {
const { directory, worktree } = context
return `Hello ${args.foo} from ${directory} (worktree: ${worktree})`
},
}),
},
}
}

The tool helper builds a tool Kodine can call. Feed it a definition containing:

  • description: a summary of the tool’s purpose
  • args: a Zod schema describing the tool’s arguments
  • execute: the function invoked on each call

From then on, your custom tool sits next to the built-in ones.


Logging

Prefer client.app.log() over console.log when you want structured logging:

.kodine/plugins/my-plugin.ts
export const MyPlugin = async ({ client }) => {
await client.app.log({
body: {
service: "my-plugin",
level: "info",
message: "Plugin initialized",
extra: { foo: "bar" },
},
})
}

Available levels: debug, info, warn, error. The SDK documentation has the specifics.


Compaction hooks

Shape what context survives when a session is compacted:

.kodine/plugins/compaction.ts
import type { Plugin } from "@kodine-ai/plugin"
export const CompactionPlugin: Plugin = async (ctx) => {
return {
"experimental.session.compacting": async (input, output) => {
// Inject additional context into the compaction prompt
output.context.push(`
## Custom Context
Include any state that should persist across compaction:
- Current task status
- Important decisions made
- Files being actively worked on
`)
},
}
}

experimental.session.compacting runs just before the LLM writes its continuation summary — your chance to slip in domain-specific context the stock prompt would overlook.

Setting output.prompt goes further and swaps out the whole compaction prompt:

.kodine/plugins/custom-compaction.ts
import type { Plugin } from "@kodine-ai/plugin"
export const CustomCompactionPlugin: Plugin = async (ctx) => {
return {
"experimental.session.compacting": async (input, output) => {
// Replace the entire compaction prompt
output.prompt = `
You are generating a continuation prompt for a multi-agent swarm session.
Summarize:
1. The current task and its status
2. Which files are being modified and by whom
3. Any blockers or dependencies between agents
4. The next steps to complete the work
Format as a structured prompt that a new agent can use to resume work.
`
},
}
}

With output.prompt present, the default compaction prompt is fully replaced and the output.context array gets ignored.