mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-06-10 10:13:36 +00:00
Compare commits
4 Commits
add-hana-c
...
fix-2071-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a67587c816 | ||
|
|
502de97746 | ||
|
|
679f52da9e | ||
|
|
13a0208f38 |
@@ -2135,22 +2135,6 @@
|
||||
},
|
||||
"homepage": "https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server"
|
||||
},
|
||||
{
|
||||
"name": "sap-hana-cli",
|
||||
"description": "150+ SAP HANA database tools for AI assistants. Query tables, import/export data, profile data quality, compare schemas, manage backups, monitor performance, and more. Connects to SAP HANA Cloud and on-premise databases.",
|
||||
"author": {
|
||||
"name": "SAP SE",
|
||||
"email": "ospo@sap.com",
|
||||
"url": "https://www.sap.com"
|
||||
},
|
||||
"category": "database",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/SAP-samples/hana-cli-claude-plugin.git",
|
||||
"sha": "160ae47efaffea2e1dd9d6877ab9ec49b78542a0"
|
||||
},
|
||||
"homepage": "https://github.com/SAP-samples/hana-cli-claude-plugin"
|
||||
},
|
||||
{
|
||||
"name": "sap-mdk-server",
|
||||
"description": "MCP server for SAP Mobile Development Kit (MDK). Build and modify MDK applications with AI assistance — schema lookups, action validation, rule editing, and project scaffolding.",
|
||||
@@ -2601,6 +2585,20 @@
|
||||
},
|
||||
"homepage": "https://github.com/vercel/vercel-plugin"
|
||||
},
|
||||
{
|
||||
"name": "vibe-prospecting",
|
||||
"description": "Vibe Prospecting connects Claude to live B2B company and contact data so users can search, match, enrich, filter, and export prospects at scale. It turns natural-language requests into structured GTM workflows for lead generation, CRM enrichment, company research, executive discovery, and multi-step prospecting automation inside Claude Cowork and Claude Code.",
|
||||
"author": {
|
||||
"name": "vibeprospecting.ai"
|
||||
},
|
||||
"category": "productivity",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/explorium-ai/vibeprospecting-plugin.git",
|
||||
"sha": "ada4d569dbf70194fe18750ecbc5170e9a3f120a"
|
||||
},
|
||||
"homepage": "https://www.vibeprospecting.ai/product/claude-plugin"
|
||||
},
|
||||
{
|
||||
"name": "windsor-ai",
|
||||
"description": "Connect Claude Code to 325+ business data sources via Windsor.ai. Query marketing, sales, CRM, ecommerce, finance, and analytics data from Google Ads, Meta, HubSpot, Salesforce, Shopify, Stripe, and hundreds more — directly from your terminal.",
|
||||
|
||||
163
.github/workflows/scan-plugins.yml
vendored
163
.github/workflows/scan-plugins.yml
vendored
@@ -381,3 +381,166 @@ jobs:
|
||||
echo "::error::Scan step failed without a parseable policy verdict (likely an infra error)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# emit-verdict: post a sticky comment per entry to the bump PR with the
|
||||
# structured verdict, so downstream tooling (label automation, delist
|
||||
# authoring) can read verdicts directly instead of scraping job logs.
|
||||
# Sticky comment marker: `<!-- bump-pr-verdict:<name> -->`.
|
||||
#
|
||||
# Mirrors the schema_v1 contract from
|
||||
# anthropics/claude-plugins-community-internal#3908 so the triage scripts
|
||||
# in mcp-local-directory/scripts/triage/ work uniformly across both repos.
|
||||
# -official doesn't run per-entry static checks (zombie, schema, binaries,
|
||||
# etc.) so the `scan.*` axes are emitted as "skipped". The granular policy
|
||||
# booleans (`has_broad_scope_hooks`, `has_undisclosed_telemetry`,
|
||||
# `description_matches_behavior`) aren't surfaced by this workflow's
|
||||
# per-entry artifact yet, so they're emitted as null; the triage
|
||||
# `triage_bool_to_str` helper maps null → "?" so display is graceful.
|
||||
# Status describes the execution state, not the outcome — `ran` when the
|
||||
# scan action evaluated this SHA fresh, `cached` when a prior verdict was
|
||||
# reused (cf. run-verdicts.json's `source` field). Outcome lives in
|
||||
# `policy.passes`. policy-sweep.sh dispatches on this exact vocabulary.
|
||||
#
|
||||
# PR resolution: pull_request events carry the PR number directly. The
|
||||
# bump workflow creates bump PRs via GITHUB_TOKEN (which doesn't fire
|
||||
# pull_request triggers — recursion guard) and dispatches this scan via
|
||||
# workflow_dispatch on the bump branch. In that case we look up the
|
||||
# open PR by head ref. No PR (scan_all dispatch on main, etc.) → no-op.
|
||||
#
|
||||
# continue-on-error at the job level: emit failure must NOT block the
|
||||
# `scan` required check. Consumers fall back to log-scraping if the
|
||||
# comment is absent (gradual migration; no flag day).
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
emit-verdict:
|
||||
needs: [scan]
|
||||
if: always() && needs.scan.result != 'skipped' && needs.scan.result != 'cancelled'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Download scan verdicts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scan-verdicts
|
||||
path: /tmp/scan-verdicts
|
||||
continue-on-error: true
|
||||
|
||||
- name: Resolve PR number for this ref
|
||||
id: pr
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
PR_FROM_EVENT: ${{ github.event.pull_request.number }}
|
||||
REF: ${{ github.ref_name }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_FROM_EVENT" ]]; then
|
||||
echo "number=$PR_FROM_EVENT" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
# workflow_dispatch on the bump branch: find the open PR for it.
|
||||
# head filter takes the form owner:branch.
|
||||
owner="${REPO%%/*}"
|
||||
pr=$(gh api "/repos/${REPO}/pulls?state=open&head=${owner}:${REF}&per_page=1" \
|
||||
--jq '.[0].number // ""')
|
||||
if [[ -z "$pr" ]]; then
|
||||
echo "::notice::No open PR for ref ${REF} — sticky comments skipped (verdicts still in scan-verdicts artifact)"
|
||||
fi
|
||||
echo "number=$pr" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build and post sticky comments
|
||||
if: steps.pr.outputs.number != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR: ${{ steps.pr.outputs.number }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
verdicts_path=/tmp/scan-verdicts/run-verdicts.json
|
||||
# Missing/empty artifact: scan job ran but didn't produce verdicts
|
||||
# (e.g. the relevance gate said "no changes"). Nothing to comment;
|
||||
# exit clean.
|
||||
if [[ ! -s "$verdicts_path" ]]; then
|
||||
echo "::notice::No run-verdicts.json artifact — nothing to emit"
|
||||
exit 0
|
||||
fi
|
||||
count=$(jq 'length' "$verdicts_path")
|
||||
if [[ "$count" == "0" ]]; then
|
||||
echo "::notice::run-verdicts.json is empty — nothing to emit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ran_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# scan.* axes: -official doesn't run per-entry static checks; emit
|
||||
# "skipped" for each so the schema is shape-compatible with -internal.
|
||||
scan_stub='{"clone":"skipped","subpath_missing":"skipped","schema":"skipped","zombie":"skipped","tool_allowlist":"skipped","binaries":"skipped","unique":"skipped","mcp":"skipped"}'
|
||||
|
||||
# Pre-fetch all PR comments once (paginated) for the marker lookup.
|
||||
gh api --paginate "/repos/$REPO/issues/$PR/comments" \
|
||||
--jq '.[] | {id, body}' > /tmp/comments.ndjson
|
||||
|
||||
jq -c '.[]' "$verdicts_path" | while read -r entry; do
|
||||
name=$(jq -r '.name' <<< "$entry")
|
||||
passes=$(jq -r '.passes' <<< "$entry")
|
||||
summary=$(jq -r '.summary // ""' <<< "$entry")
|
||||
violations=$(jq -r '.violations // ""' <<< "$entry")
|
||||
source=$(jq -r '.source // "scan"' <<< "$entry")
|
||||
|
||||
# status = execution state (cf. -internal#3908 vocabulary).
|
||||
# Outcome is in `passes`. Map source → status: scan-action-run
|
||||
# → "ran"; cache-served → "cached". Anything else falls through
|
||||
# as "ran" (only those two values appear in run-verdicts.json).
|
||||
case "$source" in
|
||||
cache) status="cached" ;;
|
||||
scan) status="ran" ;;
|
||||
*) status="ran" ;;
|
||||
esac
|
||||
|
||||
policy=$(jq -n \
|
||||
--argjson passes "$passes" \
|
||||
--arg summary "$summary" \
|
||||
--arg violations "$violations" \
|
||||
--arg source "$source" \
|
||||
--arg status "$status" \
|
||||
'{passes: $passes,
|
||||
has_broad_scope_hooks: null,
|
||||
has_undisclosed_telemetry: null,
|
||||
description_matches_behavior: null,
|
||||
summary: $summary,
|
||||
violations: $violations,
|
||||
source: $source,
|
||||
status: $status}')
|
||||
|
||||
verdict=$(jq -n \
|
||||
--argjson scan "$scan_stub" \
|
||||
--argjson policy "$policy" \
|
||||
--arg ran_at "$ran_at" \
|
||||
--arg run_id "$RUN_ID" \
|
||||
'{schema_version: 1, ran_at: $ran_at, run_id: $run_id, scan: $scan, policy: $policy}')
|
||||
|
||||
marker="<!-- bump-pr-verdict:$name -->"
|
||||
body=$(printf '%s\n```json\n%s\n```' "$marker" "$verdict")
|
||||
|
||||
# jq's first() short-circuits and avoids SIGPIPE under pipefail if
|
||||
# duplicate markers exist (shouldn't, but a prior buggy run could
|
||||
# double-post). -s slurps NDJSON; `// empty` yields no output when
|
||||
# no match.
|
||||
existing=$(jq -rs --arg m "$marker" \
|
||||
'first(.[] | select(.body | startswith($m)) | .id) // empty' \
|
||||
/tmp/comments.ndjson)
|
||||
|
||||
if [[ -n "$existing" ]]; then
|
||||
gh api -X PATCH "/repos/$REPO/issues/comments/$existing" -f body="$body" >/dev/null
|
||||
echo "Updated comment $existing for $name"
|
||||
else
|
||||
gh api -X POST "/repos/$REPO/issues/$PR/comments" -f body="$body" >/dev/null
|
||||
echo "Created comment for $name"
|
||||
fi
|
||||
done
|
||||
|
||||
31
README.md
31
README.md
@@ -42,6 +42,37 @@ plugin-name/
|
||||
└── README.md # Documentation
|
||||
```
|
||||
|
||||
## Skill-bundle plugins
|
||||
|
||||
When a plugin's source repository ships skills (`SKILL.md` files) without a `.claude-plugin/plugin.json` manifest, the marketplace entry can declare the skills directly using `strict: false` and an explicit `skills` array.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "example-bundle",
|
||||
"description": "Brief description of the bundled skills.",
|
||||
"author": { "name": "Author Name" },
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "https://github.com/example-org/sdk.git",
|
||||
"path": "packages/agent-skills",
|
||||
"ref": "main",
|
||||
"sha": "<commit sha>"
|
||||
},
|
||||
"strict": false,
|
||||
"skills": [
|
||||
"./skill-a",
|
||||
"./skill-b",
|
||||
"./skill-c"
|
||||
],
|
||||
"homepage": "https://github.com/example-org/sdk"
|
||||
}
|
||||
```
|
||||
|
||||
Each path in `skills` is relative to `source.path` and points at a directory containing a `SKILL.md`. Paths can reach deeper than a single level — for example, `["./libA/skill-1", "./libB/skill-2"]` exposes a curated subset across multiple library subdirectories. Each skill is registered as `<plugin-name>:<skill-name>` in Claude Code.
|
||||
|
||||
For the underlying schema, see [Strict mode](https://code.claude.com/docs/en/plugin-marketplaces) in the marketplace documentation.
|
||||
|
||||
## License
|
||||
|
||||
Please see each linked plugin for the relevant LICENSE file.
|
||||
|
||||
@@ -32,6 +32,8 @@ BUILD_FAILED = 3 # venv create or pip install raised/timed out
|
||||
# llm.py also matches Windows venv layout (Lib/site-packages). Don't reuse the
|
||||
# value — telemetry rows from older plugin builds still emit 4.
|
||||
SKIP_SENTINEL = 5 # another SessionStart is currently building
|
||||
HOOK_PY_INCOMPATIBLE = 6 # hook interpreter is <3.10 — SDK syntax can't load
|
||||
# here no matter how the venv was built. See #2071.
|
||||
|
||||
|
||||
def _sdk_on_syspath() -> bool:
|
||||
@@ -62,6 +64,29 @@ def main() -> tuple[int, str, str]:
|
||||
err_phase / err_kind are non-empty only on BUILD_FAILED — they let
|
||||
telemetry split bootstrap failures by root cause.
|
||||
"""
|
||||
# Honesty check (fixes the misleading NOOP_VENV in #2071): the SDK
|
||||
# requires Python >=3.10 and uses 3.10+ syntax (match statements,
|
||||
# PEP 604 unions). On a 3.9 hook interpreter we CANNOT import it no
|
||||
# matter how the venv was built — llm.py runs in this same interpreter
|
||||
# and the syntax-level import will SyntaxError. macOS ships 3.9.6 as
|
||||
# the default `python3` and `/usr/bin` precedes Homebrew in PATH, so
|
||||
# this case is the default state for a large share of macOS users.
|
||||
#
|
||||
# sg-python.sh now prefers python3.10+ binaries so most users won't
|
||||
# reach this branch; the fallback to 3.9 is preserved for the
|
||||
# pattern-warning hooks that don't need the SDK. Reporting
|
||||
# HOOK_PY_INCOMPATIBLE here:
|
||||
# (a) avoids 30-60s of wasted pip install,
|
||||
# (b) avoids the lie where the venv_py probe says NOOP_VENV but the
|
||||
# consumer import fails, and
|
||||
# (c) gives telemetry a clean bucket to size the affected fleet.
|
||||
if sys.version_info < (3, 10):
|
||||
return (
|
||||
HOOK_PY_INCOMPATIBLE,
|
||||
"hook_py",
|
||||
f"py_{sys.version_info[0]}.{sys.version_info[1]}",
|
||||
)
|
||||
|
||||
if _sdk_on_syspath():
|
||||
return NOOP_SYSTEM, "", ""
|
||||
|
||||
@@ -195,6 +220,56 @@ def main() -> tuple[int, str, str]:
|
||||
sentinel.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def _maybe_emit_user_notice(outcome: int, pv: int) -> str | None:
|
||||
"""Return a one-time user-visible notice when the agentic reviewer is
|
||||
in a persistent broken state on this machine, or None if we've already
|
||||
shown the notice for this plugin version (or shouldn't show one).
|
||||
|
||||
The marker file is plugin-version-keyed: a future plugin update can
|
||||
re-notify if behavior changes (e.g. we ship out-of-process SDK in v3
|
||||
and want to tell affected users it's fixed). Failures to write the
|
||||
marker degrade to "skip the notice this session" so we don't spam
|
||||
every SessionStart on a read-only home dir.
|
||||
|
||||
Currently only HOOK_PY_INCOMPATIBLE qualifies. BUILD_FAILED is
|
||||
intentionally excluded — it covers transient causes (network failure,
|
||||
pip registry hiccup, in-flight rebuild) where the next session may
|
||||
succeed and a permanent notice would mislead.
|
||||
"""
|
||||
if outcome != HOOK_PY_INCOMPATIBLE:
|
||||
return None
|
||||
try:
|
||||
state_dir = Path(
|
||||
os.environ.get("SECURITY_WARNINGS_STATE_DIR")
|
||||
or os.path.expanduser("~/.claude/security")
|
||||
)
|
||||
marker = state_dir / f".agentic_unavailable_notice_v{pv or 0}"
|
||||
if marker.exists():
|
||||
return None
|
||||
state_dir.mkdir(parents=True, exist_ok=True)
|
||||
# Write timestamp + Python version so the marker is self-documenting
|
||||
# if a user goes looking. O_EXCL would be racier with no real win
|
||||
# (two concurrent SessionStarts both showing the notice once is fine).
|
||||
marker.write_text(
|
||||
f"{time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())} "
|
||||
f"py={sys.version_info[0]}.{sys.version_info[1]}\n"
|
||||
)
|
||||
except OSError:
|
||||
return None
|
||||
return (
|
||||
f"⚠ security-guidance plugin: the cross-file commit reviewer "
|
||||
f"(layer 3 of 3 — catches IDOR, auth-bypass, cross-file SSRF) "
|
||||
f"is unavailable in this environment. It requires Python ≥3.10, "
|
||||
f"but the hook is running on "
|
||||
f"{sys.version_info[0]}.{sys.version_info[1]}.\n\n"
|
||||
f"Pattern checks and the single-shot LLM diff review are still "
|
||||
f"active. To enable the deeper reviewer, install Python 3.10+ "
|
||||
f"(e.g. `brew install python` on macOS) and restart Claude Code.\n\n"
|
||||
f"This notice is shown once per plugin version. "
|
||||
f"See: github.com/anthropics/claude-plugins-official/issues/2071"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Tell the harness this is async — venv create + pip install can take
|
||||
# 30-60s on a cold cache, well past the default sync hook timeout.
|
||||
@@ -231,4 +306,18 @@ if __name__ == "__main__":
|
||||
pv = _plugin_version_int()
|
||||
if pv:
|
||||
metrics["pv"] = pv
|
||||
print(json.dumps({"metrics": metrics}), flush=True)
|
||||
response: dict[str, object] = {"metrics": metrics}
|
||||
# One-time user-visible notice when the agentic reviewer is dead on
|
||||
# arrival. Uses hookSpecificOutput.additionalContext (SessionStart's
|
||||
# supported channel for surfacing text to both the model and the user)
|
||||
# plus systemMessage as a belt-and-suspenders. Marker-file-gated so
|
||||
# this fires exactly once per plugin version per install — see
|
||||
# _maybe_emit_user_notice.
|
||||
notice = _maybe_emit_user_notice(outcome, pv)
|
||||
if notice:
|
||||
response["hookSpecificOutput"] = {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": notice,
|
||||
}
|
||||
response["systemMessage"] = notice
|
||||
print(json.dumps(response), flush=True)
|
||||
|
||||
@@ -47,21 +47,65 @@ fi
|
||||
|
||||
probe() {
|
||||
# $1..N: the interpreter command (may be multi-word like `py -3`)
|
||||
# Probe writes the major version to stdout and exits 0 iff it's >=3.
|
||||
"$@" -c 'import sys; print(sys.version_info[0])' 2>/dev/null
|
||||
# Writes "<major>.<minor>" to stdout and exits 0 iff at least Python 3.
|
||||
"$@" -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")' 2>/dev/null
|
||||
}
|
||||
|
||||
# True iff arg is a "M.m" version string >= 3.10. claude_agent_sdk requires
|
||||
# Python >= 3.10; below that, pip install fails ("No matching distribution")
|
||||
# and the LLM-powered review (Stop / commit / push) silently no-ops while
|
||||
# pattern checks (PostToolUse regex) keep working. macOS ships 3.9.6 as the
|
||||
# default `python3` on current versions, so this guard matters in practice.
|
||||
# See anthropics/claude-plugins-official#2071.
|
||||
is_sdk_compatible() {
|
||||
case "$1" in
|
||||
3.1[0-9]|3.[2-9][0-9]|[4-9].*|[1-9][0-9].*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Pass 1 — try minor-versioned binaries in descending order. These are only
|
||||
# present if the user explicitly installed them (Homebrew / python.org / pyenv),
|
||||
# so picking one here always upgrades over the system `python3`. Highest
|
||||
# available wins; the user doesn't have to PATH-prefer it.
|
||||
for cmd in "python3.13" "python3.12" "python3.11" "python3.10"; do
|
||||
v=$(probe "$cmd") || continue
|
||||
if is_sdk_compatible "$v"; then
|
||||
exec "$cmd" "$@"
|
||||
fi
|
||||
done
|
||||
|
||||
# Pass 2 — bare interpreters, but only if SDK-compatible. Covers Linux distros
|
||||
# that ship 3.10+ as the default `python3`, and Windows where `python` /
|
||||
# `py -3` resolves to the user's python.org install.
|
||||
for cmd in "python3" "python" "py -3"; do
|
||||
# Word-split intentionally so `py -3` works
|
||||
# shellcheck disable=SC2086
|
||||
v=$(probe $cmd) || continue
|
||||
if [ "$v" = "3" ]; then
|
||||
if is_sdk_compatible "$v"; then
|
||||
# shellcheck disable=SC2086
|
||||
exec $cmd "$@"
|
||||
fi
|
||||
done
|
||||
|
||||
# Pass 3 — fallback to any Python 3, even <3.10. Pattern-based checks
|
||||
# (PostToolUse regex on Edit/Write) only need 3.6+ and are useful on their
|
||||
# own; the SDK-dependent paths will detect the version mismatch and degrade
|
||||
# inside the Python code. Without this fallback, the entire plugin would
|
||||
# stop working on default macOS, which is a regression vs today.
|
||||
for cmd in "python3" "python" "py -3"; do
|
||||
# shellcheck disable=SC2086
|
||||
v=$(probe $cmd) || continue
|
||||
# Accept anything that successfully reported a "M.m" string.
|
||||
case "$v" in
|
||||
[0-9]*.[0-9]*)
|
||||
# shellcheck disable=SC2086
|
||||
exec $cmd "$@"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "security-guidance: no working Python 3 interpreter found." >&2
|
||||
echo " tried: python3, python, py -3" >&2
|
||||
echo " tried: python3.13, python3.12, python3.11, python3.10, python3, python, py -3" >&2
|
||||
echo " on Windows, install Python from https://python.org (NOT the Microsoft Store)" >&2
|
||||
echo " on macOS, install Python 3.10+ via Homebrew (\`brew install python\`)" >&2
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user