From 01ed857acf4ed9bacf898b4bdb088f73a291d59c Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Sun, 19 Jul 2026 11:58:41 -0500 Subject: [PATCH 1/2] fix(codex): install plugins through CLI Delegate cache publication and config enablement to Codex's supported plugin commands. Keep explicit pruning version-aware and publish 1.7.2. --- .claude-plugin/marketplace.json | 16 +- AGENTS.md | 4 +- README.md | 4 +- plugins/go-dev/.claude-plugin/plugin.json | 2 +- plugins/go-dev/.codex-plugin/plugin.json | 2 +- plugins/go-web/.claude-plugin/plugin.json | 2 +- plugins/go-web/.codex-plugin/plugin.json | 2 +- .../go-workflow/.claude-plugin/plugin.json | 2 +- plugins/go-workflow/.codex-plugin/plugin.json | 2 +- .../hooks/codex-cleanup-on-start.sh | 6 +- .../gopher-guides/.claude-plugin/plugin.json | 2 +- .../gopher-guides/.codex-plugin/plugin.json | 2 +- plugins/llm-tools/.claude-plugin/plugin.json | 2 +- plugins/llm-tools/.codex-plugin/plugin.json | 2 +- .../productivity/.claude-plugin/plugin.json | 2 +- plugins/tailwind/.claude-plugin/plugin.json | 2 +- plugins/tailwind/.codex-plugin/plugin.json | 2 +- scripts/build-universal.sh | 14 +- scripts/install-all.sh | 2 +- scripts/install-codex.sh | 165 +++------- scripts/test-installation.sh | 283 +++++++++--------- 21 files changed, 220 insertions(+), 300 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index cdf4153e..0eebbc4c 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,14 +6,14 @@ }, "metadata": { "description": "Go-specific and general-purpose AI coding assistant plugins - by Gopher Guides", - "version": "1.7.1" + "version": "1.7.2" }, "plugins": [ { "name": "go-workflow", "source": "./plugins/go-workflow", "description": "Issue-to-PR workflow automation with worktree management", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "workflow", "github", @@ -26,7 +26,7 @@ "name": "go-dev", "source": "./plugins/go-dev", "description": "Go-specific development tools with best practices", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "go", "testing", @@ -39,7 +39,7 @@ "name": "productivity", "source": "./plugins/productivity", "description": "Standup reports and git productivity helpers", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "standup", "changelog", @@ -52,7 +52,7 @@ "name": "gopher-guides", "source": "./plugins/gopher-guides", "description": "Go best practices guidance powered by Gopher Guides training materials", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "go", "training", @@ -64,7 +64,7 @@ "name": "llm-tools", "source": "./plugins/llm-tools", "description": "Multi-LLM integration for second opinions and task delegation", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "llm", "openai", @@ -81,7 +81,7 @@ "name": "go-web", "source": "./plugins/go-web", "description": "Opinionated Go web app scaffolding with Templ + HTMX + Alpine.js + Tailwind", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "go", "web", @@ -97,7 +97,7 @@ "name": "tailwind", "source": "./plugins/tailwind", "description": "Tailwind CSS v4 tools for initialization, auditing, migration, and optimization", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "tailwind", "css", diff --git a/AGENTS.md b/AGENTS.md index 2a17ae66..8eff945a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,7 +70,7 @@ For Codex, "global" means installing plugins through the Codex marketplace cache ./scripts/install-codex.sh --user ``` -`--user` stages each new commit-hash root before activating it and retains previously published roots so active Codex sessions keep working through an update. +`--user` registers or upgrades the marketplace and runs `codex plugin add` for each Codex-capable plugin. Codex owns config updates and publishes immutable versioned roots, retaining prior versions for active sessions. The universal installer handles every detected platform in one step: @@ -86,7 +86,7 @@ After all Codex sessions have exited, stale marketplace cache roots can be remov ./scripts/install-codex.sh --prune-cache ``` -Do not prune while a Codex session is active. Running sessions retain absolute hook and skill paths into their original cache roots. The prune command keeps the latest installed commit. +Do not prune while a Codex session is active. Running sessions retain absolute hook and skill paths into their original cache roots. The prune command keeps the current plugin version. ### Migration diff --git a/README.md b/README.md index 12c825f9..714a7fcd 100644 --- a/README.md +++ b/README.md @@ -305,9 +305,9 @@ Plugins are distributed via the [Codex plugin system](https://developers.openai. **Install into another repo:** `./scripts/install-codex.sh --repo /path/to/your-repo` copies the current plugin set and merges entries into that repo's `.agents/plugins/marketplace.json` without removing unrelated plugin entries. -**GitHub one-liner:** `bash -c "$(curl -fsSL https://raw.githubusercontent.com/gopherguides/gopher-ai/main/scripts/install-all.sh)"` auto-detects all platforms — installs Claude Code and Gemini, and installs Codex plugins globally via Codex's marketplace mechanism (so skills load in every Codex session). The Codex install runs `codex plugin marketplace add gopherguides/gopher-ai`, populates `~/.codex/plugins/cache/gopher-ai///` from the marketplace clone, and writes `[plugins."@gopher-ai"]\nenabled = true` entries to `~/.codex/config.toml`. The marketplace cache is the only path Codex actually loads from — direct copies to `~/.codex/plugins//` are silently ignored. +**GitHub one-liner:** `bash -c "$(curl -fsSL https://raw.githubusercontent.com/gopherguides/gopher-ai/main/scripts/install-all.sh)"` auto-detects all platforms — installs Claude Code and Gemini, and installs Codex plugins globally so skills load in every Codex session. The Codex install registers or upgrades the gopher-ai marketplace, then runs `codex plugin add @gopher-ai` for all six Codex-capable plugins. Codex owns the cache publication and plugin enablement; the installer does not write private cache roots or `config.toml` plugin entries. -**Cache lifecycle:** `install-codex.sh --user` stages each new commit-hash root before activating it and leaves older roots available to Codex sessions that started before the update. After all Codex sessions have exited, remove superseded roots with `./scripts/install-codex.sh --prune-cache`. Do not prune while Codex is running; active sessions retain absolute hook and skill paths into their original roots. The prune command keeps the latest installed commit and requires confirmation unless `--yes` is supplied. +**Cache lifecycle:** Codex publishes each plugin under its manifest version and leaves older roots available to sessions that started before an update. After all Codex sessions have exited, remove superseded roots with `./scripts/install-codex.sh --prune-cache`. Do not prune while Codex is running; active sessions retain absolute hook and skill paths into their original roots. The prune command keeps the current version and requires confirmation unless `--yes` is supplied. **Migration from older versions:** Three earlier states needed cleanup, all handled automatically by the SessionStart hook (no command required) plus by `install-codex.sh --user` whenever you run install-all: - Flat skills at `~/.codex/skills//` from the original (broken) `--user` mode — overflowed Codex's [skill metadata budget](https://developers.openai.com/codex/skills). diff --git a/plugins/go-dev/.claude-plugin/plugin.json b/plugins/go-dev/.claude-plugin/plugin.json index af879959..15d140f3 100644 --- a/plugins/go-dev/.claude-plugin/plugin.json +++ b/plugins/go-dev/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "go-dev", "description": "Go-specific development tools with idiomatic best practices", - "version": "1.7.1", + "version": "1.7.2", "author": { "name": "Gopher Guides", "email": "support@gopherguides.com" diff --git a/plugins/go-dev/.codex-plugin/plugin.json b/plugins/go-dev/.codex-plugin/plugin.json index f121cd6c..9bb9f949 100644 --- a/plugins/go-dev/.codex-plugin/plugin.json +++ b/plugins/go-dev/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "go-dev", - "version": "1.7.1", + "version": "1.7.2", "description": "Go-specific development tools with idiomatic best practices", "author": { "name": "Gopher Guides", diff --git a/plugins/go-web/.claude-plugin/plugin.json b/plugins/go-web/.claude-plugin/plugin.json index 44c775ea..6998ce7a 100644 --- a/plugins/go-web/.claude-plugin/plugin.json +++ b/plugins/go-web/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "go-web", "description": "Opinionated Go web app scaffolding with Templ + HTMX + Alpine.js + Tailwind", - "version": "1.7.1", + "version": "1.7.2", "author": { "name": "Gopher Guides", "email": "support@gopherguides.com" diff --git a/plugins/go-web/.codex-plugin/plugin.json b/plugins/go-web/.codex-plugin/plugin.json index 19910bf3..47530ccd 100644 --- a/plugins/go-web/.codex-plugin/plugin.json +++ b/plugins/go-web/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "go-web", - "version": "1.7.1", + "version": "1.7.2", "description": "Opinionated Go web app scaffolding with Templ + HTMX + Alpine.js + Tailwind", "author": { "name": "Gopher Guides", diff --git a/plugins/go-workflow/.claude-plugin/plugin.json b/plugins/go-workflow/.claude-plugin/plugin.json index 442fccb1..25c392b3 100644 --- a/plugins/go-workflow/.claude-plugin/plugin.json +++ b/plugins/go-workflow/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "go-workflow", "description": "Issue-to-PR workflow automation with git worktree management", - "version": "1.7.1", + "version": "1.7.2", "author": { "name": "Gopher Guides", "email": "support@gopherguides.com" diff --git a/plugins/go-workflow/.codex-plugin/plugin.json b/plugins/go-workflow/.codex-plugin/plugin.json index f4c9f6c2..e31f07a7 100644 --- a/plugins/go-workflow/.codex-plugin/plugin.json +++ b/plugins/go-workflow/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "go-workflow", - "version": "1.7.1", + "version": "1.7.2", "description": "Issue-to-PR workflow automation with git worktree management", "author": { "name": "Gopher Guides", diff --git a/plugins/go-workflow/hooks/codex-cleanup-on-start.sh b/plugins/go-workflow/hooks/codex-cleanup-on-start.sh index 96528102..87b82345 100755 --- a/plugins/go-workflow/hooks/codex-cleanup-on-start.sh +++ b/plugins/go-workflow/hooks/codex-cleanup-on-start.sh @@ -256,9 +256,9 @@ json_author_email() { # # A directory at ~/.codex/plugins// is ALWAYS stale (regardless # of marker) — we just learned that Codex never actually loaded plugins from -# there. The legitimate install path is the marketplace cache at -# ~/.codex/plugins/cache/gopher-ai///, populated by -# install-codex.sh --user. Earlier `--user` versions wrote a marker file +# there. The legitimate install path is the versioned marketplace cache at +# ~/.codex/plugins/cache/gopher-ai///, populated by the public +# Codex plugin CLI. Earlier `--user` versions wrote a marker file # alongside a direct install; that direct install was inert, so removing it # only eliminates clutter and frees the directory name for the user. if [[ -d "$PLUGINS_HOME" ]]; then diff --git a/plugins/gopher-guides/.claude-plugin/plugin.json b/plugins/gopher-guides/.claude-plugin/plugin.json index 25de9efb..0796a62f 100644 --- a/plugins/gopher-guides/.claude-plugin/plugin.json +++ b/plugins/gopher-guides/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "gopher-guides", "description": "Gopher Guides training materials integrated into Claude", - "version": "1.7.1", + "version": "1.7.2", "author": { "name": "Gopher Guides", "email": "support@gopherguides.com" diff --git a/plugins/gopher-guides/.codex-plugin/plugin.json b/plugins/gopher-guides/.codex-plugin/plugin.json index 48f53233..12701c9a 100644 --- a/plugins/gopher-guides/.codex-plugin/plugin.json +++ b/plugins/gopher-guides/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "gopher-guides", - "version": "1.7.1", + "version": "1.7.2", "description": "Gopher Guides training materials integration", "author": { "name": "Gopher Guides", diff --git a/plugins/llm-tools/.claude-plugin/plugin.json b/plugins/llm-tools/.claude-plugin/plugin.json index 204e8655..82a75d17 100644 --- a/plugins/llm-tools/.claude-plugin/plugin.json +++ b/plugins/llm-tools/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "llm-tools", "description": "Multi-LLM integration for second opinions and task delegation", - "version": "1.7.1", + "version": "1.7.2", "author": { "name": "Gopher Guides", "email": "support@gopherguides.com" diff --git a/plugins/llm-tools/.codex-plugin/plugin.json b/plugins/llm-tools/.codex-plugin/plugin.json index 47ef3496..c0ed5786 100644 --- a/plugins/llm-tools/.codex-plugin/plugin.json +++ b/plugins/llm-tools/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "llm-tools", - "version": "1.7.1", + "version": "1.7.2", "description": "Multi-LLM integration for second opinions and task delegation", "author": { "name": "Gopher Guides", diff --git a/plugins/productivity/.claude-plugin/plugin.json b/plugins/productivity/.claude-plugin/plugin.json index 896495dd..8e275ebd 100644 --- a/plugins/productivity/.claude-plugin/plugin.json +++ b/plugins/productivity/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "productivity", "description": "Standup reports, changelogs, and git productivity helpers", - "version": "1.7.1", + "version": "1.7.2", "author": { "name": "Gopher Guides", "email": "support@gopherguides.com" diff --git a/plugins/tailwind/.claude-plugin/plugin.json b/plugins/tailwind/.claude-plugin/plugin.json index c4c4fcc6..3a76436b 100644 --- a/plugins/tailwind/.claude-plugin/plugin.json +++ b/plugins/tailwind/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "tailwind", "description": "Tailwind CSS v4 tools for initialization, auditing, migration, and optimization", - "version": "1.7.1", + "version": "1.7.2", "author": { "name": "Gopher Guides", "email": "support@gopherguides.com" diff --git a/plugins/tailwind/.codex-plugin/plugin.json b/plugins/tailwind/.codex-plugin/plugin.json index be16701e..0ee1d086 100644 --- a/plugins/tailwind/.codex-plugin/plugin.json +++ b/plugins/tailwind/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "tailwind", - "version": "1.7.1", + "version": "1.7.2", "description": "Tailwind CSS v4 tools for initialization, auditing, migration, and optimization", "author": { "name": "Gopher Guides", diff --git a/scripts/build-universal.sh b/scripts/build-universal.sh index 8f27c599..a33f768c 100755 --- a/scripts/build-universal.sh +++ b/scripts/build-universal.sh @@ -139,15 +139,21 @@ To add these plugins to another repo: ### Global (Personal) Use -For Codex, "global" means cloning this repo and running Codex inside it — Codex auto-discovers `.agents/plugins/marketplace.json` and offers the plugins through `/plugins`. There is no flat-skills install path; the previous `--user` mode was removed because it double-loaded skills alongside the marketplace and overflowed Codex's skill metadata budget. +For Codex, "global" means installing plugins through the public Codex plugin CLI so they load in every session, regardless of the working directory. -If you previously ran `--user`, clean up the legacy entries once: +```bash +./scripts/install-codex.sh --user +``` + +`--user` registers or upgrades the gopher-ai marketplace and runs `codex plugin add` for each Codex-capable plugin. Codex owns config updates and versioned cache publication. + +After all Codex sessions have exited, stale version roots can be removed explicitly: ```bash -./scripts/install-codex.sh --cleanup +./scripts/install-codex.sh --prune-cache ``` -Restart Codex after cleanup. Use `/plugins` to verify. +Do not prune while a Codex session is active. Restart Codex after installation and use `/plugins` to verify. ## Architecture diff --git a/scripts/install-all.sh b/scripts/install-all.sh index a071c850..e945da32 100755 --- a/scripts/install-all.sh +++ b/scripts/install-all.sh @@ -98,7 +98,7 @@ check_prerequisites() { local missing=() # jq is needed by build-universal.sh (Claude/Gemini) and by install-codex.sh - # for marketplace.json manipulation when installing globally. + # for public marketplace discovery and plugin manifest parsing. if $HAVE_CLAUDE || $HAVE_GEMINI || $HAVE_CODEX; then if ! command -v jq >/dev/null 2>&1; then missing+=("jq (brew install jq / apt install jq)") diff --git a/scripts/install-codex.sh b/scripts/install-codex.sh index 408cdbda..2c525dfb 100755 --- a/scripts/install-codex.sh +++ b/scripts/install-codex.sh @@ -19,9 +19,9 @@ Usage: gopher-ai for Codex can be installed in two ways: - --user Install plugins globally through the Codex marketplace cache - so they load in EVERY Codex session, regardless of the working - directory. New commit-hash roots are staged before activation. + --user Install plugins globally through the public Codex plugin CLI so + they load in EVERY Codex session, regardless of the working + directory. Codex publishes versioned roots before activation. Previously published roots are retained for active sessions. Also removes legacy ~/.codex/skills/ entries left over from older installs (with --yes semantics — assumes the user wants @@ -44,8 +44,8 @@ gopher-ai for Codex can be installed in two ways: --prune-cache Remove superseded Gopher AI marketplace cache roots. Close all Codex sessions before running this command; active sessions - retain absolute paths into these roots. The latest installed - commit is always kept. + retain absolute paths into these roots. The current plugin + version is always kept. --yes Skip interactive confirmation in --cleanup or --prune-cache. --help Show this help text @@ -442,126 +442,53 @@ cache_root_matches_plugin() { return 0 } -# Install plugins globally for Codex via the marketplace + cache mechanism -# Codex actually uses (verified empirically — direct copies to -# ~/.codex/plugins// are silently ignored by Codex). -# -# Three things are required for skills to actually load: -# 1. The marketplace must be registered in ~/.codex/config.toml under -# [marketplaces.gopher-ai]. Achieved by `codex plugin marketplace add`. -# 2. ~/.codex/plugins/cache/gopher-ai/// must exist -# with the plugin contents. Codex's TUI populates this after a user -# enables a plugin via /plugins; from the CLI we populate it ourselves -# from the marketplace clone at ~/.codex/.tmp/marketplaces/gopher-ai/. -# 3. ~/.codex/config.toml must contain [plugins."@gopher-ai"] -# enabled = true entries. -# -# Published commit-hash roots are immutable because active Codex sessions keep -# absolute paths into them. Superseded roots are removed only by --prune-cache. install_user_plugins() { require_cmd codex - require_cmd git - - local config_file="$HOME/.codex/config.toml" - local marketplace_clone="$HOME/.codex/.tmp/marketplaces/gopher-ai" - local cache_root="$HOME/.codex/plugins/cache/gopher-ai" + if ! codex plugin add --help >/dev/null 2>&1; then + echo "error: installed Codex CLI does not support 'codex plugin add'." >&2 + echo " upgrade Codex, then retry --user." >&2 + return 1 + fi - mkdir -p "$HOME/.codex" - [[ -f "$config_file" ]] || touch "$config_file" + local marketplaces + if ! marketplaces="$(codex plugin marketplace list --json 2>/dev/null)"; then + echo "error: could not list configured Codex plugin marketplaces." >&2 + return 1 + fi - # 1. Register the marketplace (idempotent — `add` succeeds the first time, - # `upgrade` brings an existing registration up to date). - if grep -q '^\[marketplaces\.gopher-ai\]' "$config_file" 2>/dev/null; then + if jq -e '.marketplaces[]? | select(.name == "gopher-ai" and .marketplaceSource != null)' \ + >/dev/null <<<"$marketplaces"; then echo "marketplace already registered — upgrading..." codex plugin marketplace upgrade gopher-ai 2>&1 | sed 's/^/ /' else echo "registering gopher-ai marketplace..." - codex plugin marketplace add gopherguides/gopher-ai 2>&1 | sed 's/^/ /' - fi - - if [[ ! -d "$marketplace_clone" ]]; then - echo "error: marketplace clone missing at $marketplace_clone" >&2 - echo " \`codex plugin marketplace add\` should have created it." >&2 - return 1 + codex plugin marketplace add "$REPO_SLUG" --ref "$REPO_REF" 2>&1 | sed 's/^/ /' fi - # 2. Populate the cache with the plugin contents under the commit-hash - # subdir Codex looks for. Hash is the short SHA of the marketplace - # clone's HEAD (matches the path scheme openai-curated uses). - local commit_hash - commit_hash="$(git -C "$marketplace_clone" rev-parse --short=8 HEAD 2>/dev/null)" - if [[ -z "$commit_hash" ]]; then - echo "error: could not read commit hash from $marketplace_clone" >&2 - return 1 - fi - echo "populating cache (commit $commit_hash)..." - local installed=0 local plugin_dir - for plugin_dir in "$marketplace_clone"/plugins/*; do + for plugin_dir in "$ROOT_DIR"/plugins/*; do [[ -d "$plugin_dir" ]] || continue [[ -f "$plugin_dir/.codex-plugin/plugin.json" ]] || continue - local plugin_name plugin_cache dest staging + local plugin_name plugin_name="$(basename "$plugin_dir")" - plugin_cache="$cache_root/$plugin_name" - dest="$plugin_cache/$commit_hash" - - if [[ -d "$dest" ]]; then - if ! cache_root_matches_plugin "$plugin_dir" "$dest"; then - echo "error: incomplete published cache root at $dest" >&2 - echo " close Codex, run --prune-cache, then retry --user." >&2 - return 1 - fi - echo " retained: $plugin_name (already cached at $dest)" - installed=$((installed + 1)) - continue - fi - - mkdir -p "$plugin_cache" - staging="$(mktemp -d "$plugin_cache/.${commit_hash}.tmp.XXXXXX")" - if ! cp -R "$plugin_dir"/. "$staging/"; then - rm -rf "${staging:?}" - return 1 - fi - rm -rf "$staging/.claude-plugin" - if ! mv "$staging" "$dest"; then - rm -rf "${staging:?}" - return 1 - fi - echo " installed: $plugin_name (cached at $dest)" + echo "installing $plugin_name@gopher-ai..." + codex plugin add "${plugin_name}@gopher-ai" 2>&1 | sed 's/^/ /' installed=$((installed + 1)) done - # 3. Enable each plugin in config.toml. Idempotent: skip entries that - # already exist. - local enabled=0 - for plugin_dir in "$marketplace_clone"/plugins/*; do - [[ -d "$plugin_dir" ]] || continue - [[ -f "$plugin_dir/.codex-plugin/plugin.json" ]] || continue - local plugin_name - plugin_name="$(basename "$plugin_dir")" - if grep -qF "[plugins.\"${plugin_name}@gopher-ai\"]" "$config_file"; then - continue - fi - # Append a new section. A blank line keeps the file readable. - printf '\n[plugins."%s@gopher-ai"]\nenabled = true\n' "$plugin_name" >> "$config_file" - enabled=$((enabled + 1)) - done - if [[ "$enabled" -gt 0 ]]; then - echo "enabled $enabled new plugin entr$([ "$enabled" -eq 1 ] && echo "y" || echo "ies") in $config_file" + if [[ "$installed" -eq 0 ]]; then + echo "error: no Codex-capable plugins found in $ROOT_DIR/plugins." >&2 + return 1 fi - # Remove any leftover direct ~/.codex/plugins// installs from the - # OLD broken --user behavior. Those are completely invisible to Codex - # (Codex never reads from there), but they confuse users who see them. local removed_legacy=0 - for plugin_dir in "$marketplace_clone"/plugins/*; do + for plugin_dir in "$ROOT_DIR"/plugins/*; do [[ -d "$plugin_dir" ]] || continue + [[ -f "$plugin_dir/.codex-plugin/plugin.json" ]] || continue local plugin_name plugin_name="$(basename "$plugin_dir")" local legacy="$HOME/.codex/plugins/$plugin_name" - # Only remove if it has our marker (from previous broken --user install) - # OR if it has gopher-ai author email. NEVER touch user-authored plugins. if [[ -d "$legacy" ]]; then if [[ -f "$legacy/.gopher-ai-installed" ]]; then rm -rf "$legacy" @@ -582,13 +509,12 @@ install_user_plugins() { fi echo "" - echo "installed $installed plugin(s) via gopher-ai marketplace." + echo "installed $installed plugin(s) through the Codex plugin CLI." echo "Restart Codex; gopher-ai skills will load globally." } prune_user_plugin_cache() { local assume_yes="${1:-false}" - local marketplace_clone="$HOME/.codex/.tmp/marketplaces/gopher-ai" local cache_root="$HOME/.codex/plugins/cache/gopher-ai" if [[ ! -d "$cache_root" ]]; then @@ -596,43 +522,34 @@ prune_user_plugin_cache() { return 0 fi - require_cmd git - if [[ ! -d "$marketplace_clone" ]]; then - echo "error: marketplace clone missing at $marketplace_clone" >&2 - echo " run --user before pruning the cache." >&2 - return 1 - fi - - local commit_hash - commit_hash="$(git -C "$marketplace_clone" rev-parse --short=8 HEAD 2>/dev/null)" - if [[ -z "$commit_hash" ]]; then - echo "error: could not read commit hash from $marketplace_clone" >&2 - return 1 - fi - local candidates=() local deferred=() - local plugin_cache plugin_name plugin_source current_root current_ready cache_entry + local plugin_cache plugin_name plugin_source plugin_manifest current_version current_root current_ready cache_entry for plugin_cache in "$cache_root"/*; do [[ -d "$plugin_cache" ]] || continue plugin_name="$(basename "$plugin_cache")" - plugin_source="$marketplace_clone/plugins/$plugin_name" - current_root="$plugin_cache/$commit_hash" + plugin_source="$ROOT_DIR/plugins/$plugin_name" + plugin_manifest="$plugin_source/.codex-plugin/plugin.json" + current_version="" + if [[ -f "$plugin_manifest" ]]; then + current_version="$(jq -r '.version // empty' "$plugin_manifest")" + fi + current_root="$plugin_cache/$current_version" current_ready=false - if [[ -f "$plugin_source/.codex-plugin/plugin.json" ]] \ + if [[ -n "$current_version" ]] \ && cache_root_matches_plugin "$plugin_source" "$current_root"; then current_ready=true - elif [[ -f "$plugin_source/.codex-plugin/plugin.json" ]]; then + elif [[ -n "$current_version" ]]; then deferred+=("$plugin_cache") fi for cache_entry in "$plugin_cache"/* "$plugin_cache"/.[!.]* "$plugin_cache"/..?*; do [[ -e "$cache_entry" || -L "$cache_entry" ]] || continue - if [[ -f "$plugin_source/.codex-plugin/plugin.json" ]]; then + if [[ -n "$current_version" ]]; then if [[ "$current_ready" == "true" && "$cache_entry" == "$current_root" ]]; then continue fi if [[ "$current_ready" != "true" && "$cache_entry" != "$current_root" \ - && "$(basename "$cache_entry")" != ".${commit_hash}.tmp."* ]]; then + && "$(basename "$cache_entry")" != ".${current_version}.tmp."* ]]; then continue fi fi @@ -738,6 +655,8 @@ main() { cleanup_legacy_user_skills "$assume_yes" ;; --prune-cache) + require_cmd jq + bootstrap_repo local assume_yes=false if [[ "${2:-}" == "--yes" || "${2:-}" == "-y" ]]; then assume_yes=true diff --git a/scripts/test-installation.sh b/scripts/test-installation.sh index 380cfdf7..017710c2 100755 --- a/scripts/test-installation.sh +++ b/scripts/test-installation.sh @@ -890,61 +890,55 @@ else echo "OK" fi -# --- Tests for the new marketplace-based --user install ------------------- -# -# install-codex.sh --user uses Codex's marketplace + cache mechanism (the only -# install path Codex actually loads from). End-to-end testing requires the -# codex CLI; for unit-testing the cache layout + config edits, we stub codex -# with a no-op and pre-populate a fake marketplace clone that mirrors what -# `codex plugin marketplace add` would create. - -# Helper: build a fake marketplace clone in $1/.codex/.tmp/marketplaces/gopher-ai/ -# that mimics what `codex plugin marketplace add gopherguides/gopher-ai` produces. -seed_fake_marketplace_clone() { - local home="$1" - local clone="$home/.codex/.tmp/marketplaces/gopher-ai" - mkdir -p "$clone" - # Copy the current source plugins into plugins//, mirroring marketplace layout. - mkdir -p "$clone/plugins" - for plugin_dir in "$ROOT_DIR"/plugins/*; do - [ -d "$plugin_dir/.codex-plugin" ] || continue - cp -R "$plugin_dir" "$clone/plugins/" - done - cp -R "$ROOT_DIR/.agents" "$clone/" 2>/dev/null || true - # Initialize a git repo so `git rev-parse --short=8 HEAD` works. - git -C "$clone" init --quiet 2>/dev/null - git -C "$clone" config user.email test@example.com - git -C "$clone" config user.name test - git -C "$clone" add -A - git -C "$clone" commit --quiet -m "test seed" 2>/dev/null - echo "$clone" -} - -# Helper: build a stubbed PATH that has a no-op `codex` and core utilities. build_stub_path() { local stub_dir stub_dir="$(mktemp -d)" cat > "$stub_dir/codex" <<'STUB' #!/bin/sh -# No-op stub. install-codex.sh --user expects `codex plugin marketplace add` to -# produce ~/.codex/.tmp/marketplaces/gopher-ai/ — we pre-seed that directly in -# the test, so the stub just succeeds. +set -eu +printf '%s\n' "$*" >> "${CODEX_STUB_LOG:?}" +if [ "${1:-}" = "plugin" ] && [ "${2:-}" = "add" ] && [ "${3:-}" = "--help" ]; then + [ "${CODEX_STUB_SUPPORTS_ADD:-true}" = "true" ] + exit +fi +if [ "${1:-}" = "plugin" ] && [ "${2:-}" = "marketplace" ] && [ "${3:-}" = "list" ]; then + if [ "${CODEX_STUB_MARKETPLACE_REGISTERED:-false}" = "true" ]; then + printf '%s\n' '{"marketplaces":[{"name":"gopher-ai","marketplaceSource":{"sourceType":"git","source":"https://github.com/gopherguides/gopher-ai.git"}}]}' + else + printf '%s\n' '{"marketplaces":[]}' + fi + exit +fi +if [ "${1:-}" = "plugin" ] && [ "${2:-}" = "add" ]; then + plugin="${3%@*}" + source_root="${CODEX_STUB_SOURCE_ROOT:?}/plugins/$plugin" + version="$(jq -r '.version' "$source_root/.codex-plugin/plugin.json")" + destination="$HOME/.codex/plugins/cache/gopher-ai/$plugin/$version" + if [ ! -d "$destination" ]; then + mkdir -p "$destination" + cp -R "$source_root"/. "$destination/" + rm -rf "$destination/.claude-plugin" + fi + exit +fi exit 0 STUB chmod +x "$stub_dir/codex" - for cmd in bash sh awk sed grep find mkdir rm cp mv cmp mktemp printf cat dirname basename tr head tail xargs sleep date wc sha256sum git sort uniq stat ln readlink jq comm touch chmod cut id env true false echo test; do + for cmd in bash sh awk sed grep rg find mkdir rm cp mv cmp mktemp printf cat dirname basename tr head tail xargs sleep date wc sha256sum git sort uniq stat ln readlink jq comm touch chmod cut id env true false echo test; do cmd_path="$(command -v "$cmd" 2>/dev/null || true)" [ -n "$cmd_path" ] && ln -s "$cmd_path" "$stub_dir/$cmd" done echo "$stub_dir" } -echo -n "Codex --user populates marketplace cache with commit-hash subdir... " +echo -n "Codex --user installs all six plugins through codex plugin add... " TMP_HOME=$(mktemp -d) -seed_fake_marketplace_clone "$TMP_HOME" >/dev/null -STUB_PATH=$(build_stub_path "$TMP_HOME") -EXPECTED_HASH=$(git -C "$TMP_HOME/.codex/.tmp/marketplaces/gopher-ai" rev-parse --short=8 HEAD) -if ! HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/tmp/gopher-ai-user-install.log 2>&1; then +STUB_PATH=$(build_stub_path) +STUB_LOG=$(mktemp) +CURRENT_VERSION=$(jq -r '.metadata.version' "$ROOT_DIR/.claude-plugin/marketplace.json") +if ! HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ + CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" bash "$ROOT_DIR/scripts/install-codex.sh" --user \ + >/tmp/gopher-ai-user-install.log 2>&1; then echo "FAIL (--user exited non-zero)" sed -n '1,40p' /tmp/gopher-ai-user-install.log ERRORS=$((ERRORS + 1)) @@ -952,102 +946,77 @@ else EXPECTED_PLUGINS="go-dev go-web go-workflow gopher-guides llm-tools tailwind" MISSING="" for p in $EXPECTED_PLUGINS; do - cached="$TMP_HOME/.codex/plugins/cache/gopher-ai/$p/$EXPECTED_HASH" - if [ ! -f "$cached/.codex-plugin/plugin.json" ]; then - MISSING="$MISSING $p" - elif [ -d "$cached/.claude-plugin" ]; then - MISSING="$MISSING $p(has-claude-plugin)" + if ! rg -qx "plugin add ${p}@gopher-ai" "$STUB_LOG"; then + MISSING="$MISSING $p(command)" + elif [ ! -f "$TMP_HOME/.codex/plugins/cache/gopher-ai/$p/$CURRENT_VERSION/.codex-plugin/plugin.json" ]; then + MISSING="$MISSING $p(cache)" fi done - if [ -n "$MISSING" ]; then - echo "FAIL (cache populated incorrectly:$MISSING)" - sed -n '1,30p' /tmp/gopher-ai-user-install.log + ADD_COUNT=$(awk '$1 == "plugin" && $2 == "add" && $3 != "--help" { count++ } END { print count + 0 }' "$STUB_LOG") + if [ -n "$MISSING" ] || [ "$ADD_COUNT" -ne 6 ]; then + echo "FAIL (plugin add calls were incorrect:$MISSING; count=$ADD_COUNT)" + sed -n '1,40p' "$STUB_LOG" + ERRORS=$((ERRORS + 1)) + elif ! rg -qx 'plugin marketplace add gopherguides/gopher-ai --ref main' "$STUB_LOG"; then + echo "FAIL (marketplace was not registered through the Codex CLI)" ERRORS=$((ERRORS + 1)) else - echo "OK ($EXPECTED_HASH cached for 6 plugins)" + echo "OK" fi fi -rm -rf "$TMP_HOME" "$STUB_PATH" -echo -n "Codex --user adds [plugins.@gopher-ai] entries to config.toml... " -TMP_HOME=$(mktemp -d) -seed_fake_marketplace_clone "$TMP_HOME" >/dev/null -STUB_PATH=$(build_stub_path "$TMP_HOME") -HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 -MISSING="" -for p in go-dev go-web go-workflow gopher-guides llm-tools tailwind; do - if ! grep -qF "[plugins.\"${p}@gopher-ai\"]" "$TMP_HOME/.codex/config.toml" 2>/dev/null; then - MISSING="$MISSING $p" - fi -done -if [ -n "$MISSING" ]; then - echo "FAIL (missing config entries:$MISSING)" +echo -n "Codex --user leaves config and cache publication to the CLI... " +if [ -f "$TMP_HOME/.codex/config.toml" ] && rg -q '^\[plugins\.' "$TMP_HOME/.codex/config.toml"; then + echo "FAIL (installer wrote plugin entries to config.toml)" + ERRORS=$((ERRORS + 1)) +elif rg -q 'populating cache|enabled [0-9]+ new plugin' /tmp/gopher-ai-user-install.log; then + echo "FAIL (installer reported private cache or config writes)" ERRORS=$((ERRORS + 1)) else echo "OK" fi -rm -rf "$TMP_HOME" "$STUB_PATH" -echo -n "Codex --user preserves a published root on the same commit... " -TMP_HOME=$(mktemp -d) -seed_fake_marketplace_clone "$TMP_HOME" >/dev/null -STUB_PATH=$(build_stub_path "$TMP_HOME") -HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 -HASH=$(git -C "$TMP_HOME/.codex/.tmp/marketplaces/gopher-ai" rev-parse --short=8 HEAD) -PUBLISHED_ROOT="$TMP_HOME/.codex/plugins/cache/gopher-ai/go-dev/$HASH" -echo "active session" > "$PUBLISHED_ROOT/ACTIVE_SESSION" -HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 -if [ ! -f "$PUBLISHED_ROOT/ACTIVE_SESSION" ]; then - echo "FAIL (published root was rewritten on re-install)" +echo -n "Codex --user upgrades a registered marketplace through the CLI... " +: > "$STUB_LOG" +HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ + CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" CODEX_STUB_MARKETPLACE_REGISTERED=true \ + bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 +if ! rg -qx 'plugin marketplace upgrade gopher-ai' "$STUB_LOG"; then + echo "FAIL (registered marketplace was not upgraded)" ERRORS=$((ERRORS + 1)) -elif [ ! -f "$PUBLISHED_ROOT/.codex-plugin/plugin.json" ]; then - echo "FAIL (re-install did not repopulate cache)" +elif rg -q '^plugin marketplace add ' "$STUB_LOG"; then + echo "FAIL (registered marketplace was added again)" ERRORS=$((ERRORS + 1)) else echo "OK" fi -echo -n "Codex --prune-cache recovers an incomplete current root... " -rm -f "$PUBLISHED_ROOT/skills/go/SKILL.md" -for p in go-web go-workflow gopher-guides llm-tools tailwind; do - rm -rf "$TMP_HOME/.codex/plugins/cache/gopher-ai/$p/$HASH" -done -set +e -HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 -INCOMPLETE_EXIT=$? -set -e -if [ "$INCOMPLETE_EXIT" -eq 0 ]; then - echo "FAIL (--user should reject an incomplete published root)" - ERRORS=$((ERRORS + 1)) -elif ! HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --prune-cache --yes >/dev/null 2>&1; then - echo "FAIL (--prune-cache rejected the incomplete root)" - ERRORS=$((ERRORS + 1)) -elif ! HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1; then - echo "FAIL (--user did not republish the pruned root)" +echo -n "Codex --user preserves a published root on the same version... " +PUBLISHED_ROOT="$TMP_HOME/.codex/plugins/cache/gopher-ai/go-dev/$CURRENT_VERSION" +echo "active session" > "$PUBLISHED_ROOT/ACTIVE_SESSION" +HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ + CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" CODEX_STUB_MARKETPLACE_REGISTERED=true \ + bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 +if [ ! -f "$PUBLISHED_ROOT/ACTIVE_SESSION" ]; then + echo "FAIL (published root was rewritten on re-install)" ERRORS=$((ERRORS + 1)) -elif [ ! -f "$PUBLISHED_ROOT/.codex-plugin/plugin.json" ] \ - || [ ! -f "$TMP_HOME/.codex/plugins/cache/gopher-ai/go-web/$HASH/.codex-plugin/plugin.json" ]; then - echo "FAIL (missing or incomplete current roots were not restored)" +elif [ ! -f "$PUBLISHED_ROOT/.codex-plugin/plugin.json" ]; then + echo "FAIL (re-install did not repopulate cache)" ERRORS=$((ERRORS + 1)) else echo "OK" fi -rm -rf "$TMP_HOME" "$STUB_PATH" -echo -n "Codex --user retains active hooks and skills across an update... " -TMP_HOME=$(mktemp -d) -seed_fake_marketplace_clone "$TMP_HOME" >/dev/null -STUB_PATH=$(build_stub_path "$TMP_HOME") -HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 -MARKETPLACE_CLONE="$TMP_HOME/.codex/.tmp/marketplaces/gopher-ai" -OLD_HASH=$(git -C "$MARKETPLACE_CLONE" rev-parse --short=8 HEAD) -OLD_ROOT="$TMP_HOME/.codex/plugins/cache/gopher-ai/go-workflow/$OLD_HASH" -git -C "$MARKETPLACE_CLONE" commit --quiet --allow-empty -m "test update" -NEW_HASH=$(git -C "$MARKETPLACE_CLONE" rev-parse --short=8 HEAD) -HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 -NEW_ROOT="$TMP_HOME/.codex/plugins/cache/gopher-ai/go-workflow/$NEW_HASH" -if [ "$OLD_HASH" = "$NEW_HASH" ]; then - echo "FAIL (fixture did not create a new marketplace commit)" +echo -n "Codex --user retains prior version roots across an update... " +OLD_ROOT="$TMP_HOME/.codex/plugins/cache/gopher-ai/go-workflow/1.7.1" +mkdir -p "$OLD_ROOT" +cp -R "$ROOT_DIR/plugins/go-workflow"/. "$OLD_ROOT/" +rm -rf "$OLD_ROOT/.claude-plugin" +echo "active session" > "$OLD_ROOT/ACTIVE_SESSION" +if ! HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ + CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" CODEX_STUB_MARKETPLACE_REGISTERED=true \ + bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1; then + echo "FAIL (--user exited non-zero)" ERRORS=$((ERRORS + 1)) elif [ ! -x "$OLD_ROOT/hooks/pre-tool-use.sh" ] \ || [ ! -x "$OLD_ROOT/hooks/post-tool-use.sh" ] \ @@ -1062,29 +1031,8 @@ elif ! bash -n "$OLD_ROOT/hooks/pre-tool-use.sh" \ elif [ ! -r "$OLD_ROOT/skills/ship/SKILL.md" ]; then echo "FAIL (an active skill path was removed or became unreadable)" ERRORS=$((ERRORS + 1)) -elif [ ! -f "$NEW_ROOT/.codex-plugin/plugin.json" ]; then - echo "FAIL (new marketplace commit was not activated)" - ERRORS=$((ERRORS + 1)) -elif [ -n "$(compgen -G "$TMP_HOME/.codex/plugins/cache/gopher-ai/*/.*.tmp.*" || true)" ]; then - echo "FAIL (staging directory remained after activation)" - ERRORS=$((ERRORS + 1)) -else - echo "OK ($OLD_HASH retained, $NEW_HASH activated)" -fi - -echo -n "Codex --prune-cache retains old roots until the latest root exists... " -rm -rf "$NEW_ROOT" -if ! HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --prune-cache --yes >/dev/null 2>&1; then - echo "FAIL (--prune-cache exited non-zero)" - ERRORS=$((ERRORS + 1)) -elif [ ! -d "$OLD_ROOT" ]; then - echo "FAIL (old root was removed before the latest root existed)" - ERRORS=$((ERRORS + 1)) -elif ! HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1; then - echo "FAIL (--user did not restore the latest root)" - ERRORS=$((ERRORS + 1)) -elif [ ! -f "$NEW_ROOT/.codex-plugin/plugin.json" ]; then - echo "FAIL (latest root was not restored)" +elif [ ! -f "$TMP_HOME/.codex/plugins/cache/gopher-ai/go-workflow/$CURRENT_VERSION/.codex-plugin/plugin.json" ]; then + echo "FAIL (current version was not activated)" ERRORS=$((ERRORS + 1)) else echo "OK" @@ -1108,25 +1056,47 @@ else fi rm -f "$PRUNE_NOCONFIRM_LOG" -echo -n "Codex --prune-cache removes only superseded roots... " +echo -n "Codex --prune-cache retains prior roots until the current root is complete... " +CURRENT_WORKFLOW_ROOT="$TMP_HOME/.codex/plugins/cache/gopher-ai/go-workflow/$CURRENT_VERSION" +rm -f "$CURRENT_WORKFLOW_ROOT/skills/ship/SKILL.md" +if ! HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --prune-cache --yes >/dev/null 2>&1; then + echo "FAIL (--prune-cache exited non-zero)" + ERRORS=$((ERRORS + 1)) +elif [ ! -d "$OLD_ROOT" ]; then + echo "FAIL (prior root was removed while the current root was incomplete)" + ERRORS=$((ERRORS + 1)) +elif [ -d "$CURRENT_WORKFLOW_ROOT" ]; then + echo "FAIL (incomplete current root was not removed)" + ERRORS=$((ERRORS + 1)) +elif ! HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ + CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" CODEX_STUB_MARKETPLACE_REGISTERED=true \ + bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1; then + echo "FAIL (--user did not restore the current root)" + ERRORS=$((ERRORS + 1)) +else + echo "OK" +fi + +echo -n "Codex --prune-cache keeps the current versioned root... " if ! HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --prune-cache --yes >/dev/null 2>&1; then echo "FAIL (--prune-cache exited non-zero)" ERRORS=$((ERRORS + 1)) elif [ -d "$OLD_ROOT" ]; then - echo "FAIL (superseded cache root was not removed)" + echo "FAIL (superseded version root was not removed)" ERRORS=$((ERRORS + 1)) -elif [ ! -f "$NEW_ROOT/.codex-plugin/plugin.json" ]; then - echo "FAIL (latest cache root was removed)" +elif [ ! -f "$PUBLISHED_ROOT/.codex-plugin/plugin.json" ]; then + echo "FAIL (current versioned root was removed)" ERRORS=$((ERRORS + 1)) else echo "OK" fi -rm -rf "$TMP_HOME" "$STUB_PATH" + +rm -rf "$TMP_HOME" "$STUB_PATH" "$STUB_LOG" echo -n "Codex --user removes legacy direct ~/.codex/plugins// installs... " TMP_HOME=$(mktemp -d) -seed_fake_marketplace_clone "$TMP_HOME" >/dev/null -STUB_PATH=$(build_stub_path "$TMP_HOME") +STUB_PATH=$(build_stub_path) +STUB_LOG=$(mktemp) # Plant a legacy unmarked direct install with our author email. mkdir -p "$TMP_HOME/.codex/plugins/go-dev/.codex-plugin" cp "$ROOT_DIR/plugins/go-dev/.codex-plugin/plugin.json" "$TMP_HOME/.codex/plugins/go-dev/.codex-plugin/" @@ -1135,7 +1105,8 @@ mkdir -p "$TMP_HOME/.codex/plugins/go-workflow/.codex-plugin" printf '{\n "name": "go-workflow",\n "version": "9.9.9",\n "author": { "email": "other@example.com" }\n}\n' \ > "$TMP_HOME/.codex/plugins/go-workflow/.codex-plugin/plugin.json" echo "user content" > "$TMP_HOME/.codex/plugins/go-workflow/USER_FILE" -HOME="$TMP_HOME" PATH="$STUB_PATH" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 +HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ + CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 if [ -d "$TMP_HOME/.codex/plugins/go-dev" ]; then echo "FAIL (gopher-ai legacy direct install not removed: go-dev)" ERRORS=$((ERRORS + 1)) @@ -1145,7 +1116,31 @@ elif [ ! -f "$TMP_HOME/.codex/plugins/go-workflow/USER_FILE" ]; then else echo "OK" fi -rm -rf "$TMP_HOME" "$STUB_PATH" +rm -rf "$TMP_HOME" "$STUB_PATH" "$STUB_LOG" + +echo -n "Codex --user rejects a CLI without plugin add support... " +TMP_HOME=$(mktemp -d) +STUB_PATH=$(build_stub_path) +STUB_LOG=$(mktemp) +set +e +HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ + CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" CODEX_STUB_SUPPORTS_ADD=false \ + bash "$ROOT_DIR/scripts/install-codex.sh" --user >/tmp/gopher-ai-user-old-codex.log 2>&1 +EXIT=$? +set -e +if [ "$EXIT" -eq 0 ]; then + echo "FAIL (--user should reject an unsupported Codex CLI)" + ERRORS=$((ERRORS + 1)) +elif ! rg -q "does not support 'codex plugin add'" /tmp/gopher-ai-user-old-codex.log; then + echo "FAIL (error did not explain the required CLI support)" + ERRORS=$((ERRORS + 1)) +elif rg -q '^plugin marketplace ' "$STUB_LOG"; then + echo "FAIL (installer mutated marketplace state before the capability check)" + ERRORS=$((ERRORS + 1)) +else + echo "OK" +fi +rm -rf "$TMP_HOME" "$STUB_PATH" "$STUB_LOG" echo -n "Codex --user fails cleanly when codex CLI is missing... " TMP_HOME=$(mktemp -d) From b3b29f195e8f674626e01f0156390aeed90e5c59 Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Sun, 19 Jul 2026 12:01:59 -0500 Subject: [PATCH 2/2] test(codex): avoid rg in installer tests --- scripts/test-installation.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/test-installation.sh b/scripts/test-installation.sh index 017710c2..15058e1d 100755 --- a/scripts/test-installation.sh +++ b/scripts/test-installation.sh @@ -924,7 +924,7 @@ fi exit 0 STUB chmod +x "$stub_dir/codex" - for cmd in bash sh awk sed grep rg find mkdir rm cp mv cmp mktemp printf cat dirname basename tr head tail xargs sleep date wc sha256sum git sort uniq stat ln readlink jq comm touch chmod cut id env true false echo test; do + for cmd in bash sh awk sed grep find mkdir rm cp mv cmp mktemp printf cat dirname basename tr head tail xargs sleep date wc sha256sum git sort uniq stat ln readlink jq comm touch chmod cut id env true false echo test; do cmd_path="$(command -v "$cmd" 2>/dev/null || true)" [ -n "$cmd_path" ] && ln -s "$cmd_path" "$stub_dir/$cmd" done @@ -946,7 +946,7 @@ else EXPECTED_PLUGINS="go-dev go-web go-workflow gopher-guides llm-tools tailwind" MISSING="" for p in $EXPECTED_PLUGINS; do - if ! rg -qx "plugin add ${p}@gopher-ai" "$STUB_LOG"; then + if ! grep -qx "plugin add ${p}@gopher-ai" "$STUB_LOG"; then MISSING="$MISSING $p(command)" elif [ ! -f "$TMP_HOME/.codex/plugins/cache/gopher-ai/$p/$CURRENT_VERSION/.codex-plugin/plugin.json" ]; then MISSING="$MISSING $p(cache)" @@ -957,7 +957,7 @@ else echo "FAIL (plugin add calls were incorrect:$MISSING; count=$ADD_COUNT)" sed -n '1,40p' "$STUB_LOG" ERRORS=$((ERRORS + 1)) - elif ! rg -qx 'plugin marketplace add gopherguides/gopher-ai --ref main' "$STUB_LOG"; then + elif ! grep -qx 'plugin marketplace add gopherguides/gopher-ai --ref main' "$STUB_LOG"; then echo "FAIL (marketplace was not registered through the Codex CLI)" ERRORS=$((ERRORS + 1)) else @@ -966,10 +966,10 @@ else fi echo -n "Codex --user leaves config and cache publication to the CLI... " -if [ -f "$TMP_HOME/.codex/config.toml" ] && rg -q '^\[plugins\.' "$TMP_HOME/.codex/config.toml"; then +if [ -f "$TMP_HOME/.codex/config.toml" ] && grep -q '^\[plugins\.' "$TMP_HOME/.codex/config.toml"; then echo "FAIL (installer wrote plugin entries to config.toml)" ERRORS=$((ERRORS + 1)) -elif rg -q 'populating cache|enabled [0-9]+ new plugin' /tmp/gopher-ai-user-install.log; then +elif grep -Eq 'populating cache|enabled [0-9]+ new plugin' /tmp/gopher-ai-user-install.log; then echo "FAIL (installer reported private cache or config writes)" ERRORS=$((ERRORS + 1)) else @@ -981,10 +981,10 @@ echo -n "Codex --user upgrades a registered marketplace through the CLI... " HOME="$TMP_HOME" PATH="$STUB_PATH" CODEX_STUB_LOG="$STUB_LOG" \ CODEX_STUB_SOURCE_ROOT="$ROOT_DIR" CODEX_STUB_MARKETPLACE_REGISTERED=true \ bash "$ROOT_DIR/scripts/install-codex.sh" --user >/dev/null 2>&1 -if ! rg -qx 'plugin marketplace upgrade gopher-ai' "$STUB_LOG"; then +if ! grep -qx 'plugin marketplace upgrade gopher-ai' "$STUB_LOG"; then echo "FAIL (registered marketplace was not upgraded)" ERRORS=$((ERRORS + 1)) -elif rg -q '^plugin marketplace add ' "$STUB_LOG"; then +elif grep -q '^plugin marketplace add ' "$STUB_LOG"; then echo "FAIL (registered marketplace was added again)" ERRORS=$((ERRORS + 1)) else @@ -1131,10 +1131,10 @@ set -e if [ "$EXIT" -eq 0 ]; then echo "FAIL (--user should reject an unsupported Codex CLI)" ERRORS=$((ERRORS + 1)) -elif ! rg -q "does not support 'codex plugin add'" /tmp/gopher-ai-user-old-codex.log; then +elif ! grep -q "does not support 'codex plugin add'" /tmp/gopher-ai-user-old-codex.log; then echo "FAIL (error did not explain the required CLI support)" ERRORS=$((ERRORS + 1)) -elif rg -q '^plugin marketplace ' "$STUB_LOG"; then +elif grep -q '^plugin marketplace ' "$STUB_LOG"; then echo "FAIL (installer mutated marketplace state before the capability check)" ERRORS=$((ERRORS + 1)) else