Plugins extend LocalCode with custom slash commands (e.g. /deploy prod) and custom agent tools (callable by the AI during a task). They are plain CommonJS .js files — no build step required.
~/.localcode/plugins/
└── my-plugin/
└── index.js ← entry point (CommonJS)
Install via LocalCode:
/plugin install ./path/to/my-plugin
Or copy manually:
mkdir -p ~/.localcode/plugins/my-plugin
cp index.js ~/.localcode/plugins/my-plugin/
A plugin is a CommonJS module that exports a single object:
/** @type {import('localcode/plugins').LocalCodePlugin} */
const plugin = {
name: 'my-plugin', // unique identifier, used as folder name
version: '1.0.0',
description: 'What this plugin does',
commands: [ /* slash command definitions */ ],
tools: [ /* agent tool definitions */ ],
}
module.exports = pluginBoth commands and tools are optional. A plugin can provide only one, both, or neither.
Slash commands are triggered by the user in the LocalCode input bar.
commands: [
{
cmd: '/deploy ', // the slash command; trailing space = accepts args
description: 'Deploy to an environment',
async handler(args, ctx) {
// args: string — everything the user typed after "/deploy "
// ctx: { cwd: string }
const env = args.trim() || 'staging'
// ... your logic ...
return {
type: 'text', // 'text' | 'error' | 'done' | 'command'
content: `Deployed to ${env}!`,
title: 'deploy', // only used when type === 'command'
}
},
},
],| Pattern | Meaning |
|---|---|
/hello |
Exact command, no arguments |
/hello |
Command with arguments (trailing space) |
/deploy prod |
Fixed phrase — matches only that exact text |
type |
What LocalCode renders |
|---|---|
'text' |
Plain text output |
'done' |
Styled "done" block (green checkmark area) |
'error' |
Red error line |
'command' |
Code-block style panel; use title as header |
Agent tools are functions the AI can call during a task. Define them in tools: and the agent automatically learns about them via the system prompt.
tools: [
{
name: 'send_slack', // snake_case, used in JSON
description: 'Send a message to a Slack channel',
async handler(args, ctx) {
// args: Record<string, unknown> — parsed from the agent's JSON call
// ctx: { cwd: string }
const channel = String(args.channel ?? '#general')
const text = String(args.text ?? '')
// ... call Slack API ...
return {
success: true,
output: `Message sent to ${channel}`,
// error: 'Something went wrong', ← set on failure
// images: ['base64...'], ← optional screenshots / images
}
},
},
],The agent calls your tool like this:
{"tool": "send_slack", "arguments": {"channel": "#builds", "text": "Build passed!"}}{
success: boolean
output: string // shown to the agent as the tool result
error?: string // message on failure
images?: string[] // base64 images (optional)
}You do not need TypeScript to write plugins, but you can use JSDoc for editor support:
/** @type {import('localcode/plugins').LocalCodePlugin} */Relevant types (from src/plugins/PluginLoader.ts):
interface PluginContext {
cwd: string
}
interface PluginCommandResult {
type: 'text' | 'error' | 'done' | 'command'
content: string
title?: string // only for type === 'command'
}
interface PluginCommand {
cmd: string
description: string
handler: (args: string, ctx: PluginContext) => Promise<PluginCommandResult>
}
interface PluginTool {
name: string
description: string
handler: (args: Record<string, unknown>, ctx: PluginContext) => Promise<ToolResult>
}
interface LocalCodePlugin {
name: string
version: string
description?: string
commands?: PluginCommand[]
tools?: PluginTool[]
}| Event | What happens |
|---|---|
| App start | All plugins in ~/.localcode/plugins/ are loaded automatically |
/plugin install |
Plugin is copied to the plugin dir and immediately activated |
/plugin remove |
Plugin folder is deleted; changes take effect immediately |
/plugin reload |
All plugins are unloaded and re-loaded from disk |
/plugin List all installed plugins
/plugin install <path> Install from a folder or .js file
/plugin remove <name> Uninstall by plugin name
/plugin reload Reload all plugins from disk
# Option A: single file
~/.localcode/plugins/my-plugin.js
# Option B: directory (recommended for multi-file plugins)
~/.localcode/plugins/my-plugin/
├── index.js ← entry point (or index.cjs)
├── lib/
│ └── helper.js
└── package.json ← optional; not required
Important: Plugins run as CommonJS (
.js/.cjs).
Do not use ES module syntax (import/export) — userequire()andmodule.exports.
If your plugin throws during load, LocalCode records the error and skips the plugin. Run /plugin list to see load errors.
In handlers, prefer returning { type: 'error', content: '...' } (commands) or { success: false, error: '...' } (tools) over throwing — this gives the user a clean error message.
Two ready-to-run example plugins are included:
examples/plugins/hello-world/ ← /hello command + "greet" agent tool
examples/plugins/file-stats/ ← /stats command + "file_stats" agent tool
Install them:
/plugin install examples/plugins/hello-world
/plugin install examples/plugins/file-stats
- Plugins run with full Node.js permissions in the same process as LocalCode.
- Only install plugins from sources you trust.
- The plugin directory is
~/.localcode/plugins/— review files there before reloading. - The agent asks for user confirmation before calling any tool (including plugin tools).
// ~/.localcode/plugins/time/index.js
const plugin = {
name: 'time',
version: '1.0.0',
commands: [
{
cmd: '/time',
description: 'Show the current time',
async handler(_args, _ctx) {
return { type: 'text', content: new Date().toLocaleTimeString() }
},
},
],
tools: [
{
name: 'current_time',
description: 'Returns the current date and time as a string',
async handler(_args, _ctx) {
return { success: true, output: new Date().toISOString() }
},
},
],
}
module.exports = pluginAfter saving the file, run /plugin reload (or restart LocalCode) and type /time.