| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- #!/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())
|