mirror of
https://github.com/anthropics/claude-code.git
synced 2026-02-19 04:27:33 -08:00
feat: Add slack-quote-formatter plugin for better quoted message display
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
This commit is contained in:
@@ -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)<br>**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<br>**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
|
||||
|
||||
|
||||
18
plugins/slack-quote-formatter/.claude-plugin/plugin.json
Normal file
18
plugins/slack-quote-formatter/.claude-plugin/plugin.json
Normal file
@@ -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"
|
||||
}
|
||||
87
plugins/slack-quote-formatter/README.md
Normal file
87
plugins/slack-quote-formatter/README.md
Normal file
@@ -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.
|
||||
185
plugins/slack-quote-formatter/hooks/format_slack_quotes.py
Executable file
185
plugins/slack-quote-formatter/hooks/format_slack_quotes.py
Executable file
@@ -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()
|
||||
14
plugins/slack-quote-formatter/hooks/hooks.json
Normal file
14
plugins/slack-quote-formatter/hooks/hooks.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/format_slack_quotes.py",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user