Compare commits

...

8 Commits

Author SHA1 Message Date
Claude
69585d4ffa 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
2026-02-10 19:46:10 +00:00
GitHub Actions
19bb071fe0 chore: Update CHANGELOG.md 2026-02-10 00:52:42 +00:00
GitHub Actions
85f2807991 chore: Update CHANGELOG.md 2026-02-07 19:09:18 +00:00
GitHub Actions
e7f36bcdf0 chore: Update CHANGELOG.md 2026-02-07 18:01:26 +00:00
GitHub Actions
2bc62d1456 chore: Update CHANGELOG.md 2026-02-06 14:26:00 +00:00
GitHub Actions
ef1e0ac098 chore: Update CHANGELOG.md 2026-02-06 01:46:32 +00:00
GitHub Actions
d7e3cfb31c chore: Update CHANGELOG.md 2026-02-05 17:47:04 +00:00
GitHub Actions
bd78b216ed chore: Update CHANGELOG.md 2026-02-04 00:43:28 +00:00
6 changed files with 376 additions and 0 deletions

View File

@@ -1,5 +1,76 @@
# Changelog
## 2.1.38
- Fixed VS Code terminal scroll-to-top regression introduced in 2.1.37
- Fixed Tab key queueing slash commands instead of autocompleting
- Fixed bash permission matching for commands using environment variable wrappers
- Fixed text between tool uses disappearing when not using streaming
- Fixed duplicate sessions when resuming in VS Code extension
- Improved heredoc delimiter parsing to prevent command smuggling
- Blocked writes to `.claude/skills` directory in sandbox mode
## 2.1.37
- Fixed an issue where /fast was not immediately available after enabling /extra-usage
## 2.1.36
- Fast mode is now available for Opus 4.6. Learn more at https://code.claude.com/docs/en/fast-mode
## 2.1.34
- Fixed a crash when agent teams setting changed between renders
- Fixed a bug where commands excluded from sandboxing (via `sandbox.excludedCommands` or `dangerouslyDisableSandbox`) could bypass the Bash ask permission rule when `autoAllowBashIfSandboxed` was enabled
## 2.1.33
- Fixed agent teammate sessions in tmux to send and receive messages
- Fixed warnings about agent teams not being available on your current plan
- Added `TeammateIdle` and `TaskCompleted` hook events for multi-agent workflows
- Added support for restricting which sub-agents can be spawned via `Task(agent_type)` syntax in agent "tools" frontmatter
- Added `memory` frontmatter field support for agents, enabling persistent memory with `user`, `project`, or `local` scope
- Added plugin name to skill descriptions and `/skills` menu for better discoverability
- Fixed an issue where submitting a new message while the model was in extended thinking would interrupt the thinking phase
- Fixed an API error that could occur when aborting mid-stream, where whitespace text combined with a thinking block would bypass normalization and produce an invalid request
- Fixed API proxy compatibility issue where 404 errors on streaming endpoints no longer triggered non-streaming fallback
- Fixed an issue where proxy settings configured via `settings.json` environment variables were not applied to WebFetch and other HTTP requests on the Node.js build
- Fixed `/resume` session picker showing raw XML markup instead of clean titles for sessions started with slash commands
- Improved error messages for API connection failures — now shows specific cause (e.g., ECONNREFUSED, SSL errors) instead of generic "Connection error"
- Errors from invalid managed settings are now surfaced
- VSCode: Added support for remote sessions, allowing OAuth users to browse and resume sessions from claude.ai
- VSCode: Added git branch and message count to the session picker, with support for searching by branch name
- VSCode: Fixed scroll-to-bottom under-scrolling on initial session load and session switch
## 2.1.32
- Claude Opus 4.6 is now available!
- Added research preview agent teams feature for multi-agent collaboration (token-intensive feature, requires setting CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)
- Claude now automatically records and recalls memories as it works
- Added "Summarize from here" to the message selector, allowing partial conversation summarization.
- Skills defined in `.claude/skills/` within additional directories (`--add-dir`) are now loaded automatically.
- Fixed `@` file completion showing incorrect relative paths when running from a subdirectory
- Updated --resume to re-use --agent value specified in previous conversation by default.
- Fixed: Bash tool no longer throws "Bad substitution" errors when heredocs contain JavaScript template literals like `${index + 1}`, which previously interrupted tool execution
- Skill character budget now scales with context window (2% of context), so users with larger context windows can see more skill descriptions without truncation
- Fixed Thai/Lao spacing vowels (สระ า, ำ) not rendering correctly in the input field
- VSCode: Fixed slash commands incorrectly being executed when pressing Enter with preceding text in the input field
- VSCode: Added spinner when loading past conversations list
## 2.1.31
- Added session resume hint on exit, showing how to continue your conversation later
- Added support for full-width (zenkaku) space input from Japanese IME in checkbox selection
- Fixed PDF too large errors permanently locking up sessions, requiring users to start a new conversation
- Fixed bash commands incorrectly reporting failure with "Read-only file system" errors when sandbox mode was enabled
- Fixed a crash that made sessions unusable after entering plan mode when project config in `~/.claude.json` was missing default fields
- Fixed `temperatureOverride` being silently ignored in the streaming API path, causing all streaming requests to use the default temperature (1) regardless of the configured override
- Fixed LSP shutdown/exit compatibility with strict language servers that reject null params
- Improved system prompts to more clearly guide the model toward using dedicated tools (Read, Edit, Glob, Grep) instead of bash equivalents (`cat`, `sed`, `grep`, `find`), reducing unnecessary bash command usage
- Improved PDF and request size error messages to show actual limits (100 pages, 20MB)
- Reduced layout jitter in the terminal when the spinner appears and disappears during streaming
- Removed misleading Anthropic API pricing from model selector for third-party provider (Bedrock, Vertex, Foundry) users
## 2.1.30
- Added `pages` parameter to the Read tool for PDFs, allowing specific page ranges to be read (e.g., `pages: "1-5"`). Large PDFs (>10 pages) now return a lightweight reference when `@` mentioned instead of being inlined into context.

View File

@@ -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

View 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"
}

View 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.

View 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()

View File

@@ -0,0 +1,14 @@
{
"UserPromptSubmit": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/format_slack_quotes.py",
"timeout": 5000
}
]
}
]
}