Plugins
Plugins let you extend Cosine CLI with custom Lua scripts that react to agent events in real time. Plugins can log activity, send webhooks, queue nudges, and integrate with external tools — all without modifying the CLI itself.
Installing a Plugin
Section titled “Installing a Plugin”Install plugins from any git repository:
cos plugin install https://github.com/user/my-plugin.gitShort formats are also supported:
# GitHub shorthandcos plugin install user/my-plugin
# Domain-prefixedcos plugin install github.com/user/my-pluginPlugins are installed to ~/.cosine/plugins/<plugin-name>/.
Managing Plugins
Section titled “Managing Plugins”List installed plugins
Section titled “List installed plugins”cos plugin listOutput:
event-logger (v0.1.0) - Logs all agent events to a file /Users/you/.cosine/plugins/event-loggerRemove a plugin
Section titled “Remove a plugin”cos plugin remove event-loggerWriting a Plugin
Section titled “Writing a Plugin”Every plugin needs two files in its repository root:
PLUGIN.md — Manifest
Section titled “PLUGIN.md — Manifest”A markdown file with YAML frontmatter that describes the plugin:
---name: my-plugindescription: What this plugin doesversion: 0.1.0events: - stream - checkpoint---
Optional documentation for users of this plugin.| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier (a-z, 0-9, -, _ only) |
description | No | Human-readable description |
version | No | Semver version string |
events | No | Event types to subscribe to. Omit to receive all events. |
plugin.lua — Entry point
Section titled “plugin.lua — Entry point”The Lua script that runs when the plugin loads. Use cos.on() to register event callbacks:
-- React to text being streamed from the agentcos.on("stream", function(event) if event.data.type == "text_delta" then cos.log("Agent: " .. (event.data.text or "")) endend)
-- React to checkpointscos.on("checkpoint", function(event) cos.log("Checkpoint " .. event.data.id .. " created")end)Event Callbacks
Section titled “Event Callbacks”Register callbacks with cos.on(event_kind, callback). The callback receives an event table with:
| Field | Type | Description |
|---|---|---|
event.kind | string | The event type (e.g., "stream", "checkpoint") |
event.origin | string | "agent" or "ui" |
event.timestamp | string | ISO 8601 timestamp |
event.data | table | Event-specific payload |
Available Event Types
Section titled “Available Event Types”| Event Kind | Description | Key event.data Fields |
|---|---|---|
stream | Agent text, tool calls, errors | type, text, tool_name, tool_args, error |
confirmation | Tool confirmation requested | confirmed |
external_access | External access confirmation | confirmed, allow_for_session, paths |
plan_confirm | Plan mode entry confirmation | confirmed |
plan_accept | Plan acceptance/rejection | accepted, feedback |
questionnaire | Questionnaire responses | results |
todo_list_created | Todo list created | items (array of {id, content, active_form, state, order}) |
todo_item_updated | Todo item changed | id, content, active_form, state, order |
todo_item_added | New todo item added | id, content, active_form, state, order |
todo_item_deleted | Todo item removed | id |
todo_in_progress_changed | Active todo item changed | active_form, current_index, total_count |
subagent_started | Subagent spawned | agent_id, task, status, subagent_type |
subagent_progress | Subagent progress update | agent_id, task, status, current_tool |
subagent_completed | Subagent finished | agent_id, task, success, output, tokens_used |
subagent_query | Query sent to subagent | agent_id, message |
subagent_response | Response from subagent | agent_id, response |
checkpoint | Git checkpoint created | id, files, turn |
qa_started | QA verification started | agent_id, source_agent_id, summary |
qa_completed | QA verification finished | agent_id, success, output, error |
feedback | User feedback (thumbs up/down) | sentiment, session_id |
session_title_updated | Session title changed | title |
permission_retry | Permission error retry | confirmed |
Use "*" to receive all events:
cos.on("*", function(event) cos.log("[" .. event.kind .. "] event received")end)Stream Event Types
Section titled “Stream Event Types”The stream event kind contains many sub-types in event.data.type:
event.data.type | Description |
|---|---|
text_delta | New text content from the agent |
tool_call_start | Tool call begins |
tool_call_complete | Tool call ready to execute |
tool_result | Tool finished executing |
turn_complete | Agent turn completed |
complete | Entire run completed |
error | An error occurred |
Lua API Reference
Section titled “Lua API Reference”Event Handling
Section titled “Event Handling”| Function | Description |
|---|---|
cos.on(kind, fn) | Register a callback for an event type. Use "*" for all events. |
Logging
Section titled “Logging”| Function | Description |
|---|---|
cos.log(message) | Log a message to stderr (visible in debug output) |
IPC — Interact with the Agent
Section titled “IPC — Interact with the Agent”| Function | Description |
|---|---|
cos.ipc.queue_nudge(text) | Queue a message to send to the agent on its next turn |
cos.ipc.send_confirmation(bool) | Respond to a tool confirmation prompt |
cos.ipc.send_plan_confirm(bool) | Respond to a plan mode confirmation |
cos.ipc.send_plan_accept(accepted, feedback) | Accept or reject a plan |
Shell Commands
Section titled “Shell Commands”| Function | Returns | Description |
|---|---|---|
cos.exec(command) | stdout or "", error | Run a shell command (30s timeout) |
HTTP Requests
Section titled “HTTP Requests”| Function | Returns | Description |
|---|---|---|
cos.http.get(url) | body or "", error | HTTP GET request (10s timeout) |
cos.http.post(url, body) | body or "", error | HTTP POST with JSON content type (10s timeout) |
File System (Sandboxed)
Section titled “File System (Sandboxed)”File operations are restricted to the plugin’s own directory.
| Function | Returns | Description |
|---|---|---|
cos.fs.read(path) | content or nil, error | Read a file relative to the plugin directory |
cos.fs.write(path, content) | nothing or nil, error | Write a file relative to the plugin directory |
Utilities
Section titled “Utilities”| Function | Returns | Description |
|---|---|---|
cos.home() | string | Path to ~/.cosine |
cos.cwd() | string | Current working directory |
Example: Event Logger Plugin
Section titled “Example: Event Logger Plugin”This plugin logs all agent events to a JSON Lines file in the plugin directory:
PLUGIN.md:
---name: event-loggerdescription: Logs all agent events to a JSONL fileversion: 0.1.0---plugin.lua:
-- Log all events to a JSONL filecos.on("*", function(event) local line = string.format( '{"time":"%s","kind":"%s","origin":"%s"}', event.timestamp, event.kind, event.origin )
-- Append to log file (sandboxed to plugin directory) local existing = "" local content, err = cos.fs.read("events.jsonl") if not err then existing = content end cos.fs.write("events.jsonl", existing .. line .. "\n")
-- Also log stream text to stderr for visibility if event.kind == "stream" and event.data.type == "text_delta" then cos.log(event.data.text or "") endend)
cos.log("event-logger plugin loaded")Example: Webhook Notifier Plugin
Section titled “Example: Webhook Notifier Plugin”Send a webhook when the agent creates a checkpoint:
PLUGIN.md:
---name: webhook-notifierdescription: Sends webhooks on checkpoint eventsversion: 0.1.0events: - checkpoint - subagent_completed---plugin.lua:
local WEBHOOK_URL = "https://hooks.example.com/cosine"
cos.on("checkpoint", function(event) local files = "" if event.data.files then for i = 1, #event.data.files do if files ~= "" then files = files .. ", " end files = files .. '"' .. event.data.files[i] .. '"' end end
local body = string.format( '{"event":"checkpoint","id":"%s","files":[%s],"turn":%d}', event.data.id, files, event.data.turn or 0 )
local result, err = cos.http.post(WEBHOOK_URL, body) if err then cos.log("webhook failed: " .. err) endend)
cos.on("subagent_completed", function(event) local status = event.data.success and "success" or "failed" local body = string.format( '{"event":"subagent_completed","agent_id":"%s","status":"%s"}', event.data.agent_id or "", status ) cos.http.post(WEBHOOK_URL, body)end)
cos.log("webhook-notifier plugin loaded")Example: Auto-Nudge Plugin
Section titled “Example: Auto-Nudge Plugin”Automatically remind the agent to run tests after code changes:
PLUGIN.md:
---name: auto-nudgedescription: Reminds the agent to run tests after editing filesversion: 0.1.0events: - stream---plugin.lua:
local edited_files = 0
cos.on("stream", function(event) -- Count file edits by watching for tool results from code_edit if event.data.type == "tool_result" then local tool = event.data.tool_name or "" if tool == "code_edit" or tool == "edit" or tool == "apply_patch" then edited_files = edited_files + 1 end end
-- After 3+ file edits in one turn, nudge to run tests if event.data.type == "turn_complete" and edited_files >= 3 then cos.ipc.queue_nudge("You've edited " .. edited_files .. " files. Please run the test suite to verify your changes.") edited_files = 0 endend)
cos.log("auto-nudge plugin loaded")Security
Section titled “Security”Plugins run with the same permissions as the Cosine CLI process. Before installing a plugin:
- Review the source code — especially
plugin.lua— before installing cos.exec()can run any shell command on your machinecos.http.*can make outbound HTTP requests to any URLcos.fs.*is sandboxed to the plugin’s own directory and cannot access files outside it
Only install plugins from sources you trust.
Plugin Lifecycle
Section titled “Plugin Lifecycle”- Install —
cos plugin install <url>clones the repo and copies it to~/.cosine/plugins/<name>/ - Load — When Cosine CLI starts, all plugins in
~/.cosine/plugins/are discovered and loaded - Subscribe — Each plugin’s Lua callbacks are connected to the agent event bus
- Dispatch — As the agent runs, events are dispatched to matching plugin callbacks
- Cleanup — When the CLI exits, all plugin VMs are closed
Next Steps
Section titled “Next Steps”- Skills — Learn about reusable skill packages
- Configuration — Configure the CLI
- MCP Configuration — Connect external tool servers