Skip to content

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.

Install plugins from any git repository:

Terminal window
cos plugin install https://github.com/user/my-plugin.git

Short formats are also supported:

Terminal window
# GitHub shorthand
cos plugin install user/my-plugin
# Domain-prefixed
cos plugin install github.com/user/my-plugin

Plugins are installed to ~/.cosine/plugins/<plugin-name>/.

Terminal window
cos plugin list

Output:

event-logger (v0.1.0) - Logs all agent events to a file
/Users/you/.cosine/plugins/event-logger
Terminal window
cos plugin remove event-logger

Every plugin needs two files in its repository root:

A markdown file with YAML frontmatter that describes the plugin:

---
name: my-plugin
description: What this plugin does
version: 0.1.0
events:
- stream
- checkpoint
---
Optional documentation for users of this plugin.
FieldRequiredDescription
nameYesUnique identifier (a-z, 0-9, -, _ only)
descriptionNoHuman-readable description
versionNoSemver version string
eventsNoEvent types to subscribe to. Omit to receive all events.

The Lua script that runs when the plugin loads. Use cos.on() to register event callbacks:

-- React to text being streamed from the agent
cos.on("stream", function(event)
if event.data.type == "text_delta" then
cos.log("Agent: " .. (event.data.text or ""))
end
end)
-- React to checkpoints
cos.on("checkpoint", function(event)
cos.log("Checkpoint " .. event.data.id .. " created")
end)

Register callbacks with cos.on(event_kind, callback). The callback receives an event table with:

FieldTypeDescription
event.kindstringThe event type (e.g., "stream", "checkpoint")
event.originstring"agent" or "ui"
event.timestampstringISO 8601 timestamp
event.datatableEvent-specific payload
Event KindDescriptionKey event.data Fields
streamAgent text, tool calls, errorstype, text, tool_name, tool_args, error
confirmationTool confirmation requestedconfirmed
external_accessExternal access confirmationconfirmed, allow_for_session, paths
plan_confirmPlan mode entry confirmationconfirmed
plan_acceptPlan acceptance/rejectionaccepted, feedback
questionnaireQuestionnaire responsesresults
todo_list_createdTodo list createditems (array of {id, content, active_form, state, order})
todo_item_updatedTodo item changedid, content, active_form, state, order
todo_item_addedNew todo item addedid, content, active_form, state, order
todo_item_deletedTodo item removedid
todo_in_progress_changedActive todo item changedactive_form, current_index, total_count
subagent_startedSubagent spawnedagent_id, task, status, subagent_type
subagent_progressSubagent progress updateagent_id, task, status, current_tool
subagent_completedSubagent finishedagent_id, task, success, output, tokens_used
subagent_queryQuery sent to subagentagent_id, message
subagent_responseResponse from subagentagent_id, response
checkpointGit checkpoint createdid, files, turn
qa_startedQA verification startedagent_id, source_agent_id, summary
qa_completedQA verification finishedagent_id, success, output, error
feedbackUser feedback (thumbs up/down)sentiment, session_id
session_title_updatedSession title changedtitle
permission_retryPermission error retryconfirmed

Use "*" to receive all events:

cos.on("*", function(event)
cos.log("[" .. event.kind .. "] event received")
end)

The stream event kind contains many sub-types in event.data.type:

event.data.typeDescription
text_deltaNew text content from the agent
tool_call_startTool call begins
tool_call_completeTool call ready to execute
tool_resultTool finished executing
turn_completeAgent turn completed
completeEntire run completed
errorAn error occurred
FunctionDescription
cos.on(kind, fn)Register a callback for an event type. Use "*" for all events.
FunctionDescription
cos.log(message)Log a message to stderr (visible in debug output)
FunctionDescription
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
FunctionReturnsDescription
cos.exec(command)stdout or "", errorRun a shell command (30s timeout)
FunctionReturnsDescription
cos.http.get(url)body or "", errorHTTP GET request (10s timeout)
cos.http.post(url, body)body or "", errorHTTP POST with JSON content type (10s timeout)

File operations are restricted to the plugin’s own directory.

FunctionReturnsDescription
cos.fs.read(path)content or nil, errorRead a file relative to the plugin directory
cos.fs.write(path, content)nothing or nil, errorWrite a file relative to the plugin directory
FunctionReturnsDescription
cos.home()stringPath to ~/.cosine
cos.cwd()stringCurrent working directory

This plugin logs all agent events to a JSON Lines file in the plugin directory:

PLUGIN.md:

---
name: event-logger
description: Logs all agent events to a JSONL file
version: 0.1.0
---

plugin.lua:

-- Log all events to a JSONL file
cos.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 "")
end
end)
cos.log("event-logger plugin loaded")

Send a webhook when the agent creates a checkpoint:

PLUGIN.md:

---
name: webhook-notifier
description: Sends webhooks on checkpoint events
version: 0.1.0
events:
- 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)
end
end)
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")

Automatically remind the agent to run tests after code changes:

PLUGIN.md:

---
name: auto-nudge
description: Reminds the agent to run tests after editing files
version: 0.1.0
events:
- 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
end
end)
cos.log("auto-nudge plugin loaded")

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 machine
  • cos.http.* can make outbound HTTP requests to any URL
  • cos.fs.* is sandboxed to the plugin’s own directory and cannot access files outside it

Only install plugins from sources you trust.

  1. Installcos plugin install <url> clones the repo and copies it to ~/.cosine/plugins/<name>/
  2. Load — When Cosine CLI starts, all plugins in ~/.cosine/plugins/ are discovered and loaded
  3. Subscribe — Each plugin’s Lua callbacks are connected to the agent event bus
  4. Dispatch — As the agent runs, events are dispatched to matching plugin callbacks
  5. Cleanup — When the CLI exits, all plugin VMs are closed