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 0000000..2276d64 Binary files /dev/null and b/assets/logo-dark.png differ 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 @@ + + + + + +