Plugin Author Helper
Design, validate, and ship a Plugin that fits into the Codex (or compatible) Plugin
ecosystem. Mirrors the design of codex-rs/core-plugins/.
When to use
Activate when:
- Writing a new
plugin.json/marketplace.jsonmanifest. - Choosing between manifest formats (Legacy vs AgentPlugin).
- Validating a plugin before publish.
- Designing install / remove / upgrade / sync flows.
- Picking a plugin scope (User / System / Admin / Plugin) and pinning a version.
When NOT to use
- Single skill authoring → use
skill-auto-select. - Pure MCP server (no Skill bundle) → use the
mcp-servercrate's own conventions. - One-off tool scripts → don't package.
Host runtime requirements
This Skill describes how to design a Plugin (manifest, idempotency, scope). It does not cause the agent to install, modify, or publish anything on its own. Specifically, the agent MUST NOT, on the strength of this Skill alone:
- Run
npm install/npm link/ any package manager command for the user. - Write or overwrite files in
~/.minimax/.../plugins/,~/.codex/.../,~/.config/, or any other user-level config directory. - Hit a marketplace endpoint (download, install, upgrade) on the user's behalf.
- Trigger a plugin sync that reaches the network (3-layer fallback in Codex is a Codex-runtime concept; MiniMax Code may or may not have an equivalent).
All of the above require explicit user confirmation in the host's normal
permission flow (approval_policy, ask mode, or whatever the host uses).
This Skill is for designing the manifest / sync flow, not for executing it.
The agent that runs the install / sync must follow the host's user-confirmation
policy, not the patterns in this Skill.
Process
1. Pick the manifest format
Codex supports two manifest formats:
| Format | Path | Notes |
|---|---|---|
Legacy |
.claude-plugin/marketplace.json / .cursor-plugin/marketplace.json |
Older ecosystems. |
AgentPlugin |
.agents/plugins/marketplace.json / .agents/plugins/api_marketplace.json |
Current Codex format. |
If the plugin should be cross-ecosystem (OpenAI + Claude + Cursor), ship both manifests and let the loader pick whichever it finds first.
2. Write the 8-field PluginManifest
struct RawPluginManifest {
name: String, // required
version: Option<String>, // semver recommended
description: Option<String>, // one-line
keywords: Vec<String>, // tags
skills: Option<RawPluginManifestPaths>, // "./skills/<id>/SKILL.md" (./... required)
mcp_servers: Option<RawPluginManifestMcpServers>, // MCP config
apps: Option<String>, // apps connector
hooks: Option<RawPluginManifestHooks>, // 9 hook trigger points
interface: Option<RawPluginManifestInterface>, // UI (display_name, icon, brand_color)
}
Hard limits:
MAX_DEFAULT_PROMPT_COUNT: 3— at most 3 default prompts ininterface.MAX_DEFAULT_PROMPT_LEN: 128— each prompt ≤ 128 chars.- All paths in
skillsMUST use the./...syntax (./skills/<id>/SKILL.md) and resolve under the plugin root.
3. Provide a manifest fallback
If the main manifest is missing or malformed, fall back to a known-good shape. The
fallback typically contains just name + version + a minimal skills list.
4. Use a 3-letter marketplace name taxonomy
Pick a short, descriptive name with one of these prefixes:
| Prefix | Meaning |
|---|---|
openai-curated |
OpenAI-curated official |
openai-api-curated |
OpenAI API curated |
openai-bundled |
Bundled with Codex |
openai-bundled-alpha |
Bundled alpha |
openai-primary-runtime |
Primary runtime |
For your own marketplace, use <author>-<purpose> (e.g. acme-data-pipelines).
5. Use idempotency keys for create operations
pub struct CreateProjectParams {
pub name: String,
pub idempotency_key: String, // ← critical
// ...
}
pub struct CreatedProject {
pub project: StoredProject,
pub created: bool, // true = new, false = idempotent hit
}
Always require idempotency_key on create / install endpoints. The same key + same payload returns the existing object with created: false. Different key + same name creates a new object (no conflict).
6. Three-state updates: Option<Option<T>>
For partial-update APIs:
None— "do not touch this field".Some(None)— "set this field to null/empty".Some(Some(value))— "set this field to value".
This is the only correct encoding for "no change vs explicit clear" in JSON.
7. Report moved vs unchanged
pub enum ProjectMoveOutcome { Moved, Unchanged }
Reorder APIs should return whether the operation actually moved anything. UI uses this to skip re-renders on no-ops.
8. Use a BTreeMap for metadata
Stable iteration order = stable output. Don't use HashMap for user-visible metadata.
9. Three-layer startup sync
When the marketplace needs to refresh plugins at every Codex startup, use this 3-layer fallback:
1) GitHub API → GET /repos/openai/plugins/git/refs/codex/curated-sync
compare SHA against .tmp/plugins.sha
if changed, download + extract
2) Backend archive fallback → GET /backend-api/plugins/export/curated
3) Git clone → git clone https://github.com/openai/plugins.git --branch refs/codex/curated-sync
Each layer has a 30s timeout. Use a lock file (.tmp/plugins.sync.lock) to prevent
concurrent syncs from multiple Codex processes. Use a SHA cache (.tmp/plugins.sha)
to skip work when nothing changed. Stale temp dirs (older than 10 min) are auto-cleaned.
10. Decide a scope per skill within the plugin
Each skill in your plugin should be User (user-installed) / System (bundled) / Plugin (this plugin) scoped. Document the scope in the frontmatter metadata.scope field.
Output contract
A plugin that follows this design:
- Has both
plugin.json(AgentPlugin) AND a fallback manifest. - Has
name/version/description/keywords/skills/mcp_servers/apps/hooks/interfaceset. - Default prompts ≤ 3 entries, each ≤ 128 chars.
- All skill paths use the
./...syntax. - Has a marketplace name following the prefix taxonomy.
- All create / install endpoints require an
idempotency_key. - Uses
BTreeMapfor any user-visible metadata. - If a startup sync is needed, it uses a 3-layer fallback with a lock file and SHA cache.
Common pitfalls
- No idempotency key → user retries after a network blip create duplicates. Always require it.
HashMapfor metadata → JSON output flickers on every render. UseBTreeMap.- Two syncs in parallel → file corruption. Lock file mandatory.
- Forgetting fallback layer → GitHub outage takes down all installs. Always have the archive + clone as backups.
- Default prompts > 3 or > 128 chars → silently truncated. Stay under the limit.
- Skill paths not starting with
./→ resolution fails. Always use./skills/.../SKILL.md. Option<T>instead ofOption<Option<T>>→ cannot distinguish "no change" from "set to null".
Example — minimal plugin manifest
{
"$schema": "https://agent-plugins.org/schemas/1.0/plugin.schema.json",
"name": "acme-data-pipelines",
"version": "0.1.0",
"description": "Data pipeline skills for ETL, schema validation, and warehouse sync.",
"keywords": ["data", "etl", "pipeline"],
"skills": "./skills/*/SKILL.md",
"mcp_servers": {
"warehouse": {
"transport": "stdio",
"command": "./bin/warehouse-mcp"
}
},
"interface": {
"display_name": "ACME Data Pipelines",
"brand_color": "#0066cc"
}
}
Verification checklist
- Manifest has all 8 fields;
nameis set. - Default prompts ≤ 3 entries, each ≤ 128 chars.
- All skill paths use the
./...syntax. - Manifest fallback present.
- Marketplace name follows the prefix taxonomy.
- All create / install endpoints require an
idempotency_key. - Partial updates use
Option<Option<T>>for 3-state. - Reorder APIs return
Moved/Unchanged. - User-visible metadata uses
BTreeMapnotHashMap. - If startup sync is used: 3-layer fallback, lock file, SHA cache, 30s timeout, 10-min stale cleanup.