Skip to content

Commit 5065be3

Browse files
committed
reintroduce the /copy command
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
1 parent 1635b61 commit 5065be3

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ cagent is a multi-agent AI system with hierarchical agent structure and pluggabl
6363
#### Command Layer (`cmd/root/`)
6464

6565
- **Multiple interfaces**: CLI (`run.go`), TUI (default for `run` command), API (`api.go`)
66-
- **Interactive commands**: `/exit`, `/reset`, `/eval`, `/usage`, `/compact` during sessions
66+
- **Interactive commands**: `/exit`, `/reset`, `/eval`, `/usage`, `/compact`, `/copy` during sessions
6767
- **Debug support**: `--debug` flag for detailed logging
6868
- **Gateway mode**: SSE-based transport for external MCP clients like Claude Code
6969

@@ -290,6 +290,7 @@ agents:
290290
- `/reset` - Clear session history
291291
- `/usage` - Show token usage statistics
292292
- `/compact` - Generate summary and compact session history
293+
- `/copy` - Copy the current conversation to clipboard
293294
- `/eval` - Save evaluation data
294295

295296
## File Locations and Patterns

cmd/root/run.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"strings"
1616
"time"
1717

18+
"github.com/atotto/clipboard"
1819
tea "github.com/charmbracelet/bubbletea/v2"
1920
"github.com/fatih/color"
2021
"github.com/spf13/cobra"
@@ -641,12 +642,86 @@ func runUserCommand(userInput string, sess *session.Session, rt runtime.Runtime,
641642
fmt.Printf("%s\n", yellow("No summary generated"))
642643
}
643644

645+
return true, nil
646+
case "/copy":
647+
// Copy the conversation to clipboard
648+
transcript := generatePlainTextTranscript(sess)
649+
if transcript == "" {
650+
fmt.Printf("%s\n", yellow("Conversation is empty; nothing copied."))
651+
return true, nil
652+
}
653+
654+
if err := clipboard.WriteAll(transcript); err != nil {
655+
fmt.Printf("%s\n", yellow("Failed to copy conversation: %s", err.Error()))
656+
return true, err
657+
}
658+
659+
fmt.Printf("%s\n", yellow("Conversation copied to clipboard."))
644660
return true, nil
645661
}
646662

647663
return false, nil
648664
}
649665

666+
// generatePlainTextTranscript generates a plain text transcript from the session
667+
func generatePlainTextTranscript(sess *session.Session) string {
668+
var builder strings.Builder
669+
670+
for _, item := range sess.Messages {
671+
if item.IsMessage() {
672+
msg := item.Message
673+
// Skip implicit messages
674+
if msg.Implicit {
675+
continue
676+
}
677+
678+
switch msg.Message.Role {
679+
case chat.MessageRoleUser:
680+
writeTranscriptSection(&builder, "User", msg.Message.Content)
681+
case chat.MessageRoleAssistant:
682+
agentLabel := msg.AgentName
683+
if agentLabel == "" || agentLabel == "root" {
684+
agentLabel = "Assistant"
685+
}
686+
writeTranscriptSection(&builder, agentLabel, msg.Message.Content)
687+
case chat.MessageRoleTool:
688+
// Format tool results
689+
msgContent := msg.Message.Content
690+
if msg.Message.Name != "" {
691+
writeTranscriptSection(&builder, fmt.Sprintf("Tool Result (%s)", msg.Message.Name), msgContent)
692+
} else {
693+
writeTranscriptSection(&builder, "Tool Result", msgContent)
694+
}
695+
}
696+
} else if item.IsSubSession() {
697+
// Recursively process sub-sessions
698+
subTranscript := generatePlainTextTranscript(item.SubSession)
699+
if subTranscript != "" {
700+
if builder.Len() > 0 {
701+
builder.WriteString("\n\n")
702+
}
703+
builder.WriteString(subTranscript)
704+
}
705+
}
706+
}
707+
708+
return strings.TrimSpace(builder.String())
709+
}
710+
711+
// writeTranscriptSection writes a section to the transcript builder
712+
func writeTranscriptSection(builder *strings.Builder, title, text string) {
713+
trimmed := strings.TrimSpace(text)
714+
if trimmed == "" {
715+
return
716+
}
717+
if builder.Len() > 0 {
718+
builder.WriteString("\n\n")
719+
}
720+
builder.WriteString(title)
721+
builder.WriteString(":\n")
722+
builder.WriteString(trimmed)
723+
}
724+
650725
// parseAttachCommand parses user input for /attach commands
651726
// Returns the message text (with /attach commands removed) and the attachment path
652727
func parseAttachCommand(input string) (messageText, attachPath string) {

docs/USAGE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ During CLI sessions, you can use special commands:
7777
| `/reset` | Clear conversation history |
7878
| `/eval` | Save current conversation for evaluation |
7979
| `/compact` | Compact conversation to lower context usage |
80+
| `/copy` | Copy the current conversation to clipboard |
8081

8182
## 🔧 Configuration Reference
8283

0 commit comments

Comments
 (0)