Files
broodcicada-glitchandCursor 7154ed7fa5 Add Cursor generator plugin with slash commands and installers. (#417)
Ship a first-class Cursor adaptation (commands, skill, rules, marketplace manifest, CI installer tests) and document generator vs Hub consumer install paths in README/README_CN.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 15:25:52 +08:00

229 lines
10 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLUGIN_SRC="$(cd "${SCRIPT_DIR}/.." && pwd)"
REPO_ROOT="$(cd "${PLUGIN_SRC}/.." && pwd)"
CANONICAL_PLUGIN="${REPO_ROOT}/cli-anything-plugin"
TMP_DIR="$(mktemp -d)"
# Isolate HOME so discovery pointer and default paths stay inside the temp tree.
export HOME="${TMP_DIR}/home"
mkdir -p "${HOME}"
PLUGINS_HOME="${TMP_DIR}/plugins"
INSTALLED_DIR="${PLUGINS_HOME}/local/cli-anything"
STALE_STAGING_DIR="${PLUGINS_HOME}/local/.cli-anything.tmp.stale"
DISCOVERY_POINTER="${HOME}/.cursor/cli-anything-generator.root"
cleanup() {
rm -rf "${TMP_DIR}"
}
trap cleanup EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
assert_file() {
[[ -f "$1" ]] || fail "expected file: $1"
}
assert_same() {
cmp -s "$1" "$2" || fail "files differ: $1 $2"
}
assert_tree_same() {
diff -qr "$1" "$2" >/dev/null || fail "directories differ: $1 $2"
}
mkdir -p "${STALE_STAGING_DIR}"
echo "left over from an interrupted install" > "${STALE_STAGING_DIR}/stale.txt"
CURSOR_PLUGINS_HOME="${PLUGINS_HOME}" bash "${PLUGIN_SRC}/scripts/install.sh"
assert_file "${INSTALLED_DIR}/.cursor-plugin/plugin.json"
assert_file "${INSTALLED_DIR}/PLUGIN_ROOT.txt"
assert_file "${DISCOVERY_POINTER}"
assert_file "${INSTALLED_DIR}/commands/cli-anything.md"
assert_file "${INSTALLED_DIR}/commands/cli-anything-refine.md"
assert_file "${INSTALLED_DIR}/commands/cli-anything-test.md"
assert_file "${INSTALLED_DIR}/commands/cli-anything-validate.md"
assert_file "${INSTALLED_DIR}/commands/cli-anything-list.md"
assert_file "${INSTALLED_DIR}/skills/cli-anything-generator/SKILL.md"
assert_file "${INSTALLED_DIR}/rules/cli-anything-generator.mdc"
assert_file "${INSTALLED_DIR}/references/HARNESS.md"
assert_file "${INSTALLED_DIR}/references/commands/cli-anything.md"
assert_file "${INSTALLED_DIR}/references/commands/refine.md"
assert_file "${INSTALLED_DIR}/references/commands/test.md"
assert_file "${INSTALLED_DIR}/references/commands/validate.md"
assert_file "${INSTALLED_DIR}/references/commands/list.md"
assert_file "${INSTALLED_DIR}/references/guides/auto-save-dry-run.md"
assert_file "${INSTALLED_DIR}/references/guides/session-locking.md"
assert_file "${INSTALLED_DIR}/references/guides/preview-methodology.md"
assert_file "${INSTALLED_DIR}/scripts/repl_skin.py"
assert_file "${INSTALLED_DIR}/scripts/preview_bundle.py"
assert_file "${INSTALLED_DIR}/scripts/skill_generator.py"
assert_file "${INSTALLED_DIR}/scripts/templates/SKILL.md.template"
assert_file "${INSTALLED_DIR}/references/docs/PREVIEW_PROTOCOL.md"
assert_file "${INSTALLED_DIR}/scripts/install.sh"
assert_file "${INSTALLED_DIR}/scripts/install.ps1"
assert_file "${INSTALLED_DIR}/scripts/uninstall.sh"
assert_file "${INSTALLED_DIR}/scripts/uninstall.ps1"
assert_file "${INSTALLED_DIR}/scripts/lib.sh"
assert_file "${INSTALLED_DIR}/scripts/lib.ps1"
STAMP="$(tr -d '\r\n' < "${INSTALLED_DIR}/PLUGIN_ROOT.txt")"
POINTER="$(tr -d '\r\n' < "${DISCOVERY_POINTER}")"
[[ -n "${STAMP}" ]] || fail "PLUGIN_ROOT.txt is empty"
[[ "${STAMP}" == "${POINTER}" ]] || fail "discovery pointer mismatch: ${POINTER} != ${STAMP}"
assert_file "${INSTALLED_DIR}/references/HARNESS.md"
# On Git Bash / MSYS, stamps must be Windows host paths for Cursor Read tools.
case "$(uname -s 2>/dev/null || true)" in
MINGW*|MSYS*|CYGWIN*)
[[ "${STAMP}" =~ ^[A-Za-z]:\\ ]] ||
fail "Windows Git Bash install must stamp a drive path, got: ${STAMP}"
if command -v cygpath >/dev/null 2>&1; then
STAMP_UNIX="$(cygpath -u "${STAMP}")"
assert_file "${STAMP_UNIX}/references/HARNESS.md"
fi
;;
*)
assert_file "${STAMP}/references/HARNESS.md"
;;
esac
[[ -d "${STALE_STAGING_DIR}" ]] ||
fail "installer reused or removed the stale staging directory"
[[ ! -d "${INSTALLED_DIR}/tests" ]] ||
fail "installer copied tests/ into the installed plugin"
[[ ! -d "${INSTALLED_DIR}/cursor-plugin" ]] ||
fail "installer created a nested cursor-plugin directory"
[[ ! -d "${INSTALLED_DIR}/skills/cli-anything" ]] ||
fail "old skills/cli-anything name should not be installed"
assert_same "${CANONICAL_PLUGIN}/HARNESS.md" "${INSTALLED_DIR}/references/HARNESS.md"
assert_same "${CANONICAL_PLUGIN}/repl_skin.py" "${INSTALLED_DIR}/scripts/repl_skin.py"
assert_same "${CANONICAL_PLUGIN}/preview_bundle.py" "${INSTALLED_DIR}/scripts/preview_bundle.py"
assert_same "${CANONICAL_PLUGIN}/skill_generator.py" "${INSTALLED_DIR}/scripts/skill_generator.py"
assert_same "${REPO_ROOT}/docs/PREVIEW_PROTOCOL.md" "${INSTALLED_DIR}/references/docs/PREVIEW_PROTOCOL.md"
assert_tree_same "${CANONICAL_PLUGIN}/commands" "${INSTALLED_DIR}/references/commands"
assert_tree_same "${CANONICAL_PLUGIN}/guides" "${INSTALLED_DIR}/references/guides"
assert_tree_same "${CANONICAL_PLUGIN}/templates" "${INSTALLED_DIR}/scripts/templates"
python3 -m py_compile \
"${INSTALLED_DIR}/scripts/repl_skin.py" \
"${INSTALLED_DIR}/scripts/preview_bundle.py" \
"${INSTALLED_DIR}/scripts/skill_generator.py"
if CURSOR_PLUGINS_HOME="${PLUGINS_HOME}" bash "${PLUGIN_SRC}/scripts/install.sh" >/dev/null 2>&1; then
fail "installer overwrote an existing plugin without --force"
fi
MARKER="${INSTALLED_DIR}/.install-marker"
echo "first-install" > "${MARKER}"
CURSOR_PLUGINS_HOME="${PLUGINS_HOME}" bash "${PLUGIN_SRC}/scripts/install.sh" --force
[[ ! -f "${MARKER}" ]] || fail "--force upgrade did not replace the previous install"
assert_file "${INSTALLED_DIR}/references/HARNESS.md"
assert_file "${INSTALLED_DIR}/PLUGIN_ROOT.txt"
STAMP2="$(tr -d '\r\n' < "${INSTALLED_DIR}/PLUGIN_ROOT.txt")"
[[ -n "${STAMP2}" ]] || fail "stamp after --force is empty"
assert_file "${INSTALLED_DIR}/references/HARNESS.md"
grep -q 'cli-anything-generator.root' "${INSTALLED_DIR}/commands/cli-anything.md" ||
fail "installed command does not document discovery pointer"
grep -q 'agent-harness/.cli-anything-progress.json' "${INSTALLED_DIR}/commands/cli-anything.md" ||
fail "installed command does not use agent-harness progress path"
grep -q 'alwaysApply: false' "${INSTALLED_DIR}/rules/cli-anything-generator.mdc" ||
fail "generator rule must be alwaysApply false (avoid global context pollution)"
grep -q 'agent-harness/\*\*' "${INSTALLED_DIR}/rules/cli-anything-generator.mdc" ||
fail "generator rule must include agent-harness globs"
grep -q 'references/HARNESS.md' "${INSTALLED_DIR}/skills/cli-anything-generator/SKILL.md" ||
fail "installed skill does not point to vendored HARNESS.md"
grep -q 'cli-hub-meta-skill' "${INSTALLED_DIR}/skills/cli-anything-generator/SKILL.md" ||
fail "installed skill does not document consumer Hub path"
python3 - <<'PY' "${INSTALLED_DIR}/.cursor-plugin/plugin.json"
import json, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
assert data.get("name") == "cli-anything", data
PY
MARKETPLACE="${REPO_ROOT}/.cursor-plugin/marketplace.json"
assert_file "${MARKETPLACE}"
python3 - <<'PY' "${MARKETPLACE}"
import json, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
names = [p.get("name") for p in data.get("plugins", [])]
assert "cli-anything" in names, names
sources = [p.get("source") for p in data.get("plugins", [])]
assert "./cursor-plugin" in sources, sources
PY
DETACHED_ROOT="${TMP_DIR}/detached"
DETACHED_LOG="${TMP_DIR}/detached-install.log"
mkdir -p "${DETACHED_ROOT}"
cp -R "${PLUGIN_SRC}" "${DETACHED_ROOT}/cursor-plugin"
if CURSOR_PLUGINS_HOME="${TMP_DIR}/detached-plugins/plugins" bash "${DETACHED_ROOT}/cursor-plugin/scripts/install.sh" >"${DETACHED_LOG}" 2>&1; then
fail "installer accepted a detached plugin without canonical resources"
fi
grep -q 'Cannot find canonical CLI-Anything resources' "${DETACHED_LOG}" ||
fail "detached install did not explain the missing canonical resources"
BAD_HOME_LOG="${TMP_DIR}/bad-home.log"
BAD_HOME_PATH="${TMP_DIR}/not-plugins-dir"
if CURSOR_PLUGINS_HOME="${BAD_HOME_PATH}" bash "${PLUGIN_SRC}/scripts/install.sh" >"${BAD_HOME_LOG}" 2>&1; then
fail "installer accepted CURSOR_PLUGINS_HOME whose basename is not plugins"
fi
grep -q "named 'plugins'" "${BAD_HOME_LOG}" ||
fail "installer did not reject non-plugins CURSOR_PLUGINS_HOME"
[[ ! -e "${BAD_HOME_PATH}" ]] ||
fail "rejected CURSOR_PLUGINS_HOME should not create the target directory"
TRAVERSAL_LOG="${TMP_DIR}/traversal.log"
TRAVERSAL_OUTSIDE="${TMP_DIR}/outside"
# After normalization this becomes TMP_DIR/outside (basename outside), not plugins.
if CURSOR_PLUGINS_HOME="${TMP_DIR}/evil/../outside" bash "${PLUGIN_SRC}/scripts/install.sh" >"${TRAVERSAL_LOG}" 2>&1; then
fail "installer accepted traversed CURSOR_PLUGINS_HOME"
fi
grep -q "named 'plugins'" "${TRAVERSAL_LOG}" ||
fail "installer did not reject traversed CURSOR_PLUGINS_HOME"
[[ ! -e "${TRAVERSAL_OUTSIDE}" ]] ||
fail "rejected traversed CURSOR_PLUGINS_HOME should not create the outside directory"
# Uninstall clears install dir + matching discovery pointer.
CURSOR_PLUGINS_HOME="${PLUGINS_HOME}" bash "${PLUGIN_SRC}/scripts/uninstall.sh"
[[ ! -e "${INSTALLED_DIR}" ]] || fail "uninstall left plugin directory behind"
[[ ! -e "${DISCOVERY_POINTER}" ]] || fail "uninstall left discovery pointer behind"
# Reinstall, then foreign pointer must be preserved across uninstall.
CURSOR_PLUGINS_HOME="${PLUGINS_HOME}" bash "${PLUGIN_SRC}/scripts/install.sh"
assert_file "${INSTALLED_DIR}/references/HARNESS.md"
FOREIGN_ROOT="${TMP_DIR}/some-other-plugin-root"
printf '%s\n' "${FOREIGN_ROOT}" > "${DISCOVERY_POINTER}"
CURSOR_PLUGINS_HOME="${PLUGINS_HOME}" bash "${PLUGIN_SRC}/scripts/uninstall.sh"
[[ ! -e "${INSTALLED_DIR}" ]] || fail "uninstall left plugin directory behind after foreign-pointer case"
[[ -f "${DISCOVERY_POINTER}" ]] || fail "uninstall removed a non-matching discovery pointer"
LEFT_POINTER="$(tr -d '\r\n' < "${DISCOVERY_POINTER}")"
[[ "${LEFT_POINTER}" == "${FOREIGN_ROOT}" ]] ||
fail "foreign discovery pointer changed: ${LEFT_POINTER}"
# Shared lib path helpers
# shellcheck source=../scripts/lib.sh
source "${PLUGIN_SRC}/scripts/lib.sh"
is_cli_anything_install_path "/tmp/foo/local/cli-anything" ||
fail "is_cli_anything_install_path should accept .../local/cli-anything"
is_cli_anything_install_path 'C:\Users\x\.cursor\plugins\local\cli-anything' ||
fail "is_cli_anything_install_path should accept Windows ...\\local\\cli-anything"
if is_cli_anything_install_path "/tmp/CLI-Anything/cursor-plugin"; then
fail "is_cli_anything_install_path must reject repo cursor-plugin path"
fi
# Final reinstall for a clean installed tree at end of test.
CURSOR_PLUGINS_HOME="${PLUGINS_HOME}" bash "${PLUGIN_SRC}/scripts/install.sh"
assert_file "${INSTALLED_DIR}/references/HARNESS.md"
assert_file "${DISCOVERY_POINTER}"
echo "PASS: Cursor plugin installer vendors the complete CLI-Anything resource set."