From a17aaaef3a81d9fb920aee2c693ab919c076e3c8 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Mon, 22 Jun 2026 10:32:59 -0700 Subject: [PATCH] Add Codex marketplace manifest Prompt: Jesse asked for a new worktree off the local superpowers dev branch to add the Codex manifest after diagnosing why github.com/obra/superpowers did not show installable Codex plugins. Root cause: Codex marketplace sources expect a .agents/plugins/marketplace.json at the marketplace root. The superpowers repo only had the Claude marketplace file and the Codex plugin manifest, so Codex could configure the marketplace name but found no installable plugin entries. Changes: add a repo-local Codex marketplace manifest for superpowers-dev that points at this same repository root via the same-root source pattern Codex already accepts; add a focused marketplace manifest test; remove the unsupported hooks field from .codex-plugin/plugin.json so the plugin validator accepts the manifest. Validation: bash tests/codex/test-marketplace-manifest.sh; uv run --with PyYAML python /Users/jesse/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py /Users/jesse/git/superpowers/superpowers/.worktrees/codex-marketplace-manifest; throwaway HOME codex plugin marketplace add/list/add; bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh; bash tests/kimi/test-plugin-manifest.sh; bash tests/shell-lint/test-lint-shell.sh; scripts/lint-shell.sh tests/codex/test-marketplace-manifest.sh. --- .agents/plugins/marketplace.json | 20 +++++++ .codex-plugin/plugin.json | 1 - tests/codex/test-marketplace-manifest.sh | 66 ++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 .agents/plugins/marketplace.json create mode 100755 tests/codex/test-marketplace-manifest.sh diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 00000000..4e3b4350 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "superpowers-dev", + "interface": { + "displayName": "Superpowers Dev" + }, + "plugins": [ + { + "name": "superpowers", + "source": { + "source": "url", + "url": "./" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 7f8ae7b6..c98b525f 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -21,7 +21,6 @@ "workflow" ], "skills": "./skills/", - "hooks": "./hooks/hooks-codex.json", "interface": { "displayName": "Superpowers", "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", diff --git a/tests/codex/test-marketplace-manifest.sh b/tests/codex/test-marketplace-manifest.sh new file mode 100755 index 00000000..486cb301 --- /dev/null +++ b/tests/codex/test-marketplace-manifest.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +MARKETPLACE="$REPO_ROOT/.agents/plugins/marketplace.json" + +python3 - "$MARKETPLACE" "$REPO_ROOT" <<'PY' +import json +import sys +from pathlib import Path + +marketplace_path = Path(sys.argv[1]) +repo_root = Path(sys.argv[2]) + +if not marketplace_path.exists(): + raise AssertionError(".agents/plugins/marketplace.json must exist") + +marketplace = json.loads(marketplace_path.read_text(encoding="utf-8")) + +def assert_equal(actual, expected, label): + if actual != expected: + raise AssertionError(f"{label}: expected {expected!r}, got {actual!r}") + +assert_equal(marketplace.get("name"), "superpowers-dev", "marketplace name") +assert_equal( + marketplace.get("interface", {}).get("displayName"), + "Superpowers Dev", + "marketplace display name", +) + +plugins = marketplace.get("plugins") +if not isinstance(plugins, list): + raise AssertionError("plugins must be a list") + +matching_plugins = [plugin for plugin in plugins if plugin.get("name") == "superpowers"] +assert_equal(len(matching_plugins), 1, "superpowers plugin entry count") + +plugin = matching_plugins[0] +assert_equal(plugin.get("source"), {"source": "url", "url": "./"}, "plugin source") +assert_equal( + plugin.get("policy"), + {"installation": "AVAILABLE", "authentication": "ON_INSTALL"}, + "plugin policy", +) +assert_equal(plugin.get("category"), "Developer Tools", "plugin category") + +plugin_manifest = repo_root / ".codex-plugin" / "plugin.json" +if not plugin_manifest.exists(): + raise AssertionError(".codex-plugin/plugin.json must exist") + +manifest = json.loads(plugin_manifest.read_text(encoding="utf-8")) +assert_equal(manifest.get("name"), plugin.get("name"), "plugin manifest name") + +unsupported_manifest_fields = ["hooks"] +present_unsupported = sorted( + field for field in unsupported_manifest_fields if field in manifest +) +if present_unsupported: + raise AssertionError( + "unsupported Codex manifest fields present: " + + ", ".join(present_unsupported) + ) + +print("Codex marketplace manifest looks good") +PY