From 4b682b4b54a71b55ee4d0d3b10d1db53e9fbde26 Mon Sep 17 00:00:00 2001 From: sparsh-deriv Date: Thu, 3 Sep 2026 12:13:49 +0400 Subject: [PATCH] Add Codex listing metadata and a dark-mode wordmark. Codex reads .codex-plugin/plugin.json with relative branding paths, not the Cursor HTTPS logo URL. Ship logoDark plus the required interface, legal, and MCP fields. Co-authored-by: Cursor --- .agents/plugins/marketplace.json | 20 ++++ .codex-plugin/plugin.json | 45 +++++++++ .github/workflows/verify-surface.mjs | 134 ++++++++++++++++++++++++++- README.md | 10 +- assets/logo-dark.png | Bin 0 -> 7508 bytes assets/logo-dark.svg | 6 ++ 6 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .codex-plugin/plugin.json create mode 100644 assets/logo-dark.png create mode 100644 assets/logo-dark.svg diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..b94a90f --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "deriv-api-marketplace", + "interface": { + "displayName": "Deriv API" + }, + "plugins": [ + { + "name": "deriv-api", + "source": { + "source": "local", + "path": "./" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..3a7b4b1 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,45 @@ +{ + "name": "deriv-api", + "version": "2.0.2", + "description": "Get live Deriv API schemas plus guides for authentication, subscriptions, trades, and errors.", + "author": { + "name": "Deriv", + "email": "api-support@deriv.com", + "url": "https://developers.deriv.com" + }, + "homepage": "https://developers.deriv.com", + "repository": "https://github.com/deriv-com/deriv-api-plugin", + "license": "MIT", + "keywords": [ + "deriv", + "trading", + "api", + "websocket", + "rest", + "options", + "finance", + "fintech" + ], + "mcpServers": "./.mcp.json", + "interface": { + "displayName": "Deriv API (Beta)", + "shortDescription": "Get live Deriv API schemas", + "longDescription": "Get live Deriv API schemas plus guides for authentication, subscriptions, trades, and errors. Search endpoints, read current request and response fields, take worked examples, and validate payloads. The plugin does not sign in to a Deriv account, store keys, or place trades.", + "developerName": "Deriv", + "category": "Developer Tools", + "capabilities": ["Read"], + "websiteURL": "https://developers.deriv.com", + "privacyPolicyURL": "https://github.com/deriv-com/deriv-api-plugin/blob/master/PRIVACY.md", + "termsOfServiceURL": "https://github.com/deriv-com/deriv-api-plugin/blob/master/LICENSE", + "supportURL": "https://github.com/deriv-com/deriv-api-plugin/issues", + "brandColor": "#FF444F", + "defaultPrompt": [ + "Look up the live request and response schema for a Deriv API endpoint", + "Validate this Deriv API request payload against the live schema", + "Explain Deriv API authentication, subscriptions, and error handling" + ], + "composerIcon": "./assets/logo.png", + "logo": "./assets/logo.png", + "logoDark": "./assets/logo-dark.png" + } +} diff --git a/.github/workflows/verify-surface.mjs b/.github/workflows/verify-surface.mjs index befc79e..089e0be 100644 --- a/.github/workflows/verify-surface.mjs +++ b/.github/workflows/verify-surface.mjs @@ -28,7 +28,7 @@ // Contract: exit 0 when both checks pass; exit non-zero and print `FAIL:` lines // otherwise. -import { readFileSync, readdirSync } from 'node:fs'; +import { existsSync, readFileSync, readdirSync } from 'node:fs'; import { join, relative, sep } from 'node:path'; import { execFileSync } from 'node:child_process'; @@ -39,9 +39,13 @@ const ALLOW = new Set([ '.cursor-plugin/marketplace.json', '.claude-plugin/plugin.json', '.claude-plugin/marketplace.json', + '.codex-plugin/plugin.json', + '.agents/plugins/marketplace.json', 'rules/deriv-api-conventions.mdc', 'assets/logo.svg', 'assets/logo.png', + 'assets/logo-dark.svg', + 'assets/logo-dark.png', 'LICENSE', 'README.md', 'CONTRIBUTING.md', @@ -173,6 +177,134 @@ for (const path of paths) { } } +// 4. Codex listing contract. Official package rules require a relative +// branding path (not an HTTPS URL), MCP declared as ./.mcp.json, and +// directory-submission length limits on the title and subtitle. +const CODEX_CATEGORIES = new Set([ + 'Productivity', + 'Creativity', + 'Developer Tools', + 'Business & Operations', + 'Data & Analytics', + 'Communication', + 'Education & Research', + 'Security', + 'Finance', + 'Healthcare', + 'Travel', + 'Entertainment', + 'Other', +]); +const HTTPS = /^https:\/\/[^/\s]+/; +const HEX = /^#[0-9A-Fa-f]{6}$/; +const SEMVER = /^\d+\.\d+\.\d+$/; +const loadJson = (path) => { + try { + return JSON.parse(readFileSync(join(root, path), 'utf8')); + } catch (err) { + fail(`codex listing: ${path} is not valid JSON (${err.message})`); + return null; + } +}; +const requireHttps = (label, value) => { + if (typeof value !== 'string' || !HTTPS.test(value)) { + fail(`codex listing: ${label} must be an https URL`); + } +}; +const requireAsset = (label, value) => { + if (typeof value !== 'string' || !value.startsWith('./') || value.includes('..')) { + fail(`codex listing: ${label} must be a relative path starting with ./`); + return; + } + if (/^https?:/i.test(value)) { + fail(`codex listing: ${label} must not be a URL`); + return; + } + const rel = value.slice(2); + if (!existsSync(join(root, rel))) fail(`codex listing: ${label} file missing (${rel})`); +}; + +const codex = loadJson('.codex-plugin/plugin.json'); +if (codex) { + const iface = codex.interface && typeof codex.interface === 'object' ? codex.interface : null; + if (!iface) fail('codex listing: interface object is required'); + if (codex.name !== 'deriv-api') fail('codex listing: name must be deriv-api'); + if (typeof codex.version !== 'string' || !SEMVER.test(codex.version)) { + fail('codex listing: version must be semver'); + } + if (typeof codex.description !== 'string' || !codex.description || codex.description.length > 1024) { + fail('codex listing: description is required and must be 1024 characters or fewer'); + } + if (!codex.author || codex.author.name !== 'Deriv') fail('codex listing: author.name must be Deriv'); + if (codex.mcpServers !== './.mcp.json') { + fail('codex listing: mcpServers must be ./.mcp.json so the hosted server is imported'); + } + if (codex.skills != null) fail('codex listing: skills must be omitted; this plugin has no skills/ tree'); + if (codex.apps != null) fail('codex listing: apps must be omitted; there is no .app.json'); + if (iface) { + if (iface.developerName !== codex.author.name) { + fail('codex listing: interface.developerName must match author.name'); + } + if (typeof iface.displayName !== 'string' || !iface.displayName || iface.displayName.length > 30) { + fail('codex listing: displayName must be 30 characters or fewer'); + } + if (typeof iface.shortDescription !== 'string' || !iface.shortDescription || iface.shortDescription.includes('\n') || iface.shortDescription.length > 30) { + fail('codex listing: shortDescription must be one line of 30 characters or fewer'); + } + if (typeof iface.longDescription !== 'string' || !iface.longDescription || iface.longDescription.length > 4000) { + fail('codex listing: longDescription is required'); + } + if (!CODEX_CATEGORIES.has(iface.category)) fail('codex listing: category is not a supported Codex category'); + if (!Array.isArray(iface.capabilities) || iface.capabilities.length === 0 || iface.capabilities.length > 20) { + fail('codex listing: capabilities must be a non-empty list of at most 20 entries'); + } + if (typeof iface.brandColor !== 'string' || !HEX.test(iface.brandColor)) { + fail('codex listing: brandColor must be a six-digit hex color'); + } + requireHttps('homepage', codex.homepage); + requireHttps('author.url', codex.author.url); + requireHttps('interface.websiteURL', iface.websiteURL); + requireHttps('interface.privacyPolicyURL', iface.privacyPolicyURL); + requireHttps('interface.termsOfServiceURL', iface.termsOfServiceURL); + requireHttps('interface.supportURL', iface.supportURL); + requireAsset('interface.logo', iface.logo); + requireAsset('interface.logoDark', iface.logoDark); + requireAsset('interface.composerIcon', iface.composerIcon); + const prompts = Array.isArray(iface.defaultPrompt) ? iface.defaultPrompt : []; + if (prompts.length === 0 || prompts.length > 3) fail('codex listing: defaultPrompt must contain 1–3 strings'); + const seen = new Set(); + for (const prompt of prompts) { + if (typeof prompt !== 'string' || !prompt || prompt.includes('\n') || prompt.length > 128) { + fail('codex listing: each defaultPrompt must be one line of 128 characters or fewer'); + } + if (prompt.includes('@')) fail('codex listing: defaultPrompt must not contain @mentions'); + const key = prompt.normalize('NFC').trim(); + if (seen.has(key)) fail('codex listing: defaultPrompt entries must be unique'); + seen.add(key); + } + } + const cursor = loadJson('.cursor-plugin/plugin.json'); + if (cursor && cursor.version !== codex.version) { + fail('codex listing: version must stay in lockstep with .cursor-plugin/plugin.json'); + } +} + +const marketplace = loadJson('.agents/plugins/marketplace.json'); +if (marketplace) { + const entry = Array.isArray(marketplace.plugins) ? marketplace.plugins[0] : null; + if (!entry) fail('codex listing: marketplace must declare one plugin entry'); + else { + if (entry.name !== 'deriv-api') fail('codex listing: marketplace plugin name must be deriv-api'); + if (!entry.source || entry.source.source !== 'local' || entry.source.path !== './') { + fail('codex listing: marketplace source must be local path ./'); + } + if (!entry.policy || entry.policy.installation !== 'AVAILABLE' || entry.policy.authentication !== 'ON_INSTALL') { + fail('codex listing: marketplace policy must be AVAILABLE / ON_INSTALL'); + } + if (entry.category !== 'Developer Tools') fail('codex listing: marketplace category must be Developer Tools'); + } +} + if (failures.length > 0) { for (const line of failures) console.error(`FAIL: ${line}`); console.error(`\nverify-surface: ${failures.length} problem(s) found`); diff --git a/README.md b/README.md index a8c0b43..76ee4d1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Deriv API plugin (Beta) A plugin that gives an AI coding agent first-class knowledge of the Deriv API. -It is a **thin client**: it points both **Cursor** and **Claude Code** at a +It is a **thin client**: it points **Cursor**, **Claude Code**, and **Codex** at a **hosted Deriv API MCP server** over HTTP, and ships one Cursor rule alongside it. The hosted server exposes the tools that search endpoints; read the current request/response schemas, fields, and worked examples; validate payloads; and @@ -43,6 +43,14 @@ git clone https://github.com/deriv-com/deriv-api-plugin ~/.cursor/plugins/local/ Then reload the Cursor window so the plugin is picked up. +### Codex + +``` +codex plugin marketplace add deriv-com/deriv-api-plugin +``` + +That registers this repository as a Codex marketplace. Install **Deriv API (Beta)** from the Plugins Directory, then restart Codex so it picks up the hosted MCP server. To refresh an existing install after a listing change, run `codex plugin marketplace upgrade`. + ### Upgrading from version 1 Version 1 shipped a local MCP server and skills under the same plugin name. If diff --git a/assets/logo-dark.png b/assets/logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2276d642ef6833cdd5761f6393876b580bd83071 GIT binary patch literal 7508 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 literal 0 HcmV?d00001 diff --git a/assets/logo-dark.svg b/assets/logo-dark.svg new file mode 100644 index 0000000..f82341d --- /dev/null +++ b/assets/logo-dark.svg @@ -0,0 +1,6 @@ + + + + + +