Merge pull request #2 from kevinsslin/fix/email-from-env-variable
Remove hardcoded values and implement environment-based configuration
This commit is contained in:
parent
5ddda6217e
commit
e5de5a7932
67
.env.example
67
.env.example
|
|
@ -1,13 +1,14 @@
|
|||
# Claude Code Remote Email Configuration
|
||||
# Claude Code Remote Email Configuration Example
|
||||
# Copy this file to .env and configure with your actual values
|
||||
|
||||
# ===== SMTP 发送邮件配置 =====
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_PORT=465
|
||||
SMTP_SECURE=true
|
||||
SMTP_USER=your-email@gmail.com
|
||||
SMTP_PASS=your-app-password
|
||||
|
||||
# 发件人信息
|
||||
# 发件人信息 (可选,默认使用 SMTP_USER)
|
||||
EMAIL_FROM=your-email@gmail.com
|
||||
EMAIL_FROM_NAME=Claude Code Remote 通知系统
|
||||
|
||||
|
|
@ -20,19 +21,19 @@ IMAP_PASS=your-app-password
|
|||
|
||||
# ===== 邮件路由配置 =====
|
||||
# 接收通知的邮箱地址
|
||||
EMAIL_TO=your-notification-email@gmail.com
|
||||
EMAIL_TO=your-email@gmail.com
|
||||
|
||||
# 允许发送命令的邮箱地址(安全白名单)
|
||||
ALLOWED_SENDERS=your-notification-email@gmail.com
|
||||
ALLOWED_SENDERS=your-email@gmail.com
|
||||
|
||||
# ===== 系统配置 =====
|
||||
# 会话映射文件路径 (请替换为你的实际路径)
|
||||
SESSION_MAP_PATH=/Users/your-username/path/to/Claude-Code-Remote/src/data/session-map.json
|
||||
# 会话映射文件路径
|
||||
SESSION_MAP_PATH=/path/to/your/project/src/data/session-map.json
|
||||
|
||||
# 运行模式:pty 或 tmux
|
||||
INJECTION_MODE=pty
|
||||
|
||||
# Claude CLI 路径(默认使用系统PATH中的claude)
|
||||
# Claude CLI 路径(可选,默认使用系统PATH中的claude)
|
||||
CLAUDE_CLI_PATH=claude
|
||||
|
||||
# 日志级别:debug, info, warn, error
|
||||
|
|
@ -41,9 +42,55 @@ LOG_LEVEL=info
|
|||
# 是否记录PTY输出(调试用)
|
||||
PTY_OUTPUT_LOG=false
|
||||
|
||||
# ===== 超时配置 =====
|
||||
# 命令执行超时时间(毫秒)
|
||||
COMMAND_TIMEOUT=10000
|
||||
|
||||
# SMTP 连接超时时间(毫秒)
|
||||
SMTP_TIMEOUT=10000
|
||||
|
||||
# 通知超时时间(毫秒)
|
||||
NOTIFICATION_TIMEOUT=3000
|
||||
|
||||
# 通知显示时间(毫秒)
|
||||
NOTIFICATION_DISPLAY_TIME=10000
|
||||
|
||||
# ===== 邮件模板配置 =====
|
||||
# 邮件检查间隔(秒)
|
||||
CHECK_INTERVAL=30
|
||||
CHECK_INTERVAL=20
|
||||
|
||||
# 会话超时时间(小时)
|
||||
SESSION_TIMEOUT=24
|
||||
|
||||
# ===== 测试配置(可选)=====
|
||||
# 测试邮件使用的固定令牌(可选,默认动态生成)
|
||||
TEST_TOKEN=
|
||||
|
||||
# Gmail 应用密码(用于测试脚本,可选)
|
||||
GMAIL_APP_PASSWORD=
|
||||
|
||||
# ===== Gmail 配置说明 =====
|
||||
# 1. 启用两步验证: https://myaccount.google.com/security
|
||||
# 2. 生成应用密码: https://myaccount.google.com/apppasswords
|
||||
# 3. 将生成的16位密码填入 SMTP_PASS 和 IMAP_PASS
|
||||
# 4. 确保 SMTP_PORT=465 和 SMTP_SECURE=true (推荐SSL连接)
|
||||
|
||||
# ===== 其他邮件服务商配置示例 =====
|
||||
# QQ邮箱:
|
||||
# SMTP_HOST=smtp.qq.com
|
||||
# SMTP_PORT=587 或 465
|
||||
# IMAP_HOST=imap.qq.com
|
||||
# IMAP_PORT=993
|
||||
|
||||
# 163邮箱:
|
||||
# SMTP_HOST=smtp.163.com
|
||||
# SMTP_PORT=587 或 465
|
||||
# IMAP_HOST=imap.163.com
|
||||
# IMAP_PORT=993
|
||||
|
||||
# Outlook:
|
||||
# SMTP_HOST=smtp.live.com
|
||||
# SMTP_PORT=587
|
||||
# IMAP_HOST=imap-mail.outlook.com
|
||||
# IMAP_PORT=993
|
||||
EOF < /dev/null
|
||||
|
|
@ -50,3 +50,4 @@ tmp/
|
|||
temp/src/data/sessions/
|
||||
src/data/processed-messages.json
|
||||
src/data/session-map.json
|
||||
src/data/sessions/*.json
|
||||
|
|
|
|||
|
|
@ -178,7 +178,8 @@ class RemoteControlSetup {
|
|||
}
|
||||
// If clauderun fails, try using full path command
|
||||
console.log('🔄 Trying full path command...');
|
||||
const fallbackCommand = `tmux new-session -d -s ${this.sessionName} -c "${workingDir}" /Users/jessytsui/.nvm/versions/node/v18.17.0/bin/claude --dangerously-skip-permissions`;
|
||||
const claudePath = process.env.CLAUDE_CLI_PATH || 'claude';
|
||||
const fallbackCommand = `tmux new-session -d -s ${this.sessionName} -c "${workingDir}" ${claudePath} --dangerously-skip-permissions`;
|
||||
exec(fallbackCommand, (fallbackError) => {
|
||||
if (fallbackError) {
|
||||
console.log(`❌ Full path command also failed: ${fallbackError.message}`);
|
||||
|
|
@ -234,7 +235,7 @@ class RemoteControlSetup {
|
|||
|
||||
console.log('📱 Email testing:');
|
||||
console.log(' Token will include session information, automatically routing to correct tmux session');
|
||||
console.log(' Recipient email: jiaxicui446@gmail.com');
|
||||
console.log(` Recipient email: ${process.env.EMAIL_TO}`);
|
||||
console.log(' Reply with command: echo "Remote control test"\n');
|
||||
|
||||
console.log('🚨 Important reminders:');
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
* Main entry point for the CLI tool
|
||||
*/
|
||||
|
||||
// Load environment variables
|
||||
require('dotenv').config();
|
||||
|
||||
const Logger = require('./src/core/logger');
|
||||
const Notifier = require('./src/core/notifier');
|
||||
const ConfigManager = require('./src/core/config');
|
||||
|
|
|
|||
|
|
@ -6,32 +6,7 @@
|
|||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"smtp": {
|
||||
"host": "smtp.feishu.cn",
|
||||
"port": 465,
|
||||
"secure": true,
|
||||
"auth": {
|
||||
"user": "noreply@pandalla.ai",
|
||||
"pass": "kKgS3tNReRTL3RQC"
|
||||
}
|
||||
},
|
||||
"imap": {
|
||||
"host": "imap.feishu.cn",
|
||||
"port": 993,
|
||||
"secure": true,
|
||||
"auth": {
|
||||
"user": "noreply@pandalla.ai",
|
||||
"pass": "kKgS3tNReRTL3RQC"
|
||||
}
|
||||
},
|
||||
"from": "Claude-Code-Remote Notification System <noreply@pandalla.ai>",
|
||||
"to": "jiaxicui446@gmail.com",
|
||||
"template": {
|
||||
"checkInterval": 30
|
||||
}
|
||||
}
|
||||
"enabled": true
|
||||
},
|
||||
"discord": {
|
||||
"type": "chat",
|
||||
|
|
|
|||
|
|
@ -8,24 +8,45 @@ require('dotenv').config();
|
|||
async function sendTestReply() {
|
||||
console.log('📧 Sending test email reply...\n');
|
||||
|
||||
// Create test SMTP transporter (using Gmail)
|
||||
// Create test SMTP transporter (using environment variables)
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||
port: parseInt(process.env.SMTP_PORT) || 587,
|
||||
secure: process.env.SMTP_SECURE === 'true',
|
||||
auth: {
|
||||
user: 'jiaxicui446@gmail.com',
|
||||
pass: process.env.GMAIL_APP_PASSWORD || 'your-app-password'
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS
|
||||
}
|
||||
});
|
||||
|
||||
// Use latest token
|
||||
const testToken = 'V5UPZ1UE'; // Latest token from session-map.json
|
||||
// Generate or use test token from environment
|
||||
let testToken = process.env.TEST_TOKEN;
|
||||
|
||||
if (!testToken) {
|
||||
// Try to read latest token from session map
|
||||
try {
|
||||
const sessionMapPath = process.env.SESSION_MAP_PATH || './src/data/session-map.json';
|
||||
if (require('fs').existsSync(sessionMapPath)) {
|
||||
const sessionMap = JSON.parse(require('fs').readFileSync(sessionMapPath, 'utf8'));
|
||||
const tokens = Object.keys(sessionMap);
|
||||
testToken = tokens[tokens.length - 1]; // Use latest token
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Could not read session map, using generated token');
|
||||
}
|
||||
|
||||
// Fallback: generate a test token
|
||||
if (!testToken) {
|
||||
testToken = Math.random().toString(36).substr(2, 8).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
const mailOptions = {
|
||||
from: 'jiaxicui446@gmail.com',
|
||||
to: 'noreply@pandalla.ai',
|
||||
from: process.env.SMTP_USER,
|
||||
to: process.env.SMTP_USER, // Self-send for testing
|
||||
subject: `Re: [Claude-Code-Remote #${testToken}] Claude Code Task Completed - Claude-Code-Remote`,
|
||||
text: 'Please explain the basic principles of quantum computing',
|
||||
replyTo: 'jiaxicui446@gmail.com'
|
||||
replyTo: process.env.EMAIL_TO || process.env.ALLOWED_SENDERS
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -58,9 +58,9 @@ class EmailChannel extends NotificationChannel {
|
|||
pass: this.config.smtp.auth.pass
|
||||
},
|
||||
// Add timeout settings
|
||||
connectionTimeout: 10000,
|
||||
greetingTimeout: 10000,
|
||||
socketTimeout: 10000
|
||||
connectionTimeout: parseInt(process.env.SMTP_TIMEOUT) || 10000,
|
||||
greetingTimeout: parseInt(process.env.SMTP_TIMEOUT) || 10000,
|
||||
socketTimeout: parseInt(process.env.SMTP_TIMEOUT) || 10000
|
||||
});
|
||||
|
||||
this.logger.debug('Email transporter initialized');
|
||||
|
|
|
|||
|
|
@ -44,12 +44,12 @@ class DesktopChannel extends NotificationChannel {
|
|||
// Try terminal-notifier first
|
||||
try {
|
||||
const cmd = `terminal-notifier -title "${title}" -message "${message}" -sound "${sound}" -group "claude-code-remote"`;
|
||||
execSync(cmd, { timeout: 3000 });
|
||||
execSync(cmd, { timeout: parseInt(process.env.NOTIFICATION_TIMEOUT) || 3000 });
|
||||
return true;
|
||||
} catch (e) {
|
||||
// Fallback to osascript
|
||||
const script = `display notification "${message}" with title "${title}"`;
|
||||
execSync(`osascript -e '${script}'`, { timeout: 3000 });
|
||||
execSync(`osascript -e '${script}'`, { timeout: parseInt(process.env.NOTIFICATION_TIMEOUT) || 3000 });
|
||||
|
||||
// Play sound separately
|
||||
this._playSound(sound);
|
||||
|
|
@ -63,7 +63,9 @@ class DesktopChannel extends NotificationChannel {
|
|||
|
||||
_sendLinux(title, message, sound) {
|
||||
try {
|
||||
execSync(`notify-send "${title}" "${message}" -t 10000`, { timeout: 3000 });
|
||||
const notificationTimeout = parseInt(process.env.NOTIFICATION_TIMEOUT) || 3000;
|
||||
const displayTime = parseInt(process.env.NOTIFICATION_DISPLAY_TIME) || 10000;
|
||||
execSync(`notify-send "${title}" "${message}" -t ${displayTime}`, { timeout: notificationTimeout });
|
||||
this._playSound(sound);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -58,19 +58,31 @@ class ConfigManager {
|
|||
},
|
||||
email: {
|
||||
type: 'email',
|
||||
enabled: false,
|
||||
enabled: process.env.SMTP_USER ? true : false,
|
||||
config: {
|
||||
smtp: {
|
||||
host: '',
|
||||
port: 587,
|
||||
secure: false,
|
||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||
port: parseInt(process.env.SMTP_PORT) || 587,
|
||||
secure: process.env.SMTP_SECURE === 'true',
|
||||
auth: {
|
||||
user: '',
|
||||
pass: ''
|
||||
user: process.env.SMTP_USER || '',
|
||||
pass: process.env.SMTP_PASS || ''
|
||||
}
|
||||
},
|
||||
from: '',
|
||||
to: []
|
||||
imap: {
|
||||
host: process.env.IMAP_HOST || 'imap.gmail.com',
|
||||
port: parseInt(process.env.IMAP_PORT) || 993,
|
||||
secure: process.env.IMAP_SECURE !== 'false',
|
||||
auth: {
|
||||
user: process.env.IMAP_USER || process.env.SMTP_USER || '',
|
||||
pass: process.env.IMAP_PASS || process.env.SMTP_PASS || ''
|
||||
}
|
||||
},
|
||||
from: process.env.EMAIL_FROM || `${process.env.EMAIL_FROM_NAME || 'Claude Code Remote'} <${process.env.SMTP_USER}>`,
|
||||
to: process.env.EMAIL_TO || '',
|
||||
template: {
|
||||
checkInterval: parseInt(process.env.CHECK_INTERVAL) || 30
|
||||
}
|
||||
}
|
||||
},
|
||||
discord: {
|
||||
|
|
@ -125,7 +137,7 @@ class ConfigManager {
|
|||
try {
|
||||
if (fs.existsSync(this.channelsConfigPath)) {
|
||||
const fileChannels = JSON.parse(fs.readFileSync(this.channelsConfigPath, 'utf8'));
|
||||
this._channels = { ...this._channels, ...fileChannels };
|
||||
this._channels = this._deepMerge(this._channels, fileChannels);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.warn('Failed to load channels config:', error.message);
|
||||
|
|
|
|||
|
|
@ -89,12 +89,11 @@ function isAllowed(fromAddress) {
|
|||
return ALLOWED_SENDERS.some(allowed => addr.includes(allowed));
|
||||
}
|
||||
|
||||
// Extract TaskPing token from subject
|
||||
// Extract Claude-Code-Remote token from subject
|
||||
function extractTokenFromSubject(subject = '') {
|
||||
const patterns = [
|
||||
/\[TaskPing\s+#([A-Za-z0-9_-]+)\]/,
|
||||
/\[TaskPing\s+([A-Za-z0-9_-]+)\]/,
|
||||
/TaskPing:\s*([A-Za-z0-9_-]+)/i
|
||||
/\[Claude-Code-Remote\s+#([A-Z0-9]+)\]/,
|
||||
/Re:\s*\[Claude-Code-Remote\s+#([A-Z0-9]+)\]/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
|
|
@ -118,7 +117,7 @@ function cleanEmailText(text = '') {
|
|||
line.includes('On') && line.includes('wrote:') ||
|
||||
line.includes('Session ID:') ||
|
||||
line.includes('Session ID:') ||
|
||||
line.includes('<noreply@pandalla.ai>') ||
|
||||
line.includes(`<${process.env.SMTP_USER}>`) ||
|
||||
line.includes('Claude-Code-Remote Notification System') ||
|
||||
line.includes('on 2025') && line.includes('wrote:') ||
|
||||
line.match(/^>.*/) || // Quote lines start with >
|
||||
|
|
@ -161,7 +160,7 @@ function cleanEmailText(text = '') {
|
|||
|
||||
// Skip remaining email quotes
|
||||
if (trimmedLine.includes('Claude-Code-Remote Notification System') ||
|
||||
trimmedLine.includes('<noreply@pandalla.ai>') ||
|
||||
trimmedLine.includes(`<${process.env.SMTP_USER}>`) ||
|
||||
trimmedLine.includes('on 2025')) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Test SMTP connection using environment variables
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
async function testSMTP() {
|
||||
console.log('🔧 Testing SMTP connection...\n');
|
||||
|
||||
const config = {
|
||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||
port: parseInt(process.env.SMTP_PORT) || 587,
|
||||
secure: process.env.SMTP_SECURE === 'true',
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS
|
||||
}
|
||||
};
|
||||
|
||||
console.log('📋 SMTP Configuration:');
|
||||
console.log(` Host: ${config.host}`);
|
||||
console.log(` Port: ${config.port}`);
|
||||
console.log(` Secure: ${config.secure}`);
|
||||
console.log(` User: ${config.auth.user}`);
|
||||
console.log(` Pass: ${'*'.repeat(config.auth.pass?.length || 0)}\n`);
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransport(config);
|
||||
|
||||
console.log('🔄 Verifying connection...');
|
||||
await transporter.verify();
|
||||
console.log('✅ SMTP connection successful!\n');
|
||||
|
||||
console.log('📧 Sending test email...');
|
||||
const info = await transporter.sendMail({
|
||||
from: process.env.EMAIL_FROM || `Claude Code Remote <${process.env.SMTP_USER}>`,
|
||||
to: process.env.EMAIL_TO,
|
||||
subject: 'Claude Code Remote - SMTP Test',
|
||||
text: 'This is a test email from Claude Code Remote. If you receive this, SMTP is working correctly!'
|
||||
});
|
||||
|
||||
console.log(`✅ Test email sent successfully!`);
|
||||
console.log(`📧 Message ID: ${info.messageId}`);
|
||||
console.log(`📬 To: ${process.env.EMAIL_TO}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ SMTP test failed:', error.message);
|
||||
|
||||
if (error.code === 'EAUTH') {
|
||||
console.error('\n💡 Authentication failed. Please check:');
|
||||
console.error(' - Gmail App Password is correct');
|
||||
console.error(' - Two-factor authentication is enabled');
|
||||
console.error(' - Email credentials in .env file');
|
||||
} else if (error.code === 'ETIMEDOUT') {
|
||||
console.error('\n💡 Connection timeout. Please check:');
|
||||
console.error(' - Internet connection');
|
||||
console.error(' - Firewall settings');
|
||||
console.error(' - SMTP host and port');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testSMTP().catch(console.error);
|
||||
Loading…
Reference in New Issue