Codevisor Docs

Plugins

Add custom panes and agent-callable tools served from a Codevisor machine.

A Codevisor plugin is a folder containing codevisor-plugin.json and a command that starts an HTTP server. Codevisor starts every installed plugin with the main server, assigns it a loopback port, and proxies its panes and tools to authenticated clients and agent sessions.

Plugins can provide workspace panes, agent-callable tools, or both. Because their UI and state live on the server machine, the same pane can appear on every connected client.

Install and manage

Browse published plugins in the plugin directory, or use the CLI on the server machine:

codevisor plugin install owner/repo
codevisor plugin list
codevisor plugin remove owner.plugin

Installation is a two-step flow. Codevisor first discovers the manifest so the client can show the exact install and run commands, panes, and tools. It installs only after the user confirms. Managed installs live under ~/.codevisor/plugins and can be updated by installing the same source again.

For local development, link an absolute directory instead of copying it:

codevisor plugin link /absolute/path/to/plugin

Linked directories remain owned by the developer. Edit the source, then restart the plugin from Codevisor; a development server with hot reload can update without a restart.

Manifest

The manifest must be named codevisor-plugin.json at the plugin root.

{
  "protocolVersion": 1,
  "id": "acme.notes",
  "name": "Notes",
  "description": "Keep notes beside a workspace.",
  "version": "0.1.0",
  "iconPath": "/assets/icon.svg",
  "panes": [
    {
      "type": "main",
      "title": "Notes",
      "path": "/panes/main/"
    }
  ],
  "tools": [
    {
      "name": "notes_add",
      "description": "Append a note to the current workspace.",
      "path": "/tools/notes-add",
      "inputSchema": {
        "type": "object",
        "properties": { "text": { "type": "string" } },
        "required": ["text"],
        "additionalProperties": false
      }
    }
  ],
  "run": { "command": "node server.js" },
  "healthPath": "/health"
}

id must contain exactly one dot and use lowercase owner.name form; each segment permits letters, digits, and hyphens. Pane types and tool names must be unique. Pane paths start and end with / and cannot contain a query, fragment, or ... Tool paths start with / but need no trailing slash.

Optional fields include:

  • install.command, run once during install or update.
  • platforms, a process.platform allowlist such as darwin or linux.
  • iconPath, a plain absolute server path to SVG, PNG, or WebP artwork for the plugin. A pane may declare its own iconPath; otherwise it inherits the plugin artwork.
  • healthPath, which must return a 2xx response before the plugin is ready. Without it, a TCP connection is the readiness check.

Server contract

Codevisor runs run.command in the plugin directory with these environment variables:

VariableMeaning
PORTAssigned loopback port
CODEVISOR_PLUGIN_IDManifest plugin ID
CODEVISOR_PLUGIN_DATA_DIRPersistent directory for plugin-owned state

Bind only to 127.0.0.1:$PORT. Write persistent data under CODEVISOR_PLUGIN_DATA_DIR; do not rely on browser storage when state must follow a workspace across devices.

Every proxied pane request includes X-Codevisor-Context, a base64-encoded JSON object with the available cwd, workspaceId, paneId, and themeMode. Treat every field as optional and validate it before using it.

Pane documents must use relative URLs for scripts, styles, requests, and WebSockets. This keeps all traffic under the authenticated plugin proxy without HTML rewriting.

This zero-dependency server implements the example manifest:

import http from "node:http"
import { readFile } from "node:fs/promises"

const decodeContext = (header) => {
  try {
    return JSON.parse(Buffer.from(header ?? "", "base64").toString("utf8"))
  } catch {
    return {}
  }
}

const server = http.createServer(async (request, response) => {
  const url = new URL(request.url ?? "/", "http://127.0.0.1")
  const context = decodeContext(request.headers["x-codevisor-context"])

  if (url.pathname === "/health") return response.end("ok")

  if (url.pathname === "/assets/icon.svg") {
    response.writeHead(200, { "content-type": "image/svg+xml" })
    return response.end(await readFile(new URL("./assets/icon.svg", import.meta.url)))
  }

  if (url.pathname === "/panes/main/") {
    response.writeHead(200, { "content-type": "text/html; charset=utf-8" })
    return response.end(`<!doctype html>
      <style>
        body { color: var(--codevisor-fg, CanvasText);
               background: var(--codevisor-bg, Canvas); }
      </style>
      <h1>Notes</h1><p id="cwd"></p>
      <script>
        fetch("context").then(r => r.json()).then(data => {
          document.querySelector("#cwd").textContent = data.cwd ?? "No workspace"
        })
      </script>`)
  }

  if (url.pathname === "/panes/main/context") {
    response.writeHead(200, { "content-type": "application/json" })
    return response.end(JSON.stringify({ cwd: context.cwd }))
  }

  if (url.pathname === "/tools/notes-add" && request.method === "POST") {
    let body = ""
    for await (const chunk of request) body += chunk
    const args = JSON.parse(body || "{}")
    response.writeHead(200, { "content-type": "application/json" })
    return response.end(JSON.stringify({ saved: args.text }))
  }

  response.writeHead(404).end()
})

server.listen(Number(process.env.PORT), "127.0.0.1")

Plugin artwork

Native Codevisor panes use platform-native symbols. Third-party pane chrome uses the artwork served from iconPath; icon names are never persisted in workspace records. The plugin-level path is the default, and a pane-level path overrides it. When neither exists—or an asset cannot be loaded—the client displays its generic plugin symbol.

Serve SVG as image/svg+xml, PNG as image/png, or WebP as image/webp. Assets are limited to 512 KiB. SVG must be self-contained: scripts, embedded documents, entities, external links, and external CSS resources are rejected. Codevisor validates the response and normalizes every format to a transparent 256 px PNG. Clients therefore render the same bounded raster asset on every platform. Use full-color artwork that remains legible in light and dark interfaces; plugin artwork is not treated as a tintable system symbol.

Pane integration

A client opens a plugin pane in four steps:

  1. Read a workspace pane whose providerId is plugin:<pluginId> and whose paneType matches the manifest.
  2. Request a short-lived pane token with POST /v1/plugins/{pluginId}/panes/{paneId}/token.
  3. Load the returned path against the machine's base URL. The initial query token is exchanged for a scoped, HttpOnly cookie.
  4. Reload open panes when a plugin.updated event arrives. Use plugin.state.updated only to show runtime state.

Do not put the machine bearer token in a webview URL. Subresources and proxied WebSockets use the pane cookie instead.

Native clients may inject a small window.codevisor bridge. Plugins should feature-detect it before calling getContext(), openUrl(url), or setTitle(title), and listen for codevisor:themechange. A pane must still work in an ordinary browser without the bridge.

Codevisor exposes theme values as CSS custom properties:

VariableMeaning
--codevisor-bg, --codevisor-bg-elevatedBase and raised backgrounds
--codevisor-fgPrimary text
--codevisor-fg-secondary, --codevisor-fg-tertiarySecondary and faint text
--codevisor-border, --codevisor-separatorBorders and dividers
--codevisor-accentInteractive accent
--codevisor-status-ok, --codevisor-status-warn, --codevisor-status-errorStatus colors
--codevisor-diff-added, --codevisor-diff-removedDiff text
--codevisor-diff-added-bg, --codevisor-diff-removed-bgDiff backgrounds
--codevisor-font-family, --codevisor-font-family-monoUI and monospace fonts

Build sensible fallbacks into every use:

body {
  color: var(--codevisor-fg, CanvasText);
  background: var(--codevisor-bg, Canvas);
  font-family: var(--codevisor-font-family, sans-serif);
}

Agent tools

For each tool, Codevisor sends the JSON arguments directly to the manifest path:

{ "text": "Remember to update the schema" }

The signed X-Codevisor-Context and X-Codevisor-Context-Signature headers carry pluginId, toolName, and the available workspaceId and cwd. Return JSON or plain text with a 2xx status; return a non-2xx response for failures. The agent-facing tool name is plugin.acme.notes.notes_add. A client invoking the management API wraps the arguments as { "args": { ... }, "workspaceId": "...", "cwd": "..." }; Codevisor unwraps args before it calls the plugin.

Lifecycle and failures

After the main HTTP listener is ready, Codevisor starts every compatible installed plugin and keeps it running until server shutdown. Codevisor waits up to 15 seconds for readiness and restarts crashed processes with exponential backoff. Five consecutive failures leave the plugin in failed until an explicit restart. One plugin failing never prevents the main server or other plugins from starting. The proxy returns 502 when a process cannot be reached and 504 when a request exceeds 30 seconds.

Publish

Publish the plugin in a public GitHub repository, keep the manifest at the repository root, and add the codevisor-plugin topic. The repository owner must match the owner segment of the manifest ID. The public index refreshes periodically and reports rejected repositories with a reason.

The Plugins API covers registry search, discovery, installation, linking, runtime state, pane tokens, and direct tool invocation.

Experimental protocol

The plugin protocol is new and can change between Codevisor releases. Pin the release you build and test against.

On this page