CODEXIS AI
CODEXIS AI AgentPlatforma / Platform

Creating a Plugin

Build and publish your own plugin: the files you write, the manifest fields you set, and the lifecycle hooks and environment variables the platform provides.

This guide explains how to build a plugin for CODEXIS AI 2.0 and publish it so anyone can install it. It covers the files you write, the manifest fields you set, and the lifecycle hooks and environment variables the platform gives you.

The official marketplace is the best source of working examples. You won't add your plugin there (you publish your own, see below), but it makes a great reference: ares is the simplest "skill + one tool" plugin and codexis is the most complete.

A plugin is a folder under plugins/<name>/. It can contain any mix of skills (Markdown that tells the AI when and how to do something; the core of most plugins), command-line tools (small prebuilt programs the AI runs, shipped in bin/), lifecycle hooks (commands run on install/update/uninstall), components (embedded web dashboards), and automations (scheduled jobs). The smallest valid plugin is a plugin.json plus one SKILL.md; everything else is opt-in.

How plugins are distributed: your own marketplace

Plugins are always installed from a marketplace, a git repository that lists one or more plugins. There is no standalone, single-plugin install: even a single plugin ships as a marketplace with one entry.

You do not add your plugin to the official marketplace. You publish your own: a git repo containing a .claude-plugin/marketplace.json and a plugins/ folder.

your-marketplace-repo/
├── .claude-plugin/
│   └── marketplace.json     # lists your plugin(s)
└── plugins/
    └── your-plugin/         # the plugin itself (everything else in this guide)

Anyone can install it by adding your repository as a marketplace in CODEXIS: choosing a Git source, pasting the repository URL (optionally a branch or tag), then installing any plugin it lists. This needs no admin rights and no involvement from us; each user points CODEXIS at the repositories they trust.

Use a public repository

Private repositories with managed credentials are not supported; publish to a public git host. Updates are picked up when you push and the user refreshes the marketplace.

The rest of this guide describes the files inside plugins/<name>/ in your own repo.

Plugin format and supported components

CODEXIS plugins use the standard Claude Code plugin format: a .claude-plugin/plugin.json manifest at the plugin root, with component files in conventional directories beside it. Follow these conventions:

  • Markdown + YAML frontmatter for skill and agent definitions (JSON for hooks and MCP).
  • Lowercase, hyphen-separated names (kebab-case), no spaces, for the plugin and every component.
  • For skills and agents, the description is written for the model: it is what the AI reads to decide when to use them. Make it specific and dense with trigger words and phrases.
  • Keep SKILL.md short. Move the details into reference files the AI loads only when needed.
  • Use ${PLUGIN_DIR} to reference your own files from hooks (this platform's equivalent of Claude Code's ${CLAUDE_PLUGIN_ROOT}).

Not every Claude Code component type is loaded by this platform. What CODEXIS AI 2.0 actually reads:

ComponentLocationSupported
Skillsskills/<name>/SKILL.mdYes, the primary component
Subagentsagents/<name>.mdYes
Event hookshooks/hooks.jsonYes, SessionStart, Stop, PreToolUse, PostToolUse, PreCompact only
MCP servers.mcp.json (mcpServers)Yes (stdio / SSE / HTTP)
Slash commandscommands/*.mdNo, not loaded; don't ship these

Slash commands are not supported

A commands/ folder is copied to disk but never read. Don't rely on it; expose user actions as automations or as a command-line tool instead.

On top of the standard format, the platform adds lifecycle hooks (postInstall / postUninstall / onUpdate), a plugin-owned env block, host-injected CODEXIS_USER_* / CODEXIS_PUBLIC_* variables, trilingual i18n metadata, embedded dashboards described by component.json, and scheduled automations. Each is covered below.

Most plugins need only skills (plus a command-line tool in bin/). Agents, event hooks, and MCP servers are there when you need them.

The runtime your plugin gets

When a user installs your plugin, its folder is copied into that user's private Linux sandbox and your install hook runs there. Every command the AI later runs through your plugin runs in the same sandbox. The contract you can rely on:

  • It is Linux. Your binaries must be Linux builds for the sandbox architecture (amd64 or arm64).
  • ~/.local/bin is first on PATH and writable. Drop a tool there and the AI can call it by name, no full path, no sudo.
  • Hooks run in a real shell with the working directory set to your plugin folder, a 5-minute timeout, and $PLUGIN_DIR pointing at that folder.
  • The CODEXIS_* environment variables (see below) are available.
  • Outbound network access may be restricted. Administrators can limit the sandbox to approved destinations, so a tool that calls an external API may need its host allow-listed. Don't assume open internet access.
  • The home directory is shared storage (mounted over NFS), and selected folders from the user's machine or company file server are synced in. Your tools can read them as ordinary paths under $HOME.

Hook failure rules: a failing postInstall aborts the install; a failing postUninstall or onUpdate is logged but does not block the operation. A blank hook is a no-op.

Quick start: a skill-only plugin

The smallest useful plugin teaches the AI a workflow and ships no program. Three files:

plugins/hello-law/
├── .claude-plugin/
│   └── plugin.json
└── skills/
    └── hello-law/
        └── SKILL.md

plugins/hello-law/.claude-plugin/plugin.json:

{
  "name": "hello-law",
  "version": "1.0.0",
  "description": "Example plugin that explains how to greet a legal question.",
  "author": { "name": "Your Name", "email": "you@example.com" },
  "keywords": ["example"],
  "license": "PROPRIETARY",
  "tags": ["example"],
  "i18n": {
    "cs": { "displayName": "Hello Law", "description": "Ukázkový plugin.", "tagLabels": { "example": "Ukázka" } },
    "en": { "displayName": "Hello Law", "description": "Example plugin.",  "tagLabels": { "example": "Example" } },
    "sk": { "displayName": "Hello Law", "description": "Ukážkový plugin.", "tagLabels": { "example": "Ukážka" } }
  },
  "skills": "./skills"
}

plugins/hello-law/skills/hello-law/SKILL.md:

---
uuid: 00000000-0000-0000-0000-000000000001
name: hello-law
description: Use when the user asks to test the hello-law example plugin or says "hello law".
version: 1.0.0
i18n:
  cs: { displayName: "Hello Law", summary: "Ukázková dovednost." }
  en: { displayName: "Hello Law", summary: "Example skill." }
  sk: { displayName: "Hello Law", summary: "Ukážková zručnosť." }
---

# Hello Law

When the user asks you to "test hello law", confirm the example plugin is working. Do not call any
external tools.

Then register it in your .claude-plugin/marketplace.json (see below). That's a complete, installable plugin.

UUIDs

Every manifest and skill carries a uuid, a permanent unique id. Generate one with uuidgen and use a distinct value per plugin, per skill, and per marketplace entry.

Plugin folder layout

Only .claude-plugin/plugin.json is required; everything else is opt-in. This is the layout of codexis, the most complete plugin:

plugins/<name>/
├── .claude-plugin/
│   └── plugin.json          # REQUIRED: the manifest
├── icon.svg                 # marketplace icon
├── README.md                # human-facing description
├── skills/                  # one folder per skill
│   └── <skill>/
│       ├── SKILL.md
│       ├── icon.svg         # optional
│       └── references/      # optional docs loaded on demand
├── agents/                  # optional subagents (one .md per agent)
├── bin/                     # prebuilt tools (committed)
├── hooks/                   # install / uninstall scripts (+ optional hooks.json event hooks)
├── .mcp.json                # optional MCP server definitions
├── lib/                     # shared library code your tools/components import
├── components/              # embedded web dashboards (advanced)
└── automations/             # scheduled jobs

Complexity tiers, by example: skills only (visualization) is just plugin.json + skills/; skill + one tool (ares) adds bin/, hooks/, and lifecycle hooks; everything (codexis) adds components/, automations/, and lib/.

Plugins ship in built form

A tool ships as a prebuilt binary committed under bin/; a component ships as its built front-end. How you produce those artifacts (compiler, bundler) is up to you and lives in your own source repo; only the built output goes into the plugin folder.

The two manifests

.claude-plugin/marketplace.json (your marketplace repo root)

The manifest of your own marketplace. List each plugin in the plugins array. The manifest is permissive: name and uuid are derived automatically when omitted, and plugins are also auto-discovered from the plugins/ folder, but we recommend listing each plugin explicitly, i18n included:

{
  "uuid": "GENERATE-A-NEW-UUID",
  "name": "your-plugin",
  "description": "One-line English description.",
  "source": "./plugins/your-plugin",
  "category": "legal",
  "i18n": {
    "cs": { "displayName": "Český název", "description": "Český popis.", "tagLabels": { "legal": "Právo" } },
    "en": { "displayName": "English name", "description": "English description.", "tagLabels": { "legal": "Law" } },
    "sk": { "displayName": "Slovenský názov", "description": "Slovenský popis.", "tagLabels": { "legal": "Právo" } }
  }
}
  • name must match the name in plugin.json and the folder in source.
  • source is always ./plugins/<name>.
  • category places the plugin in a category. Existing categories: legal, visualization, document-processing, media, open-data.
  • tagLabels localizes the tag keys declared in the tags field of plugin.json.

plugins/<name>/.claude-plugin/plugin.json

Describes your plugin. name, version, and description are required; add the rest as needed:

{
  "name": "your-plugin",
  "version": "1.0.0",
  "description": "English description used for discovery.",
  "author": { "name": "Your Name", "email": "you@example.com" },
  "keywords": ["search", "terms"],
  "license": "PROPRIETARY",
  "tags": ["legal", "czech"],
  "i18n": {
    "cs": { "displayName": "...", "description": "...", "tagLabels": { "legal": "Právo", "czech": "Česko" } },
    "en": { "displayName": "...", "description": "...", "tagLabels": { "legal": "Law",   "czech": "Czech Republic" } },
    "sk": { "displayName": "...", "description": "...", "tagLabels": { "legal": "Právo", "czech": "Česko" } }
  },

  "skills": "./skills",
  "components": "./components",

  "env": {
    "CODEXIS_PLUGIN_YOURPLUGIN_API_URL": "https://api.example.com"
  },

  "postInstall":   "bash \"${PLUGIN_DIR}/hooks/install-binaries.sh\"",
  "postUninstall": "bash \"${PLUGIN_DIR}/hooks/uninstall-binaries.sh\"",
  "onUpdate":      "bash \"${PLUGIN_DIR}/hooks/install-binaries.sh\""
}
FieldRequired?Purpose
name, version, descriptionyesIdentity and discovery. version is MAJOR.MINOR.PATCH.
author, keywords, license, tagsrecommendedMarketplace metadata and search.
i18nrecommendedLocalized display strings (see Translations).
skillsif shipping skillsAlways "./skills".
componentsoptional"./components" if shipping dashboards.
envoptionalPlugin config variables (see Environment variables).
postInstall / postUninstall / onUpdateif shipping a toolLifecycle hooks (see below).

Writing a SKILL.md

A skill is a Markdown file: a frontmatter block (between --- lines) followed by instructions written for the AI.

---
uuid: 214caaa4-3728-4d21-b379-ab4b376b7615
name: ares
description: Use for company / sole-trader lookups by IČO or name. Triggers on "ares", "ičo", "obchodní rejstřík", "živnostenský rejstřík", "plátce DPH", "vyhledej firmu", "kdo je jednatel".
version: 0.1.0
i18n:
  cs: { displayName: "ARES - registr ekonomických subjektů", summary: "Vyhledávání firem a OSVČ v ARES." }
  en: { displayName: "ARES - Czech Business Registry", summary: "Look up companies and sole traders in ARES." }
  sk: { displayName: "ARES - register ekonomických subjektov", summary: "Vyhľadávanie firiem a SZČO v ARES." }
---

# ARES - Czech Business Registry

A single tool, `ares-cli`, wraps the ARES public REST API. Assume it is installed and on `PATH`.
Do NOT call `curl` or any other tool directly.

## Commands
...

description is the field that matters most. It is not shown to users; it is what the AI reads to decide when to load the skill. Write it as an instruction packed with trigger words and phrases, not as a vague summary.

The body is the playbook: which tool to call, the exact command syntax, how to parse the output, and what to show the user. Be prescriptive: give example commands and a decision tree.

Frontmatter fieldRequired?Purpose
uuidyesPermanent unique id.
nameyesLowercase-hyphenated; matches the folder name.
descriptionyesAI trigger text. Never translated.
versionrecommendedSemantic version.
i18nrecommendeddisplayName + summary per language, shown in the UI skills list.
allowed-toolsoptionalRestricts the skill to specific tools, e.g. allowed-tools: shell.

Keep SKILL.md focused. Put the long details in skills/<name>/references/*.md and have the skill point the AI there only when needed.

Shipping a tool: bin/ and lifecycle hooks

If your plugin runs a command-line tool, you ship it as a prebuilt binary and install it onto the sandbox's PATH with a lifecycle hook.

The binary

Commit a self-contained Linux executable at plugins/<name>/bin/<tool>. Any language that compiles to a static Linux binary works; existing plugins use Rust. The tool reads its inputs and prints results (ideally JSON) to stdout.

Installing it onto PATH

An install hook has just one job: copy your binary into ~/.local/bin, which is already on PATH. For a single binary you don't even need a script; inline the commands in plugin.json (this is what the ocr plugin does). ${PLUGIN_DIR} resolves to your installed plugin folder:

  "postInstall":   "sudo install -m 0755 \"${PLUGIN_DIR}/bin/my-tool\" \"${HOME}/.local/bin/my-tool\"",
  "postUninstall": "sudo rm -f \"${HOME}/.local/bin/my-tool\"",
  "onUpdate":      "sudo install -m 0755 \"${PLUGIN_DIR}/bin/my-tool\" \"${HOME}/.local/bin/my-tool\""

That's the whole mechanism. The platform only cares that your tool ends up on PATH.

Copy the ares plugin

If you ship several binaries (or copy support files as well), move the same steps into a small hooks/install-binaries.sh / hooks/uninstall-binaries.sh pair and point the hooks at them. The cleanest example to copy is plugins/ares/hooks. Its script installs one binary; to adapt it, change a single line: the binary name.

The three lifecycle hooks

HookRunsOn failure
postInstallafter installaborts the install
onUpdateafter an update to a new versionlogged, non-fatal
postUninstallafter removallogged, non-fatal

onUpdate normally just repeats postInstall. Use ${PLUGIN_DIR} to reference your plugin's files; it is this platform's equivalent of Claude Code's ${CLAUDE_PLUGIN_ROOT}.

Environment variables

Your hooks and every command the AI runs see three families of variables.

CODEXIS_PLUGIN_* (you declare these)

Your plugin's own non-secret config (URLs, IDs), declared in the env block of plugin.json:

  "env": {
    "CODEXIS_PLUGIN_YOURPLUGIN_API_URL": "https://api.example.com"
  }

Prefix the name with CODEXIS_PLUGIN_ and make the rest unique to your plugin so it never collides with another plugin's variable. No secrets here; the value lives in the manifest. Declare config in env; don't write .env files or use export inside hooks.

CODEXIS_USER_* (injected secrets, read-only)

Per-user secrets the platform provides. Read them; never log them or bake them into a binary.

VariableWhat it is
CODEXIS_USER_API_TOKENThe user's Codexis authorization token (when available).
CODEXIS_USER_LITELLM_API_KEYPer-user key for the AI gateway.

CODEXIS_PUBLIC_* (injected context, read-only)

Non-secret runtime context.

VariableWhat it is
CODEXIS_PUBLIC_DAEMON_URLBackend GraphQL endpoint.
CODEXIS_PUBLIC_USER_HOMEThe user's home directory.
CODEXIS_PUBLIC_LITELLM_BASE_URLAI gateway base URL.
CODEXIS_PUBLIC_SESSION_IDCurrent chat session id (only during a chat).
CODEXIS_PUBLIC_TOOL_CALL_IDCurrent tool-call id (only during a chat).
CODEXIS_PUBLIC_AUTOMATION, …_AUTOMATION_ID, …_AUTOMATION_RUN_ID, …_AUTOMATION_TRIGGERSet when running inside a scheduled automation.

Install/uninstall/update hooks run outside a chat, so they get the platform URL, user home, and secrets, but not SESSION_ID / TOOL_CALL_ID.

Translations (i18n)

The UI is available in Czech (cs), English (en), and Slovak (sk). Every manifest and skill carries an i18n block.

  • Technical identifiers (name, id, tag keys) are never translated.
  • A skill's description is never translated; it's the AI trigger, not UI text.
  • Only display strings are translated: displayName, the manifest description, the skill summary, and tagLabels.

JSON manifests:

"i18n": {
  "cs": { "displayName": "...", "description": "...", "tagLabels": { "legal": "Právo" } },
  "en": { "displayName": "...", "description": "...", "tagLabels": { "legal": "Law" } },
  "sk": { "displayName": "...", "description": "...", "tagLabels": { "legal": "Právo" } }
}

SKILL.md frontmatter (note summary, not description):

i18n:
  cs: { displayName: "...", summary: "..." }
  en: { displayName: "...", summary: "..." }
  sk: { displayName: "...", summary: "..." }

Missing translations fall back in the order requested language → en → the untranslated name/description. Fill in en at minimum; cs and sk are strongly recommended.

Optional: subagents, event hooks, MCP, components, automations

Subagents (agents/<name>.md)

A subagent is a specialized assistant the AI can delegate work to. Ship one as a Markdown file under agents/, with frontmatter plus a system-prompt body:

---
name: contract-reviewer
description: Use to review a contract draft for missing clauses and risky terms.
tools: Read, Grep
model: sonnet
---

You are a contract reviewer. Analyze the provided draft for missing standard clauses, ambiguous
terms, and one-sided liability. Report findings as a prioritized list.

As with skills, description is the model-facing trigger. name is lowercase with hyphens. The agents/ folder is discovered automatically; no manifest key is needed.

Event hooks (hooks/hooks.json)

Event hooks run a command automatically on chat events (unlike the lifecycle hooks above, which run at install time). Put them in hooks/hooks.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "shell",
        "hooks": [
          { "type": "command", "command": "bash \"${PLUGIN_DIR}/hooks/guard.sh\"", "timeout": 30 }
        ]
      }
    ]
  }
}

matcher is a regex matched against the tool name; the hook command receives a JSON payload on stdin and runs in the sandbox. This platform supports only these five events: SessionStart, Stop, PreToolUse, PostToolUse, PreCompact.

MCP servers (.mcp.json)

To expose external tools through the Model Context Protocol, ship an .mcp.json (or .claude-plugin/.mcp.json) with an mcpServers map:

{
  "mcpServers": {
    "my-server": {
      "command": "${PLUGIN_DIR}/bin/my-mcp-server",
      "args": ["--stdio"]
    }
  }
}

The stdio (a process spawned in the sandbox), SSE, and streamable HTTP transports are all supported; the server's tools appear in the AI's toolkit automatically. A server that fails to start degrades gracefully to an "unavailable" notice rather than breaking the chat.

Components: embedded web dashboards

A component is a web app shown inside the product UI. Declare "components": "./components", give each component a components/<name>/component.json, and ship the built front-end (index.html, assets/, locales/) plus an optional backing script:

{
  "id": "katastr",
  "title": "Katastr - Sledovaná řízení",
  "icon": "assets/icon.png",
  "route": "katastr",
  "description": "Track the status of cadastral proceedings.",
  "entrypoint": "index.html",
  "binary": "katastr.py",
  "i18n": {
    "cs": { "displayName": "Katastr - sledovaná řízení", "description": "..." },
    "en": { "displayName": "Cadastre - Tracked Proceedings", "description": "..." },
    "sk": { "displayName": "Kataster - sledované konania", "description": "..." }
  }
}

Automations: scheduled jobs

An automation is a Markdown file in automations/ that contains only frontmatter:

---
uuid: b7c2f1a0-4e3d-4a6b-9c8e-2f5a1d0b6e74
type: COMMAND
title: Sync Codexis 1.0
description: Import your chats, agents and files from Codexis 1.0.
command: cdxctl codexis sync
cron: 0 3 * * *
enabled: true
---

command runs on the cron schedule (here, daily at 03:00).

References: on-demand skill docs

Extra Markdown files under skills/<name>/references/. Keep SKILL.md short and have it point the AI at a reference file only when needed (e.g. "For the full workflow, read references/czech-law-change-assessment.md").

Checklist

  • You have your own git repo with a .claude-plugin/marketplace.json and a plugins/ folder.
  • The plugin folder is plugins/<name>/, with <name> lowercase-hyphenated.
  • plugin.json has name, version, and description; name matches both the folder and the marketplace.json entry.
  • The plugin has a marketplace.json entry with a freshly generated uuid.
  • Each skill has its own SKILL.md with a unique uuid, a name, and a keyword-rich description.
  • If you ship a tool: the prebuilt Linux binary is committed at bin/<tool>, the hooks are wired up in plugin.json, and the binary is installed into ~/.local/bin.
  • Config lives in env as CODEXIS_PLUGIN_* variables; secrets are read from CODEXIS_USER_*, never hard-coded.
  • i18n is filled in (en at minimum; cs and sk preferred).
  • Everything is committed and pushed to a public git host; users install by adding the repo URL as a Git marketplace.

On this page