Skip to content

Commit 476af6d

Browse files
committed
chore: Loosening claudesay reply limits, update README with multi-turn tag handling documentation and reformat design spec for consistency.
1 parent c06b841 commit 476af6d

4 files changed

Lines changed: 117 additions & 49 deletions

File tree

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claudesay",
3-
"version": "2.1.0",
3+
"version": "2.2.0",
44
"description": "Renders Claude replies as ASCII character speech bubbles",
55
"author": {
66
"name": "TimJJTing"

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,39 @@ Buddy was a Tamagotchi-style virtual pet — a companion you hatch and tend to.
328328
- The raw `<claudesay>` tag appears in the terminal scrollback before the bubble renders (Claude streams it before the Stop hook fires).
329329
- Figures do not render in CI, `--print` mode, or non-interactive SSH sessions.
330330
- Per-turn reminder adds ~20 tokens per turn.
331+
- **Stop-hook bubble reflects only the final assistant text block of a turn.** The Stop hook reads `.last_assistant_message` and grabs the last `<claudesay>` tag in it (`hooks/scripts/stop.sh`, `tail -1`). If Claude emits multiple conversational text messages in one turn (text → tool → text → tool → text), only the tag in the last block renders.
331332

332-
## Compatibility
333+
**Why we stop here:** the injected protocol (`hooks/scripts/session-start.sh`) instructs Claude to emit a single tag *at the very end*, with room for ~120 chars / 1-2 sentences in first person covering the whole turn. One well-written bubble usually describes the turn in full, making multi-bubble rendering redundant for the intended use case.
333334

334-
Stacks naturally with the [caveman plugin](https://github.com/JuliusBrussee/caveman). Caveman compresses the main response; claudesay bubbles the separately-written tag. No conflict. `caveman-lite` recommended as a complementary install.
335+
**If you want to render a bubble per mid-turn text block**, parse `transcript_path` (available on Stop hook input JSON). The transcript is JSONL, one message per line. Assistant entries look like:
336+
337+
```json
338+
{"type":"assistant","message":{"role":"assistant","content":[
339+
{"type":"thinking", "...": "..."},
340+
{"type":"text","text":"<claudesay mood=\"...\">...</claudesay>"},
341+
{"type":"tool_use", "...": "..."}
342+
]}}
343+
```
344+
345+
A turn spans **multiple** assistant entries (one per LLM call, split by tool boundaries). Each has its own `.message.content` array of blocks — `thinking`, `text`, `tool_use`. Steps:
346+
347+
1. Walk JSONL backwards from EOF.
348+
2. Stop at the most recent `type:"user"` entry whose content is not a `tool_result` — that marks the start of the current turn.
349+
3. For each assistant entry after that boundary, extract `.message.content[] | select(.type=="text") | .text`.
350+
4. Run the existing `<claudesay>` regex over each block; render matches sequentially, or combine into one multi-line bubble.
351+
352+
Sketch (`jq`):
353+
354+
```bash
355+
jq -r '
356+
select(.type=="assistant")
357+
| .message.content[]?
358+
| select(.type=="text")
359+
| .text
360+
' "$TRANSCRIPT_PATH"
361+
```
362+
363+
Add guards for missing/malformed/large transcripts. The protocol would also need updating to permit (and describe) mid-turn tags. Not shipped — contributions welcome.
335364

336365
## Development
337366

docs/superpowers/specs/2026-04-17-claude-say-design.md

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# claudesay Design Spec
2+
23
_2026-04-17_
34

45
## Overview
@@ -66,10 +67,46 @@ Plugin hooks.json uses a flat format keyed directly by event name — no outer w
6667

6768
```json
6869
{
69-
"SessionStart": [{ "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-start.sh" }] }],
70-
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/prompt-submit.sh" }] }],
71-
"PreToolUse": [{ "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-tool-use.sh" }] }],
72-
"Stop": [{ "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/stop.sh" }] }]
70+
"SessionStart": [
71+
{
72+
"hooks": [
73+
{
74+
"type": "command",
75+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-start.sh"
76+
}
77+
]
78+
}
79+
],
80+
"UserPromptSubmit": [
81+
{
82+
"hooks": [
83+
{
84+
"type": "command",
85+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/prompt-submit.sh"
86+
}
87+
]
88+
}
89+
],
90+
"PreToolUse": [
91+
{
92+
"hooks": [
93+
{
94+
"type": "command",
95+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-tool-use.sh"
96+
}
97+
]
98+
}
99+
],
100+
"Stop": [
101+
{
102+
"hooks": [
103+
{
104+
"type": "command",
105+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/stop.sh"
106+
}
107+
]
108+
}
109+
]
73110
}
74111
```
75112

@@ -130,6 +167,7 @@ echo '{"systemMessage": "<claudesay-protocol>\nWhen giving a conversational repl
130167
```
131168

132169
`prompt-submit.sh` outputs a compact `systemMessage` reminder with every user turn so Claude never drifts:
170+
133171
```bash
134172
echo '{"systemMessage": "[claudesay: end chatty reply with <claudesay mood=\"X\">summary</claudesay>]"}'
135173
```
@@ -177,45 +215,46 @@ fi
177215

178216
### Mood Expressions
179217

180-
| Mood | Face | When used |
181-
|-----------|------------|----------------------------------|
182-
| happy-a | `( ^ᵕ^ )` | Normal success (variant A) |
183-
| happy-b | `( ᵕ‿ᵕ )` | Normal success (variant B) |
184-
| excited | `( ^▽^ )` | Big win |
185-
| excited-b | `( ≧▽≦ )` | Very excited |
186-
| thinking | `( ._. )` | In progress / uncertain |
187-
| focused | `( -.- )` | Running a tool |
188-
| upset | `( >_< )` | Warning or partial failure |
189-
| error | `( x_x )` | Actual error |
218+
| Mood | Face | When used |
219+
| --------- | ---------- | -------------------------- |
220+
| happy-a | `( ^ᵕ^ )` | Normal success (variant A) |
221+
| happy-b | `( ᵕ‿ᵕ )` | Normal success (variant B) |
222+
| excited | `( ^▽^ )` | Big win |
223+
| excited-b | `( ≧▽≦ )` | Very excited |
224+
| thinking | `( ._. )` | In progress / uncertain |
225+
| focused | `( -.- )` | Running a tool |
226+
| upset | `( >_< )` | Warning or partial failure |
227+
| error | `( x_x )` | Actual error |
190228

191229
Positive moods rotate between variants on each render to avoid repetition.
192230

193231
### Tool Props (PreToolUse)
194232

195233
Each tool entry has a `side` field (`left`/`right`) controlling which hand holds the prop. "Reaching out" tools (searching, reading, fetching) use the left hand; "doing" tools (editing, running, spawning) use the right.
196234

197-
| Tool(s) | Prop | Mood | Side |
198-
|----------------------|---------------|----------|-------|
199-
| Edit, Write | 🔧 wrench | focused | left |
200-
| Bash | 🪄 magic wand | focused | right |
201-
| Grep, Glob | 🔍 magnifier | thinking | left |
202-
| Read | 📖 book | thinking | left |
203-
| WebFetch, WebSearch | 📡 antenna | thinking | right |
204-
| Agent (spawn) | 🤖 buddy | excited | right |
205-
| TodoWrite | 📋 clipboard | focused | left |
206-
| default | (none) | focused ||
235+
| Tool(s) | Prop | Mood | Side |
236+
| ------------------- | ------------- | -------- | ----- |
237+
| Edit, Write | 🔧 wrench | focused | left |
238+
| Bash | 🪄 magic wand | focused | right |
239+
| Grep, Glob | 🔍 magnifier | thinking | left |
240+
| Read | 📖 book | thinking | left |
241+
| WebFetch, WebSearch | 📡 antenna | thinking | right |
242+
| Agent (spawn) | 🤖 buddy | excited | right |
243+
| TodoWrite | 📋 clipboard | focused | left |
244+
| default | (none) | focused ||
207245

208246
### Rendered Output Examples
209247

210248
**Chat reply (Stop hook):**
249+
211250
```
212251
...Claude's full response above...
213252
<claudesay mood="excited">All 3 tests pass now!</claudesay>
214253
215254
╭────────────────────────────────╮
216255
│ All 3 tests pass now! │
217256
╰────╮───────────────────────────╯
218-
257+
219258
/\__/\
220259
( ≧▽≦ )
221260
m( ,,,, )m
@@ -224,6 +263,7 @@ Each tool entry has a `side` field (`left`/`right`) controlling which hand holds
224263
```
225264

226265
**Tool state (PreToolUse), prop on right (Edit):**
266+
227267
```
228268
╭─────────────────────────────────╮
229269
│ Edit → src/utils.py │
@@ -237,6 +277,7 @@ Each tool entry has a `side` field (`left`/`right`) controlling which hand holds
237277
```
238278

239279
**Tool state (PreToolUse), prop on left (Read):**
280+
240281
```
241282
╭─────────────────────────────────╮
242283
│ Read → src/utils.py │
@@ -275,20 +316,14 @@ CHAR_BOTTOM=" || ||\`~~>\n (_) (_)" # the rest of the character, can hav
275316

276317
## Edge Cases
277318

278-
| Scenario | Behaviour |
279-
|---|---|
280-
| Claude omits `<claudesay>` tag | stop.sh exits silently — no duplicate output |
281-
| Multiple tags in one response | Take the last one |
282-
| Tool input path > 50 chars | Truncate with `` |
283-
| Message > 45 chars wide | Wrap to multiple bubble lines |
284-
| Custom character missing a mood | Fall back to default character expression |
285-
| Custom character missing `CHAR_HAND_LEFT` or `CHAR_HAND_RIGHT` | Fall back to `m` for each |
286-
| Tool entry has no `side` or side is `` | No prop shown, both hands rendered normally |
287-
| `jq` not installed | `/claudesay on` preflight check detects this and prints install instructions; hooks exit 0 silently if somehow reached |
288-
| Flag absent | All hooks exit at line 2 — zero overhead |
289-
290-
---
291-
292-
## Caveman Compatibility
293-
294-
`claudesay` stacks naturally with the caveman plugin. Caveman compresses the main response body; `claudesay` bubbles the separately-written summary tag. No conflict. README recommends `caveman-lite` as a complementary install.
319+
| Scenario | Behaviour |
320+
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
321+
| Claude omits `<claudesay>` tag | stop.sh exits silently — no duplicate output |
322+
| Multiple tags in one response | Take the last one |
323+
| Tool input path > 50 chars | Truncate with `` |
324+
| Message > 45 chars wide | Wrap to multiple bubble lines |
325+
| Custom character missing a mood | Fall back to default character expression |
326+
| Custom character missing `CHAR_HAND_LEFT` or `CHAR_HAND_RIGHT` | Fall back to `m` for each |
327+
| Tool entry has no `side` or side is `` | No prop shown, both hands rendered normally |
328+
| `jq` not installed | `/claudesay on` preflight check detects this and prints install instructions; hooks exit 0 silently if somehow reached |
329+
| Flag absent | All hooks exit at line 2 — zero overhead |

hooks/scripts/session-start.sh

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ FLAG="${CLAUDE_PROJECT_DIR}/.claude/.claudesay-active"
66

77
PROTOCOL='<claudesay-protocol>
88
When giving a conversational reply, append this tag at the very end:
9-
<claudesay mood="MOOD">Brief 1-line summary of what you did or said</claudesay>
9+
<claudesay mood="MOOD">First-person summary of what I just did or said</claudesay>
1010
1111
Available moods: happy, excited, thinking, focused, upset, error
1212
- happy / excited → success outcomes (rotate between them for variety)
@@ -16,9 +16,13 @@ Available moods: happy, excited, thinking, focused, upset, error
1616
- error → actual failure
1717
1818
Rules:
19-
- Keep message under 60 chars
20-
- Do NOT add the tag to: pure code blocks, diffs, long technical output, tool-only responses
21-
- Only chatty, conversational replies get a bubble
19+
- Up to ~120 chars. Bubble wraps at 45/line, so 1-2 short sentences.
20+
- First-person voice ("I fixed the flaky test and re-ran CI", not "Fixed the flaky test").
21+
The character speaks AS the agent.
22+
- Cover the whole turn, not just the last sentence — if multiple things happened,
23+
mention them briefly ("I read the hooks and drafted a plan.").
24+
- Do NOT add the tag to: pure code blocks, diffs, long technical output, tool-only responses.
25+
- Only chatty, conversational replies get a bubble.
2226
</claudesay-protocol>'
2327

2428
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":%s}}\n' \

0 commit comments

Comments
 (0)