diff --git a/plugins/README.md b/plugins/README.md index cf4a21ec..c05083d6 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -25,6 +25,7 @@ Learn more in the [official plugins documentation](https://docs.claude.com/en/do | [pr-review-toolkit](./pr-review-toolkit/) | Comprehensive PR review agents specializing in comments, tests, error handling, type design, code quality, and code simplification | **Command:** `/pr-review-toolkit:review-pr` - Run with optional review aspects (comments, tests, errors, types, code, simplify, all)
**Agents:** `comment-analyzer`, `pr-test-analyzer`, `silent-failure-hunter`, `type-design-analyzer`, `code-reviewer`, `code-simplifier` | | [ralph-wiggum](./ralph-wiggum/) | Interactive self-referential AI loops for iterative development. Claude works on the same task repeatedly until completion | **Commands:** `/ralph-loop`, `/cancel-ralph` - Start/stop autonomous iteration loops
**Hook:** Stop - Intercepts exit attempts to continue iteration | | [security-guidance](./security-guidance/) | Security reminder hook that warns about potential security issues when editing files | **Hook:** PreToolUse - Monitors 9 security patterns including command injection, XSS, eval usage, dangerous HTML, pickle deserialization, and os.system calls | +| [slack-quote-formatter](./slack-quote-formatter/) | Enhances visual display of Slack forwarded and quoted messages with distinctive Unicode box formatting | **Hook:** UserPromptSubmit - Transforms `[Forwarded Message]` blocks into visually distinctive formatted boxes with clear boundaries and indentation | ## Installation diff --git a/plugins/slack-quote-formatter/.claude-plugin/plugin.json b/plugins/slack-quote-formatter/.claude-plugin/plugin.json new file mode 100644 index 00000000..63abb1cc --- /dev/null +++ b/plugins/slack-quote-formatter/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "slack-quote-formatter", + "version": "1.0.0", + "description": "Enhances visual display of Slack forwarded and quoted messages with distinctive formatting", + "author": { + "name": "Anthropic", + "email": "support@anthropic.com" + }, + "license": "MIT", + "keywords": [ + "slack", + "formatting", + "quotes", + "messages", + "visual" + ], + "hooks": "./hooks/hooks.json" +} diff --git a/plugins/slack-quote-formatter/README.md b/plugins/slack-quote-formatter/README.md new file mode 100644 index 00000000..c07c0680 --- /dev/null +++ b/plugins/slack-quote-formatter/README.md @@ -0,0 +1,87 @@ +# Slack Quote Formatter + +Enhances the visual display of Slack forwarded and quoted messages with distinctive Unicode box formatting. + +## Overview + +When Claude Code receives messages from Slack that include forwarded messages, they typically appear as plain text: + +``` +[Forwarded Message] +From: Felix Klock +Message: I assume it would be a matter of reviewing the entries... +``` + +This plugin transforms these plain blocks into visually distinctive formatted quotes: + +``` +╔════════════════════════════════════════════════════════════════════╗ +║ FORWARDED MESSAGE ║ +╠════════════════════════════════════════════════════════════════════╣ +║ From: Felix Klock ║ +╟────────────────────────────────────────────────────────────────────╢ +║ I assume it would be a matter of reviewing the entries... ║ +╚════════════════════════════════════════════════════════════════════╝ +``` + +## Features + +- Unicode box drawing characters for clear visual boundaries +- Distinct header section highlighting "FORWARDED MESSAGE" +- Sender information prominently displayed +- Message content indented for quote-like appearance +- Automatic text wrapping for long messages +- Handles multiple forwarded messages in a single prompt + +## Installation + +This plugin is included in the Claude Code plugins repository. To enable it: + +1. Add the plugin to your project's `.claude/settings.json`: + ```json + { + "plugins": [ + "path/to/plugins/slack-quote-formatter" + ] + } + ``` + +2. Or install via the Claude Code plugin command: + ``` + /plugin add slack-quote-formatter + ``` + +## How It Works + +The plugin registers a `UserPromptSubmit` hook that: + +1. Intercepts incoming prompts +2. Detects `[Forwarded Message]` blocks in the Slack context +3. Transforms them into formatted boxes with visual distinction +4. Passes the transformed prompt to Claude + +## Configuration + +No configuration required. The plugin automatically activates when Slack forwarded messages are detected. + +## Plugin Structure + +``` +slack-quote-formatter/ +├── .claude-plugin/ +│ └── plugin.json # Plugin metadata +├── hooks/ +│ ├── hooks.json # Hook configuration +│ └── format_slack_quotes.py # Transformation logic +└── README.md +``` + +## Why Visual Distinction Matters + +When working with Slack context in Claude Code, quoted messages can easily blend into the surrounding text, making it harder to: + +- Identify what content is quoted vs. original +- Understand the source of different pieces of information +- Parse complex threads with multiple forwarded messages + +This plugin makes quoted content immediately recognizable, improving readability and reducing confusion. diff --git a/plugins/slack-quote-formatter/hooks/format_slack_quotes.py b/plugins/slack-quote-formatter/hooks/format_slack_quotes.py new file mode 100755 index 00000000..9f5fb496 --- /dev/null +++ b/plugins/slack-quote-formatter/hooks/format_slack_quotes.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +Slack Quote Formatter - UserPromptSubmit Hook + +Transforms plain Slack forwarded/quoted messages into visually distinctive +formatted blocks with Unicode box characters and better layout. + +Before: + [Forwarded Message] + From: John Doe + Message: Here is some text... + +After: + ╔═══════════════════════════════════════════════════════════╗ + ║ FORWARDED MESSAGE ║ + ╠═══════════════════════════════════════════════════════════╣ + ║ From: John Doe ║ + ╟───────────────────────────────────────────────────────────╢ + ║ Here is some text... ║ + ╚═══════════════════════════════════════════════════════════╝ +""" + +import json +import re +import sys + + +def format_forwarded_message(from_line: str, message_content: str) -> str: + """Format a single forwarded message with visual box styling.""" + + # Box drawing characters + TOP_LEFT = "╔" + TOP_RIGHT = "╗" + BOTTOM_LEFT = "╚" + BOTTOM_RIGHT = "╝" + HORIZONTAL = "═" + VERTICAL = "║" + LEFT_T = "╠" + RIGHT_T = "╣" + LEFT_LIGHT = "╟" + RIGHT_LIGHT = "╢" + LIGHT_HORIZONTAL = "─" + + # Configuration + BOX_WIDTH = 70 + INNER_WIDTH = BOX_WIDTH - 4 # Account for "║ " and " ║" + + def pad_line(text: str, width: int = INNER_WIDTH) -> str: + """Pad a line to fill the box width.""" + if len(text) > width: + return text[:width] + return text + " " * (width - len(text)) + + def wrap_text(text: str, width: int) -> list: + """Wrap text to fit within the specified width.""" + words = text.split() + lines = [] + current_line = [] + current_length = 0 + + for word in words: + word_len = len(word) + if current_length + word_len + (1 if current_line else 0) <= width: + current_line.append(word) + current_length += word_len + (1 if len(current_line) > 1 else 0) + else: + if current_line: + lines.append(" ".join(current_line)) + current_line = [word] + current_length = word_len + + if current_line: + lines.append(" ".join(current_line)) + + return lines if lines else [""] + + lines = [] + + # Top border with header + lines.append(f"{TOP_LEFT}{HORIZONTAL * (BOX_WIDTH - 2)}{TOP_RIGHT}") + + # Header line + header = "FORWARDED MESSAGE" + lines.append(f"{VERTICAL} {pad_line(header)}{VERTICAL}") + + # Separator after header + lines.append(f"{LEFT_T}{HORIZONTAL * (BOX_WIDTH - 2)}{RIGHT_T}") + + # From line + from_display = from_line.strip() + lines.append(f"{VERTICAL} {pad_line(from_display)}{VERTICAL}") + + # Light separator before message + lines.append(f"{LEFT_LIGHT}{LIGHT_HORIZONTAL * (BOX_WIDTH - 2)}{RIGHT_LIGHT}") + + # Message content (indented and wrapped) + message_lines = message_content.strip().split("\n") + for msg_line in message_lines: + # Handle each line, wrapping if necessary + wrapped = wrap_text(msg_line, INNER_WIDTH - 2) # Extra indent + for wrapped_line in wrapped: + indented = " " + wrapped_line # Add indent for quote appearance + lines.append(f"{VERTICAL} {pad_line(indented)}{VERTICAL}") + + # Bottom border + lines.append(f"{BOTTOM_LEFT}{HORIZONTAL * (BOX_WIDTH - 2)}{BOTTOM_RIGHT}") + + return "\n".join(lines) + + +def transform_slack_context(text: str) -> str: + """ + Find and transform [Forwarded Message] blocks in the text. + + Matches patterns like: + [Forwarded Message] + From: Name Here + Message: Content here that may span + multiple lines until we hit the next section + """ + + # Pattern to match forwarded message blocks + # This handles the common format from Slack context + pattern = r'\[Forwarded Message\]\s*\n\s*From:\s*([^\n]+)\s*\nMessage:\s*(.+?)(?=\n\s*\[|\n\[pnkfelix\]|\n<|\Z)' + + def replace_match(match): + from_name = match.group(1).strip() + message = match.group(2).strip() + + formatted = format_forwarded_message(f"From: {from_name}", message) + return f"\n{formatted}\n" + + # Apply transformation + result = re.sub(pattern, replace_match, text, flags=re.DOTALL) + + return result + + +def main(): + """Main entry point for the UserPromptSubmit hook.""" + try: + # Read input from stdin + input_data = json.load(sys.stdin) + + # Get the user prompt if available + user_prompt = input_data.get("user_prompt", "") + + if not user_prompt: + # No prompt to transform, just pass through + print(json.dumps({})) + sys.exit(0) + + # Check if there are forwarded messages to format + if "[Forwarded Message]" not in user_prompt: + # Nothing to transform + print(json.dumps({})) + sys.exit(0) + + # Transform the prompt + transformed_prompt = transform_slack_context(user_prompt) + + # If we made changes, return the transformed prompt + if transformed_prompt != user_prompt: + result = { + "transformedPrompt": transformed_prompt + } + print(json.dumps(result)) + else: + print(json.dumps({})) + + except json.JSONDecodeError: + # No valid JSON input, pass through + print(json.dumps({})) + except Exception as e: + # Log error but don't block + error_output = { + "systemMessage": f"[slack-quote-formatter] Warning: {str(e)}" + } + print(json.dumps(error_output)) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/plugins/slack-quote-formatter/hooks/hooks.json b/plugins/slack-quote-formatter/hooks/hooks.json new file mode 100644 index 00000000..342fa36c --- /dev/null +++ b/plugins/slack-quote-formatter/hooks/hooks.json @@ -0,0 +1,14 @@ +{ + "UserPromptSubmit": [ + { + "matcher": ".*", + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/format_slack_quotes.py", + "timeout": 5000 + } + ] + } + ] +}