mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-08-02 06:27:09 -03:00
100 lines
3.7 KiB
Python
Executable File
100 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Show the Claude Security banner as a display-only systemMessage.
|
|
|
|
Always exits 0 with either the banner or no output.
|
|
"""
|
|
|
|
import contextlib
|
|
import json
|
|
import os
|
|
import sys
|
|
from typing import cast
|
|
|
|
PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
LAUNCH_NOTICE = "Launching Claude Security..."
|
|
|
|
BOX_INNER = 53
|
|
|
|
MIN_PYTHON = (3, 9)
|
|
|
|
|
|
def plugin_version() -> str:
|
|
"""The plugin's version from plugin.json, or "unknown". Never raises."""
|
|
try:
|
|
path = os.path.join(PLUGIN_ROOT, ".claude-plugin", "plugin.json")
|
|
with open(path, encoding="utf-8") as handle:
|
|
loaded = cast("object", json.load(handle))
|
|
except Exception:
|
|
return "unknown"
|
|
if not isinstance(loaded, dict):
|
|
return "unknown"
|
|
version = cast("dict[str, object]", loaded).get("version")
|
|
return version if isinstance(version, str) and version else "unknown"
|
|
|
|
|
|
def box_line(text: str) -> str:
|
|
"""One boxed body line, centered so the right border always aligns."""
|
|
if len(text) > BOX_INNER:
|
|
text = text[:BOX_INNER]
|
|
return " │" + text.center(BOX_INNER) + "│"
|
|
|
|
|
|
def bottom_border(version: str) -> str:
|
|
"""The box's bottom edge with the version set into it, right-aligned."""
|
|
tag = f" v{version} "
|
|
fill = BOX_INNER - len(tag) - 3
|
|
if fill < 1:
|
|
return " └" + "─" * BOX_INNER + "┘"
|
|
return " └" + "─" * fill + tag + "─" * 3 + "┘"
|
|
|
|
|
|
def banner() -> str:
|
|
lines = [
|
|
"",
|
|
" ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗",
|
|
" ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝",
|
|
" ██║ ██║ ███████║██║ ██║██║ ██║█████╗",
|
|
" ██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝",
|
|
" ╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗",
|
|
" ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝",
|
|
" ──────── S · E · C · U · R · I · T · Y ────────",
|
|
" ┌" + "─" * BOX_INNER + "┐",
|
|
box_line("Find and fix vulnerabilities in source code"),
|
|
bottom_border(plugin_version()),
|
|
"",
|
|
]
|
|
return "\n".join(lines)
|
|
|
|
|
|
def emit(message: str) -> None:
|
|
"""Write one systemMessage. Never raises; a failed write is just no banner."""
|
|
try:
|
|
sys.stdout.write(json.dumps({"systemMessage": message}))
|
|
sys.stdout.flush()
|
|
except Exception:
|
|
# Also silence the interpreter's exit-time flush of the buffered message.
|
|
with contextlib.suppress(Exception):
|
|
os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
|
|
|
|
|
|
def main() -> int:
|
|
if sys.version_info < MIN_PYTHON:
|
|
need = f"{MIN_PYTHON[0]}.{MIN_PYTHON[1]}"
|
|
have = ".".join(str(part) for part in sys.version_info[:3])
|
|
emit(
|
|
f"\n\u26a0\ufe0f Claude Security needs python3 {need} or newer, but this "
|
|
f"python3 is {have}. Scanning and fixing will fail until a newer "
|
|
"python3 is first on PATH.\n"
|
|
)
|
|
return 0
|
|
try:
|
|
message = "\n" + LAUNCH_NOTICE + "\n\n" + banner()
|
|
except Exception:
|
|
message = "\n" + LAUNCH_NOTICE + "\n"
|
|
emit(message)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|