From 0eb01e6e75f07dc5d6286673817105015cdf7c0d Mon Sep 17 00:00:00 2001 From: Song-Ze Yu <125909247+vaclisinc@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:29:25 +0800 Subject: [PATCH] Fix #6: Implement terminal-style UI for email notifications (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix self-reply loop issue when using same email for send/receive - Add Message-ID tracking to prevent processing system-sent emails - Track sent emails in sent-messages.json with auto-cleanup - Skip system emails in both email-listener.js and relay-pty.js - Extract session from token/headers/body for proper reply routing - Reduce verbose logging in tmux-injector to debug level Fixes #3 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix working directory issue - enable claude-remote to run from any directory - Use absolute path to load .env file instead of relying on current working directory - Fix environment variable loading in both main program and relay service - Now claude-remote can be executed from any directory Fixes #5 * Fix issue #6: Implement terminal-style UI for email notifications - Redesigned email template with terminal/console aesthetic - Used monospace fonts and dark theme for tech look - Fixed Claude response truncation issue (removed 500 char limit) - Increased tmux buffer capture from 50 to 200 lines - Preserved code formatting (removed space collapsing) - Added terminal-style command prompts and colored output - Created test script for long content validation * Fix terminal UI visual issues - Fixed traffic light buttons spacing (now properly separated) - Changed background from pure black to lighter gray (#f5f5f5) - Terminal content background changed to softer dark (#1a1a1a) - Code blocks background changed to #262626 for better contrast - Improved overall visual hierarchy and readability * Fine-tune traffic light button spacing - Adjusted spacing between buttons from 8px to 6px - Reduced gap after buttons from 20px to 12px - Achieved more natural macOS-like appearance * Use table layout for better email client compatibility - Changed from inline-flex to table layout for traffic light buttons - Set explicit 5px spacing between buttons using table cells - This ensures consistent rendering across different email clients --------- Co-authored-by: Claude --- src/channels/email/smtp.js | 196 ++++++++++++++++++++----------- src/data/processed-messages.json | 22 ++-- src/data/sent-messages.json | 46 ++++++++ src/data/session-map.json | 81 +++++++++++++ src/utils/tmux-monitor.js | 17 +-- test-long-email.js | 142 ++++++++++++++++++++++ 6 files changed, 419 insertions(+), 85 deletions(-) create mode 100644 src/data/sent-messages.json create mode 100755 test-long-email.js diff --git a/src/channels/email/smtp.js b/src/channels/email/smtp.js index 73b583f..5d0f1e6 100644 --- a/src/channels/email/smtp.js +++ b/src/channels/email/smtp.js @@ -310,47 +310,77 @@ class EmailChannel extends NotificationChannel { completed: { subject: '[Claude-Code-Remote #{{token}}] Claude Code Task Completed - {{project}}', html: ` -
-
-

- 🎉 Claude Code Task Completed -

+
+
+ +
+ + + + + + + +
claude-code-remote@{{project}} - Task Completed
+
-
-

- Project: {{projectDir}}
- Time: {{timestamp}}
- Status: {{type}} -

-
- -
-

📝 Your Question

-

{{userQuestion}}

-
- -
-

🤖 Claude's Response

-

{{claudeResponse}}

-
- -
-

💡 How to Continue the Conversation

-

- To continue conversation with Claude Code, please reply to this email directly and enter your instructions in the email body. -

-
- Example replies:
- • "Please continue optimizing the code"
- • "Generate unit tests"
- • "Explain the purpose of this function" + +
+ +
+ $ claude-code status
+
+ PROJECT: {{projectDir}}
+ SESSION: #{{token}}
+ STATUS: ✓ Task Completed
+ TIME: {{timestamp}} +
+
+ + +
+ $ cat user_input.txt
+
{{userQuestion}}
+
+ + +
+ $ claude-code execute
+
+ [INFO] Processing request...
+ [INFO] Executing task... +
+
{{claudeResponse}}
+
+ [SUCCESS] Task completed successfully ✓ +
+
+ + +
+ $ claude-code help --continue
+
+
→ TO CONTINUE THIS SESSION:
+
+ Reply to this email directly with your next instruction.

+ Examples:
+ • "Add error handling to the function"
+ • "Write unit tests for this code"
+ • "Optimize the performance" +
+
+
+ + +
+ $ echo $SESSION_INFO
+
+ SESSION_ID={{sessionId}}
+ EXPIRES_IN=24h
+ SECURITY=Do not forward this email
+ POWERED_BY=Claude-Code-Remote +
-
- -
-

Session ID: {{sessionId}}

-

🔒 Security note: Please do not forward this email, session will automatically expire after 24 hours

-

📧 This is an automated email from Claude-Code-Remote

@@ -383,36 +413,66 @@ Security Note: Please do not forward this email, session will automatically expi waiting: { subject: '[Claude-Code-Remote #{{token}}] Claude Code Waiting for Input - {{project}}', html: ` -
-
-

- ⏳ Claude Code Waiting for Your Guidance -

+
+
+ +
+ + + + + + + +
claude-code-remote@{{project}} - Waiting for Input
+
-
-

- Project: {{projectDir}}
- Time: {{timestamp}}
- Status: {{type}} -

-
- -
-

⏳ Waiting for Processing

-

{{message}}

-
- -
-

💬 Please Provide Guidance

-

- Claude needs your further guidance. Please reply to this email to tell Claude what to do next. -

-
- -
-

Session ID: {{sessionId}}

-

🔒 Security note: Please do not forward this email, session will automatically expire after 24 hours

-

📧 This is an automated email from Claude-Code-Remote

+ +
+ +
+ $ claude-code status
+
+ PROJECT: {{projectDir}}
+ SESSION: #{{token}}
+ STATUS: ⏳ Waiting for input
+ TIME: {{timestamp}} +
+
+ + +
+ $ claude-code wait
+
+ [WAITING] Claude needs your input to continue...
+
+
+ {{message}} +
+
+ + +
+ $ claude-code help --respond
+
+
→ ACTION REQUIRED:
+
+ Claude is waiting for your guidance.

+ Reply to this email with your instructions to continue. +
+
+
+ + +
+ $ echo $SESSION_INFO
+
+ SESSION_ID={{sessionId}}
+ EXPIRES_IN=24h
+ SECURITY=Do not forward this email
+ POWERED_BY=Claude-Code-Remote +
+
diff --git a/src/data/processed-messages.json b/src/data/processed-messages.json index 1eccd07..02ff827 100644 --- a/src/data/processed-messages.json +++ b/src/data/processed-messages.json @@ -1,38 +1,42 @@ [ { "id": 1312, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1315, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1310, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1323, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1331, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1334, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1342, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1346, - "timestamp": 1753632056082 + "timestamp": 1754021490457 }, { "id": 1348, - "timestamp": 1753632056082 + "timestamp": 1754021490457 + }, + { + "id": 180, + "timestamp": 1754021490457 } ] \ No newline at end of file diff --git a/src/data/sent-messages.json b/src/data/sent-messages.json new file mode 100644 index 0000000..5099b92 --- /dev/null +++ b/src/data/sent-messages.json @@ -0,0 +1,46 @@ +{ + "messages": [ + { + "messageId": "<52d15aa1-d5a4-4d7d-8f01-4752d7d5fc6f-1754021037319@claude-code-remote>", + "sessionId": "52d15aa1-d5a4-4d7d-8f01-4752d7d5fc6f", + "token": "49WUF9NS", + "type": "notification", + "sentAt": "2025-08-01T04:03:59.850Z" + }, + { + "messageId": "", + "sessionId": "eba8744e-8cc1-4fad-9dc8-69d558c51cca", + "token": "N9PHUN4Q", + "type": "notification", + "sentAt": "2025-08-01T04:06:52.776Z" + }, + { + "messageId": "<859daa99-1ea9-4c40-aa68-c3967a0d7e4e-1754021233658@claude-code-remote>", + "sessionId": "859daa99-1ea9-4c40-aa68-c3967a0d7e4e", + "token": "GXWFSL3S", + "type": "notification", + "sentAt": "2025-08-01T04:07:15.556Z" + }, + { + "messageId": "", + "sessionId": "a1ed6757-6782-4b22-a486-aab5a9d60a3c", + "token": "6EZXA6IN", + "type": "notification", + "sentAt": "2025-08-01T04:07:49.959Z" + }, + { + "messageId": "<2122d57c-8434-44f0-b4e6-7eafb40ed49d-1754021285815@claude-code-remote>", + "sessionId": "2122d57c-8434-44f0-b4e6-7eafb40ed49d", + "token": "ZQY1UOIJ", + "type": "notification", + "sentAt": "2025-08-01T04:08:07.833Z" + }, + { + "messageId": "<2b30b1f7-b9c3-4cb4-b889-11c58009bd07-1754021533703@claude-code-remote>", + "sessionId": "2b30b1f7-b9c3-4cb4-b889-11c58009bd07", + "token": "L4KQ8DVJ", + "type": "notification", + "sentAt": "2025-08-01T04:12:15.795Z" + } + ] +} \ No newline at end of file diff --git a/src/data/session-map.json b/src/data/session-map.json index f1ee324..2b79bdb 100644 --- a/src/data/session-map.json +++ b/src/data/session-map.json @@ -529,5 +529,86 @@ "sessionId": "4e67ac76-5c0a-4229-b3cd-c4ff865c9df3", "tmuxSession": "video", "description": "completed - Claude-Code-Remote" + }, + "3E0T4KHA": { + "type": "pty", + "createdAt": 1754020190, + "expiresAt": 1754106590, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "1ce6c5e0-4151-4d51-9472-f481ed23f023", + "tmuxSession": "WavJaby", + "description": "completed - Claude-Code-Remote" + }, + "8BIFRACK": { + "type": "pty", + "createdAt": 1754020301, + "expiresAt": 1754106701, + "cwd": "/Users/vaclis./Documents/project/ReThreads", + "sessionId": "0fd97a36-da77-4c9b-917f-6963c0373458", + "tmuxSession": "my-project", + "description": "completed - ReThreads" + }, + "OG1SS2R9": { + "type": "pty", + "createdAt": 1754020306, + "expiresAt": 1754106706, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "43b979a7-9039-4d0f-8c09-b86365ef0e61", + "tmuxSession": "WavJaby", + "description": "completed - Claude-Code-Remote" + }, + "49WUF9NS": { + "type": "pty", + "createdAt": 1754021037, + "expiresAt": 1754107437, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "52d15aa1-d5a4-4d7d-8f01-4752d7d5fc6f", + "tmuxSession": "WavJaby", + "description": "completed - Claude-Code-Remote" + }, + "N9PHUN4Q": { + "type": "pty", + "createdAt": 1754021210, + "expiresAt": 1754107610, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "eba8744e-8cc1-4fad-9dc8-69d558c51cca", + "tmuxSession": "test-session", + "description": "completed - Claude-Code-Remote-Test" + }, + "GXWFSL3S": { + "type": "pty", + "createdAt": 1754021233, + "expiresAt": 1754107633, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "859daa99-1ea9-4c40-aa68-c3967a0d7e4e", + "tmuxSession": "WavJaby", + "description": "completed - Claude-Code-Remote" + }, + "6EZXA6IN": { + "type": "pty", + "createdAt": 1754021267, + "expiresAt": 1754107667, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "a1ed6757-6782-4b22-a486-aab5a9d60a3c", + "tmuxSession": "test-session", + "description": "completed - Claude-Code-Remote-Test" + }, + "ZQY1UOIJ": { + "type": "pty", + "createdAt": 1754021285, + "expiresAt": 1754107685, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "2122d57c-8434-44f0-b4e6-7eafb40ed49d", + "tmuxSession": "WavJaby", + "description": "completed - Claude-Code-Remote" + }, + "L4KQ8DVJ": { + "type": "pty", + "createdAt": 1754021533, + "expiresAt": 1754107933, + "cwd": "/Users/vaclis./Documents/project/Claude-Code-Remote", + "sessionId": "2b30b1f7-b9c3-4cb4-b889-11c58009bd07", + "tmuxSession": "test-session", + "description": "completed - Claude-Code-Remote-Test" } } \ No newline at end of file diff --git a/src/utils/tmux-monitor.js b/src/utils/tmux-monitor.js index dc0de39..73ba423 100644 --- a/src/utils/tmux-monitor.js +++ b/src/utils/tmux-monitor.js @@ -61,7 +61,7 @@ class TmuxMonitor { * @param {number} lines - Number of lines to retrieve * @returns {Object} - { userQuestion, claudeResponse } */ - getRecentConversation(sessionName, lines = 50) { + getRecentConversation(sessionName, lines = 200) { try { const captureFile = path.join(this.captureDir, `${sessionName}.log`); @@ -87,7 +87,7 @@ class TmuxMonitor { * @param {string} sessionName - The tmux session name * @param {number} lines - Number of lines to retrieve */ - getFromTmuxBuffer(sessionName, lines = 50) { + getFromTmuxBuffer(sessionName, lines = 200) { try { // Capture the pane contents const buffer = execSync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, { @@ -150,17 +150,18 @@ class TmuxMonitor { // Join response lines and clean up claudeResponse = responseLines.join('\n').trim(); - // Remove box characters and clean up formatting + // Remove box characters but preserve formatting claudeResponse = claudeResponse .replace(/[╭╰│]/g, '') .replace(/^\s*│\s*/gm, '') - .replace(/\s+/g, ' ') + // Don't collapse multiple spaces - preserve code formatting + // .replace(/\s+/g, ' ') .trim(); - // Limit response length - if (claudeResponse.length > 500) { - claudeResponse = claudeResponse.substring(0, 497) + '...'; - } + // Don't limit response length - we want the full response + // if (claudeResponse.length > 500) { + // claudeResponse = claudeResponse.substring(0, 497) + '...'; + // } // If we didn't find a question in the standard format, look for any recent text input if (!userQuestion) { diff --git a/test-long-email.js b/test-long-email.js new file mode 100755 index 0000000..1c85d64 --- /dev/null +++ b/test-long-email.js @@ -0,0 +1,142 @@ +#!/usr/bin/env node + +/** + * Test script for long email content + * Tests the new terminal-style email template with long Claude responses + */ + +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '.env') }); + +const EmailChannel = require('./src/channels/email/smtp'); +const ConfigManager = require('./src/core/config'); + +async function testLongEmail() { + console.log('Testing long email content...\n'); + + // Load config + const configManager = new ConfigManager(); + configManager.load(); + const emailConfig = configManager.getChannel('email'); + + if (!emailConfig || !emailConfig.enabled) { + console.error('❌ Email channel not configured or disabled'); + console.log('Please configure email in config/channels.json first'); + process.exit(1); + } + + // Create email channel + const email = new EmailChannel(emailConfig.config); + + // Create a test notification with very long content + const longCodeExample = ` +function processData(inputArray) { + // This is a sample function with detailed implementation + const results = []; + + for (let i = 0; i < inputArray.length; i++) { + const item = inputArray[i]; + + // Validate input + if (!item || typeof item !== 'object') { + console.warn(\`Invalid item at index \${i}\`); + continue; + } + + // Process each item + const processed = { + id: item.id || generateId(), + name: item.name?.trim() || 'Unknown', + timestamp: new Date().toISOString(), + data: { + original: item, + processed: true, + metadata: { + source: 'test-system', + version: '1.0.0', + processingTime: Date.now() + } + } + }; + + // Apply transformations + if (item.transform) { + processed.data.transformed = applyTransform(item.transform, item); + } + + results.push(processed); + } + + return results; +} + +// Helper functions +function generateId() { + return 'id_' + Math.random().toString(36).substr(2, 9); +} + +function applyTransform(transformType, data) { + switch (transformType) { + case 'uppercase': + return JSON.stringify(data).toUpperCase(); + case 'reverse': + return JSON.stringify(data).split('').reverse().join(''); + default: + return data; + } +} +`; + + const testNotification = { + type: 'completed', + title: 'Test Long Content', + message: 'Testing terminal-style email with long Claude response', + project: 'Claude-Code-Remote-Test', + metadata: { + userQuestion: 'Please help me implement a data processing function with error handling, validation, and transformation features', + claudeResponse: `I'll help you implement a comprehensive data processing function. Here's a complete implementation with all the features you requested: + +${longCodeExample} + +This implementation includes: + +1. **Input Validation**: The function checks each item to ensure it's a valid object before processing. + +2. **Error Handling**: Uses try-catch blocks and console warnings for invalid items. + +3. **Data Transformation**: Supports different transformation types through the \`applyTransform\` function. + +4. **Metadata Tracking**: Each processed item includes metadata about when and how it was processed. + +5. **ID Generation**: Automatically generates unique IDs for items that don't have one. + +Additional features you might want to consider: + +- **Async Processing**: For handling large datasets or async transformations +- **Batch Processing**: Process items in chunks to avoid memory issues +- **Progress Tracking**: Add callbacks or events to track processing progress +- **Custom Validators**: Allow custom validation functions to be passed in +- **Error Recovery**: Implement retry logic for failed items + +The function is designed to be extensible and maintainable. You can easily add new transformation types or modify the processing logic as needed.`, + tmuxSession: 'test-session' + } + }; + + try { + console.log('Sending test email with long content...'); + const result = await email._sendImpl(testNotification); + + if (result) { + console.log('✅ Email sent successfully!'); + console.log('Check your inbox for the terminal-style email'); + } else { + console.log('❌ Failed to send email'); + } + } catch (error) { + console.error('❌ Error:', error.message); + } +} + +// Run test +testLongEmail(); \ No newline at end of file