1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
package tmuxedit
import (
"regexp"
"strings"
)
// claudeAgent handles Claude Code's ❯ prompt between ──── horizontal rules.
// Claude Code runs in actual vim mode, so clearing uses vim commands.
// Wrapped text appears as indented continuation lines without ❯.
type claudeAgent struct{ baseAgent }
// newClaudeAgent returns a claudeAgent with the default configuration.
// SectionPattern scopes extraction to the last ─── delimited area, avoiding
// false positives from ❯ in previous messages.
func newClaudeAgent() *claudeAgent {
return &claudeAgent{baseAgent{
name: "claude",
displayName: "Claude Code",
detectPattern: `(❯|(?i)claude code|(?i)anthropic)`,
sectionPat: `^─{5,}`,
promptPat: `(?m)❯\s*(.+)$`,
clearFirst: true,
clearKeys: "C-a C-k",
newlineKeys: "S-Enter",
submitKeys: "Enter",
}}
}
// ExtractPrompt extracts the prompt text from the last section between ─────
// rules. Within the scoped section, all non-empty lines are collected:
// ❯-prefixed lines have the prefix stripped, and indented continuation lines
// (wrapped text without ❯) are included as-is after trimming.
func (c *claudeAgent) ExtractPrompt(paneContent string) string {
if c.promptPat == "" {
return ""
}
re, err := regexp.Compile(c.promptPat)
if err != nil {
return ""
}
// Scope to the last section between ───── delimiters
content := scopeToLastSection(paneContent, c.sectionPat)
// Collect ❯-prefixed lines and their continuation lines (indented
// wrapped text without ❯). Only include non-❯ lines that directly
// follow a ❯-matched line to avoid picking up unrelated content.
paneLines := strings.Split(content, "\n")
var lines []string
inPrompt := false
for _, line := range paneLines {
m := re.FindStringSubmatch(line)
if len(m) >= 2 {
// ❯-prefixed line: use the captured text
cleaned := stripNoise(m[1], c.stripPatterns)
if cleaned != "" {
lines = append(lines, cleaned)
}
inPrompt = true
} else if inPrompt {
// Non-❯ line after a prompt: include indented continuation text
trimmed := strings.TrimSpace(line)
if trimmed != "" {
lines = append(lines, trimmed)
} else {
// Empty line breaks the continuation
inPrompt = false
}
}
}
return strings.Join(lines, "\n")
}
// ClearInput sends vim commands to clear Claude Code's input:
// Escape to ensure normal mode, gg to go to top, C-v G d to visual-block
// select all and delete, then i to re-enter insert mode.
func (c *claudeAgent) ClearInput(paneID string) error {
if !c.clearFirst || c.clearKeys == "" {
return nil
}
if err := sendClearSequence(paneID, c.clearKeys); err != nil {
return err
}
sleepAfterClear()
return nil
}
|