surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
Adds a new plugin that transforms plain Slack forwarded message blocks into visually distinctive formatted boxes using Unicode box-drawing characters. Before: [Forwarded Message] From: Felix Klock Message: Here is some text... After: ╔════════════════════════════════════════════════════════════════════╗ ║ FORWARDED MESSAGE ║ ╠════════════════════════════════════════════════════════════════════╣ ║ From: Felix Klock ║ ╟────────────────────────────────────────────────────────────────────╢ ║ Here is some text... ║ ╚════════════════════════════════════════════════════════════════════╝ The plugin uses a UserPromptSubmit hook to intercept prompts containing [Forwarded Message] blocks and reformats them for better visual distinction. https://claude.ai/code/session_01YNRC6p2gAMSQZPX2NGK5F1
| @@ -25,6 +25,7 @@ | ||
| 25 | 25 | | [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)<br>**Agents:** `comment-analyzer`, `pr-test-analyzer`, `silent-failure-hunter`, `type-design-analyzer`, `code-reviewer`, `code-simplifier` | |
| 26 | 26 | | [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<br>**Hook:** Stop - Intercepts exit attempts to continue iteration | |
| 27 | 27 | | [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 | |
| 28 | +| [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 | | |
| 28 | 29 | |
| 29 | 30 | ## Installation |
| 30 | 31 |
| @@ -1,0 +1,18 @@ | ||
| 1 | +{ | |
| 2 | + "name": "slack-quote-formatter", | |
| 3 | + "version": "1.0.0", | |
| 4 | + "description": "Enhances visual display of Slack forwarded and quoted messages with distinctive formatting", | |
| 5 | + "author": { | |
| 6 | + "name": "Anthropic", | |
| 7 | + "email": "support@anthropic.com" | |
| 8 | + }, | |
| 9 | + "license": "MIT", | |
| 10 | + "keywords": [ | |
| 11 | + "slack", | |
| 12 | + "formatting", | |
| 13 | + "quotes", | |
| 14 | + "messages", | |
| 15 | + "visual" | |
| 16 | + ], | |
| 17 | + "hooks": "./hooks/hooks.json" | |
| 18 | +} |
| @@ -1,0 +1,87 @@ | ||
| 1 | +# Slack Quote Formatter | |
| 2 | + | |
| 3 | +Enhances the visual display of Slack forwarded and quoted messages with distinctive Unicode box formatting. | |
| 4 | + | |
| 5 | +## Overview | |
| 6 | + | |
| 7 | +When Claude Code receives messages from Slack that include forwarded messages, they typically appear as plain text: | |
| 8 | + | |
| 9 | +``` | |
| 10 | +[Forwarded Message] | |
| 11 | +From: Felix Klock | |
| 12 | +Message: I assume it would be a matter of reviewing the entries... | |
| 13 | +``` | |
| 14 | + | |
| 15 | +This plugin transforms these plain blocks into visually distinctive formatted quotes: | |
| 16 | + | |
| 17 | +``` | |
| 18 | +╔════════════════════════════════════════════════════════════════════╗ | |
| 19 | +║ FORWARDED MESSAGE ║ | |
| 20 | +╠════════════════════════════════════════════════════════════════════╣ | |
| 21 | +║ From: Felix Klock ║ | |
| 22 | +╟────────────────────────────────────────────────────────────────────╢ | |
| 23 | +║ I assume it would be a matter of reviewing the entries... ║ | |
| 24 | +╚════════════════════════════════════════════════════════════════════╝ | |
| 25 | +``` | |
| 26 | + | |
| 27 | +## Features | |
| 28 | + | |
| 29 | +- Unicode box drawing characters for clear visual boundaries | |
| 30 | +- Distinct header section highlighting "FORWARDED MESSAGE" | |
| 31 | +- Sender information prominently displayed | |
| 32 | +- Message content indented for quote-like appearance | |
| 33 | +- Automatic text wrapping for long messages | |
| 34 | +- Handles multiple forwarded messages in a single prompt | |
| 35 | + | |
| 36 | +## Installation | |
| 37 | + | |
| 38 | +This plugin is included in the Claude Code plugins repository. To enable it: | |
| 39 | + | |
| 40 | +1. Add the plugin to your project's `.claude/settings.json`: | |
| 41 | + ```json | |
| 42 | + { | |
| 43 | + "plugins": [ | |
| 44 | + "path/to/plugins/slack-quote-formatter" | |
| 45 | + ] | |
| 46 | + } | |
| 47 | + ``` | |
| 48 | + | |
| 49 | +2. Or install via the Claude Code plugin command: | |
| 50 | + ``` | |
| 51 | + /plugin add slack-quote-formatter | |
| 52 | + ``` | |
| 53 | + | |
| 54 | +## How It Works | |
| 55 | + | |
| 56 | +The plugin registers a `UserPromptSubmit` hook that: | |
| 57 | + | |
| 58 | +1. Intercepts incoming prompts | |
| 59 | +2. Detects `[Forwarded Message]` blocks in the Slack context | |
| 60 | +3. Transforms them into formatted boxes with visual distinction | |
| 61 | +4. Passes the transformed prompt to Claude | |
| 62 | + | |
| 63 | +## Configuration | |
| 64 | + | |
| 65 | +No configuration required. The plugin automatically activates when Slack forwarded messages are detected. | |
| 66 | + | |
| 67 | +## Plugin Structure | |
| 68 | + | |
| 69 | +``` | |
| 70 | +slack-quote-formatter/ | |
| 71 | +├── .claude-plugin/ | |
| 72 | +│ └── plugin.json # Plugin metadata | |
| 73 | +├── hooks/ | |
| 74 | +│ ├── hooks.json # Hook configuration | |
| 75 | +│ └── format_slack_quotes.py # Transformation logic | |
| 76 | +└── README.md | |
| 77 | +``` | |
| 78 | + | |
| 79 | +## Why Visual Distinction Matters | |
| 80 | + | |
| 81 | +When working with Slack context in Claude Code, quoted messages can easily blend into the surrounding text, making it harder to: | |
| 82 | + | |
| 83 | +- Identify what content is quoted vs. original | |
| 84 | +- Understand the source of different pieces of information | |
| 85 | +- Parse complex threads with multiple forwarded messages | |
| 86 | + | |
| 87 | +This plugin makes quoted content immediately recognizable, improving readability and reducing confusion. |
| @@ -1,0 +1,185 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +""" | |
| 3 | +Slack Quote Formatter - UserPromptSubmit Hook | |
| 4 | + | |
| 5 | +Transforms plain Slack forwarded/quoted messages into visually distinctive | |
| 6 | +formatted blocks with Unicode box characters and better layout. | |
| 7 | + | |
| 8 | +Before: | |
| 9 | + [Forwarded Message] | |
| 10 | + From: John Doe | |
| 11 | + Message: Here is some text... | |
| 12 | + | |
| 13 | +After: | |
| 14 | + ╔═══════════════════════════════════════════════════════════╗ | |
| 15 | + ║ FORWARDED MESSAGE ║ | |
| 16 | + ╠═══════════════════════════════════════════════════════════╣ | |
| 17 | + ║ From: John Doe ║ | |
| 18 | + ╟───────────────────────────────────────────────────────────╢ | |
| 19 | + ║ Here is some text... ║ | |
| 20 | + ╚═══════════════════════════════════════════════════════════╝ | |
| 21 | +""" | |
| 22 | + | |
| 23 | +import json | |
| 24 | +import re | |
| 25 | +import sys | |
| 26 | + | |
| 27 | + | |
| 28 | +def format_forwarded_message(from_line: str, message_content: str) -> str: | |
| 29 | + """Format a single forwarded message with visual box styling.""" | |
| 30 | + | |
| 31 | + # Box drawing characters | |
| 32 | + TOP_LEFT = "╔" | |
| 33 | + TOP_RIGHT = "╗" | |
| 34 | + BOTTOM_LEFT = "╚" | |
| 35 | + BOTTOM_RIGHT = "╝" | |
| 36 | + HORIZONTAL = "═" | |
| 37 | + VERTICAL = "║" | |
| 38 | + LEFT_T = "╠" | |
| 39 | + RIGHT_T = "╣" | |
| 40 | + LEFT_LIGHT = "╟" | |
| 41 | + RIGHT_LIGHT = "╢" | |
| 42 | + LIGHT_HORIZONTAL = "─" | |
| 43 | + | |
| 44 | + # Configuration | |
| 45 | + BOX_WIDTH = 70 | |
| 46 | + INNER_WIDTH = BOX_WIDTH - 4 # Account for "║ " and " ║" | |
| 47 | + | |
| 48 | + def pad_line(text: str, width: int = INNER_WIDTH) -> str: | |
| 49 | + """Pad a line to fill the box width.""" | |
| 50 | + if len(text) > width: | |
| 51 | + return text[:width] | |
| 52 | + return text + " " * (width - len(text)) | |
| 53 | + | |
| 54 | + def wrap_text(text: str, width: int) -> list: | |
| 55 | + """Wrap text to fit within the specified width.""" | |
| 56 | + words = text.split() | |
| 57 | + lines = [] | |
| 58 | + current_line = [] | |
| 59 | + current_length = 0 | |
| 60 | + | |
| 61 | + for word in words: | |
| 62 | + word_len = len(word) | |
| 63 | + if current_length + word_len + (1 if current_line else 0) <= width: | |
| 64 | + current_line.append(word) | |
| 65 | + current_length += word_len + (1 if len(current_line) > 1 else 0) | |
| 66 | + else: | |
| 67 | + if current_line: | |
| 68 | + lines.append(" ".join(current_line)) | |
| 69 | + current_line = [word] | |
| 70 | + current_length = word_len | |
| 71 | + | |
| 72 | + if current_line: | |
| 73 | + lines.append(" ".join(current_line)) | |
| 74 | + | |
| 75 | + return lines if lines else [""] | |
| 76 | + | |
| 77 | + lines = [] | |
| 78 | + | |
| 79 | + # Top border with header | |
| 80 | + lines.append(f"{TOP_LEFT}{HORIZONTAL * (BOX_WIDTH - 2)}{TOP_RIGHT}") | |
| 81 | + | |
| 82 | + # Header line | |
| 83 | + header = "FORWARDED MESSAGE" | |
| 84 | + lines.append(f"{VERTICAL} {pad_line(header)}{VERTICAL}") | |
| 85 | + | |
| 86 | + # Separator after header | |
| 87 | + lines.append(f"{LEFT_T}{HORIZONTAL * (BOX_WIDTH - 2)}{RIGHT_T}") | |
| 88 | + | |
| 89 | + # From line | |
| 90 | + from_display = from_line.strip() | |
| 91 | + lines.append(f"{VERTICAL} {pad_line(from_display)}{VERTICAL}") | |
| 92 | + | |
| 93 | + # Light separator before message | |
| 94 | + lines.append(f"{LEFT_LIGHT}{LIGHT_HORIZONTAL * (BOX_WIDTH - 2)}{RIGHT_LIGHT}") | |
| 95 | + | |
| 96 | + # Message content (indented and wrapped) | |
| 97 | + message_lines = message_content.strip().split("\n") | |
| 98 | + for msg_line in message_lines: | |
| 99 | + # Handle each line, wrapping if necessary | |
| 100 | + wrapped = wrap_text(msg_line, INNER_WIDTH - 2) # Extra indent | |
| 101 | + for wrapped_line in wrapped: | |
| 102 | + indented = " " + wrapped_line # Add indent for quote appearance | |
| 103 | + lines.append(f"{VERTICAL} {pad_line(indented)}{VERTICAL}") | |
| 104 | + | |
| 105 | + # Bottom border | |
| 106 | + lines.append(f"{BOTTOM_LEFT}{HORIZONTAL * (BOX_WIDTH - 2)}{BOTTOM_RIGHT}") | |
| 107 | + | |
| 108 | + return "\n".join(lines) | |
| 109 | + | |
| 110 | + | |
| 111 | +def transform_slack_context(text: str) -> str: | |
| 112 | + """ | |
| 113 | + Find and transform [Forwarded Message] blocks in the text. | |
| 114 | + | |
| 115 | + Matches patterns like: | |
| 116 | + [Forwarded Message] | |
| 117 | + From: Name Here | |
| 118 | + Message: Content here that may span | |
| 119 | + multiple lines until we hit the next section | |
| 120 | + """ | |
| 121 | + | |
| 122 | + # Pattern to match forwarded message blocks | |
| 123 | + # This handles the common format from Slack context | |
| 124 | + pattern = r'\[Forwarded Message\]\s*\n\s*From:\s*([^\n]+)\s*\nMessage:\s*(.+?)(?=\n\s*\[|\n\[pnkfelix\]|\n<|\Z)' | |
| 125 | + | |
| 126 | + def replace_match(match): | |
| 127 | + from_name = match.group(1).strip() | |
| 128 | + message = match.group(2).strip() | |
| 129 | + | |
| 130 | + formatted = format_forwarded_message(f"From: {from_name}", message) | |
| 131 | + return f"\n{formatted}\n" | |
| 132 | + | |
| 133 | + # Apply transformation | |
| 134 | + result = re.sub(pattern, replace_match, text, flags=re.DOTALL) | |
| 135 | + | |
| 136 | + return result | |
| 137 | + | |
| 138 | + | |
| 139 | +def main(): | |
| 140 | + """Main entry point for the UserPromptSubmit hook.""" | |
| 141 | + try: | |
| 142 | + # Read input from stdin | |
| 143 | + input_data = json.load(sys.stdin) | |
| 144 | + | |
| 145 | + # Get the user prompt if available | |
| 146 | + user_prompt = input_data.get("user_prompt", "") | |
| 147 | + | |
| 148 | + if not user_prompt: | |
| 149 | + # No prompt to transform, just pass through | |
| 150 | + print(json.dumps({})) | |
| 151 | + sys.exit(0) | |
| 152 | + | |
| 153 | + # Check if there are forwarded messages to format | |
| 154 | + if "[Forwarded Message]" not in user_prompt: | |
| 155 | + # Nothing to transform | |
| 156 | + print(json.dumps({})) | |
| 157 | + sys.exit(0) | |
| 158 | + | |
| 159 | + # Transform the prompt | |
| 160 | + transformed_prompt = transform_slack_context(user_prompt) | |
| 161 | + | |
| 162 | + # If we made changes, return the transformed prompt | |
| 163 | + if transformed_prompt != user_prompt: | |
| 164 | + result = { | |
| 165 | + "transformedPrompt": transformed_prompt | |
| 166 | + } | |
| 167 | + print(json.dumps(result)) | |
| 168 | + else: | |
| 169 | + print(json.dumps({})) | |
| 170 | + | |
| 171 | + except json.JSONDecodeError: | |
| 172 | + # No valid JSON input, pass through | |
| 173 | + print(json.dumps({})) | |
| 174 | + except Exception as e: | |
| 175 | + # Log error but don't block | |
| 176 | + error_output = { | |
| 177 | + "systemMessage": f"[slack-quote-formatter] Warning: {str(e)}" | |
| 178 | + } | |
| 179 | + print(json.dumps(error_output)) | |
| 180 | + | |
| 181 | + sys.exit(0) | |
| 182 | + | |
| 183 | + | |
| 184 | +if __name__ == "__main__": | |
| 185 | + main() |
| @@ -1,0 +1,14 @@ | ||
| 1 | +{ | |
| 2 | + "UserPromptSubmit": [ | |
| 3 | + { | |
| 4 | + "matcher": ".*", | |
| 5 | + "hooks": [ | |
| 6 | + { | |
| 7 | + "type": "command", | |
| 8 | + "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/format_slack_quotes.py", | |
| 9 | + "timeout": 5000 | |
| 10 | + } | |
| 11 | + ] | |
| 12 | + } | |
| 13 | + ] | |
| 14 | +} |