diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 21061fc85ec..e0d08b72de1 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -2007,6 +2007,12 @@ "fileMatch": ["**/.codex/hooks.json"], "url": "https://www.schemastore.org/codex-hooks.json" }, + { + "name": "Codex Plugin Manifest", + "description": "OpenAI Codex plugin manifest file", + "fileMatch": ["**/.codex-plugin/plugin.json"], + "url": "https://www.schemastore.org/codex-plugin-manifest.json" + }, { "name": "Codex Skill Metadata", "description": "OpenAI Codex skill metadata file", diff --git a/src/negative_test/codex-plugin-manifest/invalid-logo-dark-type.json b/src/negative_test/codex-plugin-manifest/invalid-logo-dark-type.json new file mode 100644 index 00000000000..82a167916e2 --- /dev/null +++ b/src/negative_test/codex-plugin-manifest/invalid-logo-dark-type.json @@ -0,0 +1,18 @@ +{ + "author": { + "name": "OpenAI" + }, + "description": "Bad plugin", + "interface": { + "capabilities": ["Interactive"], + "category": "Productivity", + "defaultPrompt": "Use $bad-plugin.", + "developerName": "OpenAI", + "displayName": "Bad Plugin", + "logoDark": false, + "longDescription": "A plugin manifest with an invalid dark logo value.", + "shortDescription": "Bad dark logo" + }, + "name": "bad-plugin", + "version": "1.0.0" +} diff --git a/src/negative_test/codex-plugin-manifest/remote-logo-url-dark.json b/src/negative_test/codex-plugin-manifest/remote-logo-url-dark.json new file mode 100644 index 00000000000..7f4d94e2c61 --- /dev/null +++ b/src/negative_test/codex-plugin-manifest/remote-logo-url-dark.json @@ -0,0 +1,18 @@ +{ + "author": { + "name": "OpenAI" + }, + "description": "Bad plugin", + "interface": { + "capabilities": ["Interactive"], + "category": "Productivity", + "defaultPrompt": "Use $bad-plugin.", + "developerName": "OpenAI", + "displayName": "Bad Plugin", + "logoUrlDark": "https://example.com/logo-dark.png", + "longDescription": "Remote catalog dark logo fields are not local plugin manifest fields.", + "shortDescription": "Wrong dark logo field" + }, + "name": "bad-plugin", + "version": "1.0.0" +} diff --git a/src/negative_test/codex-plugin-manifest/unsupported-hooks-field.json b/src/negative_test/codex-plugin-manifest/unsupported-hooks-field.json new file mode 100644 index 00000000000..0b1bf82d7eb --- /dev/null +++ b/src/negative_test/codex-plugin-manifest/unsupported-hooks-field.json @@ -0,0 +1,18 @@ +{ + "author": { + "name": "OpenAI" + }, + "description": "Bad plugin", + "hooks": "./hooks.json", + "interface": { + "capabilities": ["Interactive"], + "category": "Productivity", + "defaultPrompt": "Use $bad-plugin.", + "developerName": "OpenAI", + "displayName": "Bad Plugin", + "longDescription": "The bundled validator rejects unsupported top-level hooks.", + "shortDescription": "Unsupported field" + }, + "name": "bad-plugin", + "version": "1.0.0" +} diff --git a/src/schemas/json/codex-plugin-manifest.json b/src/schemas/json/codex-plugin-manifest.json new file mode 100644 index 00000000000..79d52288fd9 --- /dev/null +++ b/src/schemas/json/codex-plugin-manifest.json @@ -0,0 +1,222 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/codex-plugin-manifest.json", + "$comment": "Sources: https://github.com/openai/codex/blob/main/codex-rs/plugin/src/manifest.rs and https://github.com/openai/codex/blob/main/codex-rs/skills/src/assets/samples/plugin-creator/scripts/validate_plugin.py", + "title": "Codex plugin manifest", + "description": "Manifest for an OpenAI Codex plugin.", + "type": "object", + "required": ["name", "version", "description", "author", "interface"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Optional plugin identifier." + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Plugin identifier." + }, + "version": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$", + "description": "Strict semantic version." + }, + "description": { + "type": "string", + "minLength": 1, + "description": "Brief plugin description." + }, + "skills": { + "$ref": "#/definitions/relativePath", + "description": "Relative path to skill directories or files." + }, + "apps": { + "$ref": "#/definitions/relativePath", + "description": "Relative path to the app manifest." + }, + "mcpServers": { + "description": "Relative path to an MCP manifest or inline MCP server configuration.", + "oneOf": [ + { + "$ref": "#/definitions/relativePath" + }, + { + "type": "object", + "additionalProperties": true + } + ] + }, + "interface": { + "$ref": "#/definitions/pluginInterface" + }, + "author": { + "type": "object", + "required": ["name"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "email": { + "type": "string", + "minLength": 1 + }, + "url": { + "$ref": "#/definitions/httpsUrl" + } + } + }, + "homepage": { + "$ref": "#/definitions/httpsUrl", + "description": "Plugin documentation URL." + }, + "repository": { + "$ref": "#/definitions/httpsUrl", + "description": "Plugin source repository URL." + }, + "license": { + "type": "string", + "minLength": 1 + }, + "keywords": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "definitions": { + "httpsUrl": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "minLength": 1 + }, + "relativePath": { + "type": "string", + "minLength": 1, + "not": { + "pattern": "^(?:[A-Za-z]:|/|\\\\\\\\)" + } + }, + "assetPath": { + "type": "string", + "minLength": 1, + "description": "Package-relative asset path." + }, + "defaultPrompt": { + "description": "Starter prompt shown by Codex clients.", + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "array", + "minItems": 1, + "maxItems": 3, + "items": { + "type": "string", + "minLength": 1 + } + } + ] + }, + "pluginInterface": { + "type": "object", + "required": [ + "displayName", + "shortDescription", + "longDescription", + "developerName", + "category", + "capabilities" + ], + "anyOf": [ + { + "properties": { + "defaultPrompt": true + }, + "required": ["defaultPrompt"] + }, + { + "properties": { + "default_prompt": true + }, + "required": ["default_prompt"] + } + ], + "additionalProperties": false, + "properties": { + "displayName": { + "type": "string", + "minLength": 1 + }, + "shortDescription": { + "type": "string", + "minLength": 1 + }, + "longDescription": { + "type": "string", + "minLength": 1 + }, + "developerName": { + "type": "string", + "minLength": 1 + }, + "category": { + "type": "string", + "minLength": 1 + }, + "capabilities": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "websiteURL": { + "$ref": "#/definitions/httpsUrl" + }, + "privacyPolicyURL": { + "$ref": "#/definitions/httpsUrl" + }, + "termsOfServiceURL": { + "$ref": "#/definitions/httpsUrl" + }, + "defaultPrompt": { + "$ref": "#/definitions/defaultPrompt" + }, + "default_prompt": { + "$ref": "#/definitions/defaultPrompt" + }, + "brandColor": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{6}$" + }, + "composerIcon": { + "$ref": "#/definitions/assetPath" + }, + "logo": { + "$ref": "#/definitions/assetPath" + }, + "logoDark": { + "$ref": "#/definitions/assetPath", + "description": "Optional package-relative logo asset used in dark mode." + }, + "screenshots": { + "type": "array", + "items": { + "$ref": "#/definitions/assetPath" + } + } + } + } + } +} diff --git a/src/test/codex-plugin-manifest/inline-mcp.json b/src/test/codex-plugin-manifest/inline-mcp.json new file mode 100644 index 00000000000..9010adb89e8 --- /dev/null +++ b/src/test/codex-plugin-manifest/inline-mcp.json @@ -0,0 +1,23 @@ +{ + "author": { + "name": "OpenAI" + }, + "description": "Plugin with inline MCP configuration", + "interface": { + "capabilities": ["Interactive"], + "category": "Productivity", + "default_prompt": "Use $inline-mcp-plugin to inspect the counter.", + "developerName": "OpenAI", + "displayName": "Inline MCP Plugin", + "longDescription": "A plugin manifest with inline MCP server configuration.", + "shortDescription": "Uses inline MCP server config" + }, + "mcpServers": { + "counter": { + "type": "http", + "url": "https://sample.example/counter/mcp" + } + }, + "name": "inline-mcp-plugin", + "version": "0.1.0" +} diff --git a/src/test/codex-plugin-manifest/plugin.json b/src/test/codex-plugin-manifest/plugin.json new file mode 100644 index 00000000000..f9428bea02a --- /dev/null +++ b/src/test/codex-plugin-manifest/plugin.json @@ -0,0 +1,37 @@ +{ + "apps": "./.app.json", + "author": { + "email": "plugins@example.com", + "name": "OpenAI", + "url": "https://openai.com" + }, + "description": "Demo Codex plugin", + "homepage": "https://example.com/plugin", + "interface": { + "brandColor": "#3B82F6", + "capabilities": ["Interactive", "Write"], + "category": "Productivity", + "composerIcon": "./assets/icon.png", + "defaultPrompt": [ + "Summarize my inbox.", + "Find open bugs and draft tickets." + ], + "developerName": "OpenAI", + "displayName": "Demo Plugin", + "logo": "./assets/logo.png", + "logoDark": "./assets/logo-dark.png", + "longDescription": "A demo plugin manifest used to validate the Codex plugin schema.", + "privacyPolicyURL": "https://example.com/privacy", + "screenshots": ["./assets/screenshot.png"], + "shortDescription": "Demo plugin for Codex", + "termsOfServiceURL": "https://example.com/terms", + "websiteURL": "https://example.com/plugin" + }, + "keywords": ["demo", "codex"], + "license": "MIT", + "mcpServers": "./.mcp.json", + "name": "demo-plugin", + "repository": "https://github.com/example/demo-plugin", + "skills": "./skills", + "version": "1.2.0" +}