1
0
Fork 0
mirror of https://github.com/imjasonh/cnotes synced 2026-07-19 07:08:17 +00:00

Add configurable emojis for user and assistant messages

This commit is contained in:
Jason Hall 2025-07-21 14:41:19 -04:00
parent c2927548f9
commit cc4541b256
Failed to extract signature
6 changed files with 52 additions and 12 deletions

9
.claude/notes.json Normal file
View file

@ -0,0 +1,9 @@
{
"enabled": true,
"max_excerpt_length": 5000,
"max_prompts": 2,
"notes_ref": "claude-conversations",
"exclude_patterns": ["password", "token", "key", "secret"],
"user_emoji": "🧑",
"assistant_emoji": "🤖"
}

View file

@ -4,6 +4,8 @@
⭐️ `cnotes` is pronounced like 💵 c-notes or 🏊 [_cenotes_](https://en.wikipedia.org/wiki/Cenote) ⭐️ `cnotes` is pronounced like 💵 c-notes or 🏊 [_cenotes_](https://en.wikipedia.org/wiki/Cenote)
The conversation display now includes configurable emojis for user and assistant messages.
## Quick Start ## Quick Start
```bash ```bash

View file

@ -6,6 +6,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/imjasonh/cnotes/internal/config"
"github.com/imjasonh/cnotes/internal/notes" "github.com/imjasonh/cnotes/internal/notes"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -101,7 +102,8 @@ If no commit is specified, shows notes for HEAD.`,
} }
// Pretty-print in Markdown format // Pretty-print in Markdown format
printConversationMarkdown(*note, commit) cfg := config.LoadNotesConfig(".")
printConversationMarkdown(*note, commit, cfg)
return nil return nil
}, },
} }
@ -138,7 +140,7 @@ var listCmd = &cobra.Command{
} }
// printConversationMarkdown formats a conversation note as readable Markdown // printConversationMarkdown formats a conversation note as readable Markdown
func printConversationMarkdown(note notes.ConversationNote, commit string) { func printConversationMarkdown(note notes.ConversationNote, commit string, cfg *config.NotesConfig) {
fmt.Printf("# Claude Conversation Notes\n\n") fmt.Printf("# Claude Conversation Notes\n\n")
// Get commit info // Get commit info
@ -155,7 +157,7 @@ func printConversationMarkdown(note notes.ConversationNote, commit string) {
if note.ConversationExcerpt != "" { if note.ConversationExcerpt != "" {
fmt.Printf("## Conversation Transcript\n\n") fmt.Printf("## Conversation Transcript\n\n")
// Clean up and format the conversation excerpt for better readability // Clean up and format the conversation excerpt for better readability
formatted := formatConversationExcerpt(note.ConversationExcerpt) formatted := formatConversationExcerpt(note.ConversationExcerpt, cfg)
fmt.Printf("%s\n\n", formatted) fmt.Printf("%s\n\n", formatted)
} }
@ -164,7 +166,7 @@ func printConversationMarkdown(note notes.ConversationNote, commit string) {
} }
// formatConversationExcerpt cleans up the conversation excerpt for better readability // formatConversationExcerpt cleans up the conversation excerpt for better readability
func formatConversationExcerpt(excerpt string) string { func formatConversationExcerpt(excerpt string, cfg *config.NotesConfig) string {
// Replace escaped newlines with actual newlines // Replace escaped newlines with actual newlines
formatted := strings.ReplaceAll(excerpt, "\\n", "\n") formatted := strings.ReplaceAll(excerpt, "\\n", "\n")
@ -195,11 +197,11 @@ func formatConversationExcerpt(excerpt string) string {
} }
switch { switch {
case strings.HasPrefix(line, "User:"): case strings.HasPrefix(line, "User:") || (cfg != nil && cfg.UserEmoji != "" && strings.HasPrefix(line, cfg.UserEmoji)):
// Bold user prompts // Bold user prompts
formattedLines = append(formattedLines, "**"+line+"**") formattedLines = append(formattedLines, "**"+line+"**")
case strings.HasPrefix(line, "Claude:"): case strings.HasPrefix(line, "Claude:") || (cfg != nil && cfg.AssistantEmoji != "" && strings.HasPrefix(line, cfg.AssistantEmoji)):
// Keep Claude responses as-is // Keep Claude responses as-is
formattedLines = append(formattedLines, line) formattedLines = append(formattedLines, line)

View file

@ -132,7 +132,7 @@ func processGitCommit(ctx context.Context, input HookInput, bashInput BashToolIn
return fmt.Errorf("could not extract commit hash") return fmt.Errorf("could not extract commit hash")
} }
// Create notes manager // Create notes manager and load config
notesManager := notes.NewNotesManager(input.CWD) notesManager := notes.NewNotesManager(input.CWD)
cfg := config.LoadNotesConfig(input.CWD) cfg := config.LoadNotesConfig(input.CWD)
notesManager.SetNotesRef(cfg.NotesRef) notesManager.SetNotesRef(cfg.NotesRef)
@ -149,7 +149,7 @@ func processGitCommit(ctx context.Context, input HookInput, bashInput BashToolIn
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
// Extract conversation context since the last commit // Extract conversation context since the last commit
contextExtractor := conv.NewContextExtractor() contextExtractor := conv.NewContextExtractor(cfg)
conversationContext, err := contextExtractor.ExtractContextSince(input.TranscriptPath, input.SessionID, previousCommitTime) conversationContext, err := contextExtractor.ExtractContextSince(input.TranscriptPath, input.SessionID, previousCommitTime)
if err != nil { if err != nil {
return fmt.Errorf("failed to extract conversation context: %w", err) return fmt.Errorf("failed to extract conversation context: %w", err)

View file

@ -14,6 +14,8 @@ type NotesConfig struct {
IncludeToolOutput bool `json:"include_tool_output"` // Whether to include tool output in notes IncludeToolOutput bool `json:"include_tool_output"` // Whether to include tool output in notes
NotesRef string `json:"notes_ref"` // Git notes reference name NotesRef string `json:"notes_ref"` // Git notes reference name
ExcludePatterns []string `json:"exclude_patterns"` // Patterns to exclude from notes ExcludePatterns []string `json:"exclude_patterns"` // Patterns to exclude from notes
UserEmoji string `json:"user_emoji"` // Emoji to use for user messages
AssistantEmoji string `json:"assistant_emoji"` // Emoji to use for assistant messages
} }
// DefaultNotesConfig returns the default configuration // DefaultNotesConfig returns the default configuration
@ -32,6 +34,8 @@ func DefaultNotesConfig() *NotesConfig {
"api_key", "api_key",
"auth", "auth",
}, },
UserEmoji: "👤",
AssistantEmoji: "🤖",
} }
} }
@ -62,6 +66,12 @@ func LoadNotesConfig(projectDir string) *NotesConfig {
if config.MaxPrompts <= 0 { if config.MaxPrompts <= 0 {
config.MaxPrompts = 2 config.MaxPrompts = 2
} }
if config.UserEmoji == "" {
config.UserEmoji = "👤"
}
if config.AssistantEmoji == "" {
config.AssistantEmoji = "🤖"
}
return &config return &config
} }

View file

@ -10,6 +10,8 @@ import (
"sort" "sort"
"strings" "strings"
"time" "time"
"github.com/imjasonh/cnotes/internal/config"
) )
// ConversationContext represents relevant conversation context for a commit // ConversationContext represents relevant conversation context for a commit
@ -40,10 +42,11 @@ type ToolInteraction struct {
type ContextExtractor struct { type ContextExtractor struct {
maxExcerptLength int maxExcerptLength int
sensitivePatterns []*regexp.Regexp sensitivePatterns []*regexp.Regexp
config *config.NotesConfig
} }
// NewContextExtractor creates a new context extractor with default settings // NewContextExtractor creates a new context extractor with default settings
func NewContextExtractor() *ContextExtractor { func NewContextExtractor(cfg *config.NotesConfig) *ContextExtractor {
// Patterns to filter out sensitive information // Patterns to filter out sensitive information
sensitivePatterns := []*regexp.Regexp{ sensitivePatterns := []*regexp.Regexp{
regexp.MustCompile(`(?i)(password|token|key|secret)[:\s]*[^\s\n]+`), regexp.MustCompile(`(?i)(password|token|key|secret)[:\s]*[^\s\n]+`),
@ -52,9 +55,15 @@ func NewContextExtractor() *ContextExtractor {
regexp.MustCompile(`[A-Za-z0-9+/]{40,}={0,2}`), // Base64 encoded secrets regexp.MustCompile(`[A-Za-z0-9+/]{40,}={0,2}`), // Base64 encoded secrets
} }
maxLength := 5000
if cfg != nil && cfg.MaxExcerptLength > 0 {
maxLength = cfg.MaxExcerptLength
}
return &ContextExtractor{ return &ContextExtractor{
maxExcerptLength: 5000, // 5KB limit for context maxExcerptLength: maxLength,
sensitivePatterns: sensitivePatterns, sensitivePatterns: sensitivePatterns,
config: cfg,
} }
} }
@ -361,7 +370,11 @@ func (ce *ContextExtractor) CreateExcerpt(context *ConversationContext) string {
if len(content) > 200 { if len(content) > 200 {
content = content[:197] + "..." content = content[:197] + "..."
} }
line = fmt.Sprintf("User: %s", content) emoji := "👤"
if ce.config != nil && ce.config.UserEmoji != "" {
emoji = ce.config.UserEmoji
}
line = fmt.Sprintf("%s User: %s", emoji, content)
case "assistant": case "assistant":
// Format assistant responses // Format assistant responses
@ -369,7 +382,11 @@ func (ce *ContextExtractor) CreateExcerpt(context *ConversationContext) string {
if len(content) > 200 { if len(content) > 200 {
content = content[:197] + "..." content = content[:197] + "..."
} }
line = fmt.Sprintf("Claude: %s", content) emoji := "🤖"
if ce.config != nil && ce.config.AssistantEmoji != "" {
emoji = ce.config.AssistantEmoji
}
line = fmt.Sprintf("%s Claude: %s", emoji, content)
case "tool": case "tool":
// Format tool uses // Format tool uses