docs: frame plugin validation as general guidance

This commit is contained in:
Edward Frazer
2026-05-15 14:26:54 -07:00
parent 80d9c99d9e
commit 10f245ea07
5 changed files with 24 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
---
name: plugin-creator
description: Create and scaffold plugin directories for Codex with a required `.codex-plugin/plugin.json`, optional plugin folders/files, share-safe manifest defaults, and personal-marketplace entries by default. Use when Codex needs to create a new personal plugin, add optional plugin structure, or generate or update marketplace entries for plugin ordering and availability metadata.
description: Create and scaffold plugin directories for Codex with a required `.codex-plugin/plugin.json`, optional plugin folders/files, valid manifest defaults, and personal-marketplace entries by default. Use when Codex needs to create a new personal plugin, add optional plugin structure, or generate or update marketplace entries for plugin ordering and availability metadata.
---
# Plugin Creator
@@ -18,7 +18,7 @@ python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py <plugin-nam
```
2. Edit `<plugin-path>/.codex-plugin/plugin.json` when the request gives specific metadata.
The scaffold starts with share-safe defaults and must not contain `[TODO: ...]` placeholders.
The scaffold starts with valid defaults and must not contain `[TODO: ...]` placeholders.
3. Generate or update the personal marketplace entry when the plugin should appear in Codex UI ordering:
@@ -47,7 +47,7 @@ python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py my-plugin \
`<parent-plugin-directory>` is the directory where the plugin folder `<plugin-name>` will be created (for example `~/code/plugins`).
5. Before handing back a plugin intended for sharing, run:
5. Before handing back a generated plugin, run:
```bash
python3 .agents/skills/plugin-creator/scripts/validate_plugin.py <plugin-path>
@@ -59,7 +59,7 @@ python3 .agents/skills/plugin-creator/scripts/validate_plugin.py <plugin-path>
`~/.agents/plugins/marketplace.json`.
- Creates plugin root at `/<parent-plugin-directory>/<plugin-name>/`.
- Always creates `/<parent-plugin-directory>/<plugin-name>/.codex-plugin/plugin.json`.
- Fills the manifest with the workspace-share schema shape that the upload service accepts.
- Fills the manifest with the validated schema shape that the ingestion path accepts.
- Creates or updates `~/.agents/plugins/marketplace.json` when `--with-marketplace` is set.
- If the marketplace file does not exist yet, seed a personal marketplace root before adding the first plugin entry.
- `<plugin-name>` is normalized using skill-creator naming rules:
@@ -149,9 +149,9 @@ python3 .agents/skills/plugin-creator/scripts/validate_plugin.py <plugin-path>
- Outer folder name and `plugin.json` `"name"` are always the same normalized plugin name.
- Do not remove required structure; keep `.codex-plugin/plugin.json` present.
- Do not leave `[TODO: ...]` placeholders in plugin manifests that may be shared.
- Do not leave `[TODO: ...]` placeholders in plugin manifests.
- Keep `apps` and `mcpServers` out of `plugin.json` unless their companion files are actually created.
- Omit unsupported plugin manifest fields that workspace sharing rejects, including `hooks`.
- Omit unsupported plugin manifest fields that validation rejects, including `hooks`.
- If creating files inside an existing plugin path, use `--force` only when overwrite is intentional.
- Preserve any existing marketplace `interface.displayName`.
- When generating marketplace entries, always write `policy.installation`, `policy.authentication`, and `category` even if their values are defaults.
@@ -184,7 +184,7 @@ After editing `SKILL.md`, run:
python3 <path-to-skill-creator>/scripts/quick_validate.py .agents/skills/plugin-creator
```
Before sharing a generated plugin, run:
Before handing back a generated plugin, run:
```bash
python3 .agents/skills/plugin-creator/scripts/validate_plugin.py <plugin-path>

View File

@@ -1,6 +1,6 @@
interface:
display_name: "Plugin Creator"
short_description: "Scaffold plugins and marketplace entries"
default_prompt: "Use $plugin-creator to scaffold a share-safe plugin in the personal marketplace, then validate it before sharing."
default_prompt: "Use $plugin-creator to scaffold a valid plugin in the personal marketplace, then validate it before handing it back."
icon_small: "./assets/plugin-creator-small.svg"
icon_large: "./assets/plugin-creator.png"

View File

@@ -172,10 +172,11 @@ personal marketplace unless the caller explicitly requests a repo-local destinat
- Personal plugin: `~/.agents/plugins/marketplace.json`
- Repo/team plugin: `<repo-root>/.agents/plugins/marketplace.json`
### Workspace sharing notes
### Plugin validation notes
- The share upload path validates plugin manifests against the workspace plugin ingestion schema.
- Shareable plugin manifests must include real values for `name`, `version`, `description`,
- The validator mirrors the workspace plugin ingestion schema so generated plugins follow the same
manifest contract from the start.
- Plugin manifests must include real values for `name`, `version`, `description`,
`author.name`, and the required `interface` fields.
- `version` must use strict semver.
- `websiteURL`, `privacyPolicyURL`, and `termsOfServiceURL` must be absolute `https://` URLs when
@@ -184,7 +185,7 @@ personal marketplace unless the caller explicitly requests a repo-local destinat
present.
- `apps` and `mcpServers` should appear in `plugin.json` only when `.app.json` and `.mcp.json`
actually exist.
- Workspace sharing rejects unsupported manifest fields such as `hooks`, so the scaffold keeps them
out of the shareable manifest.
- Run `scripts/validate_plugin.py <plugin-path>` before sharing. It mirrors the workspace ingestion
contract and adds one intentional preflight check that rejects leftover `[TODO: ...]` placeholders.
- Validation rejects unsupported manifest fields such as `hooks`, so the scaffold keeps them out of
generated manifests.
- Run `scripts/validate_plugin.py <plugin-path>` before handing back a generated plugin. It adds one
intentional preflight check that rejects leftover `[TODO: ...]` placeholders.

View File

@@ -172,7 +172,7 @@ def create_stub_file(path: Path, payload: dict, force: bool) -> None:
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Create a plugin skeleton with a share-safe plugin.json."
description="Create a plugin skeleton with a validation-ready plugin.json."
)
parser.add_argument("plugin_name")
parser.add_argument(

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Validate a generated plugin against the workspace share ingestion contract."""
"""Validate a generated plugin against the plugin ingestion contract."""
from __future__ import annotations
@@ -26,9 +26,7 @@ HEX_COLOR_RE = re.compile(r"^#[0-9A-F]{6}$", re.IGNORECASE)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Validate a local plugin before sharing it from Codex."
)
parser = argparse.ArgumentParser(description="Validate a local Codex plugin.")
parser.add_argument("plugin_path", help="Path to the plugin root directory")
return parser.parse_args()
@@ -42,7 +40,7 @@ def main() -> None:
for error in errors:
print(f"- {error}")
raise SystemExit(1)
print(f"Plugin is ready for workspace sharing: {plugin_root}")
print(f"Plugin validation passed: {plugin_root}")
def validate_plugin(plugin_root: Path) -> list[str]:
@@ -110,7 +108,7 @@ def validate_manifest_shape(
"keywords",
}
for key in sorted(set(manifest) - allowed_keys):
errors.append(f"plugin.json field `{key}` is not accepted by workspace sharing")
errors.append(f"plugin.json field `{key}` is not accepted by plugin validation")
validate_optional_non_empty_string(manifest, "id", errors)
require_non_empty_string(manifest, "name", errors)
@@ -256,7 +254,7 @@ def reject_unknown_fields(
errors: list[str],
) -> None:
for key in sorted(set(payload) - allowed_keys):
errors.append(f"plugin.json field `{prefix}.{key}` is not accepted by workspace sharing")
errors.append(f"plugin.json field `{prefix}.{key}` is not accepted by plugin validation")
def validate_optional_https_url(
@@ -357,7 +355,7 @@ def reject_companion_unknown_fields(
errors: list[str],
) -> None:
for key in sorted(set(payload) - allowed_keys):
errors.append(f"{prefix} field `{key}` is not accepted by workspace sharing")
errors.append(f"{prefix} field `{key}` is not accepted by plugin validation")
def validate_skill_manifests(plugin_root: Path, errors: list[str]) -> None:
@@ -542,7 +540,7 @@ def reject_skill_agent_unknown_fields(
for key in sorted(set(payload) - allowed_keys):
field = f"{prefix}.{key}" if prefix is not None else key
errors.append(
f"skill `{skill_root.name}` agent field `{field}` is not accepted by workspace sharing"
f"skill `{skill_root.name}` agent field `{field}` is not accepted by plugin validation"
)