Compare commits
10 Commits
b14f95d821
...
e91aeabdeb
| Author | SHA1 | Date |
|---|---|---|
|
|
e91aeabdeb | |
|
|
28d280d37a | |
|
|
f608aed3c2 | |
|
|
eafe594a66 | |
|
|
bd35dfdacf | |
|
|
41543eb4b9 | |
|
|
a74e11f3f7 | |
|
|
20c6b67a53 | |
|
|
bc7019691c | |
|
|
b85abd0480 |
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebFetch(domain:github.com)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +69,10 @@ TELEGRAM_BOT_TOKEN=your-telegram-bot-token
|
|||
# Telegram webhook URL(您的公開 HTTPS URL)
|
||||
# TELEGRAM_WEBHOOK_URL=https://your-domain.com
|
||||
|
||||
# 強制使用 IPv4 連接 Telegram API(預設:false)
|
||||
# 在某些網路環境下,IPv6 連接可能不穩定,設置為 true 可強制使用 IPv4
|
||||
# TELEGRAM_FORCE_IPV4=false
|
||||
|
||||
# ===== 系统配置 =====
|
||||
# 会话映射文件路径
|
||||
SESSION_MAP_PATH=/path/to/your/project/src/data/session-map.json
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
First of all, many thanks to everyone who wants to contribute to Claude-Code-Remote!
|
||||
|
||||
# Contributing to Claude Code Remote
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Fork, clone, and setup
|
||||
git clone https://github.com/YOUR_USERNAME/Claude-Code-Remote.git
|
||||
cd Claude-Code-Remote
|
||||
npm install
|
||||
cp .env.example .env
|
||||
|
||||
# Create feature branch
|
||||
git checkout -b feature/your-feature
|
||||
|
||||
# Test your changes
|
||||
npm run webhooks
|
||||
```
|
||||
|
||||
## 📝 Coding Standards (Automated Checks)
|
||||
|
||||
### 🚫 Strictly Forbidden (CI will auto-reject)
|
||||
```javascript
|
||||
// ❌ Hardcoded secrets/tokens
|
||||
const TELEGRAM_BOT_TOKEN = "123456789:ABC...";
|
||||
const LINE_CHANNEL_ACCESS_TOKEN = "abc123";
|
||||
const SMTP_PASS = "mypassword";
|
||||
|
||||
// ❌ Hardcoded API URLs
|
||||
const API_URL = "https://api.telegram.org/bot123456789";
|
||||
fetch("https://hooks.slack.com/abc123");
|
||||
|
||||
// ❌ console.log in production code
|
||||
console.log("Debug info");
|
||||
console.error("Error occurred");
|
||||
|
||||
// ❌ Async operations without error handling
|
||||
async function sendMessage() {
|
||||
await fetch(url); // No try-catch
|
||||
}
|
||||
|
||||
// ❌ String concatenation with user input
|
||||
const query = "SELECT * FROM users WHERE id=" + userId;
|
||||
```
|
||||
|
||||
### ✅ Required Standards (CI checks pass)
|
||||
```javascript
|
||||
// ✅ Use environment variables
|
||||
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
||||
const LINE_TOKEN = process.env.LINE_CHANNEL_ACCESS_TOKEN;
|
||||
const SMTP_PASS = process.env.SMTP_PASS;
|
||||
|
||||
// ✅ Use configuration files
|
||||
const config = require('./config.json');
|
||||
const API_URL = `${config.telegram.baseUrl}/bot${process.env.TELEGRAM_BOT_TOKEN}`;
|
||||
|
||||
// ✅ Use proper logging
|
||||
const logger = require('./src/core/logger');
|
||||
logger.info('Message sent successfully');
|
||||
logger.error('Failed to send message:', error);
|
||||
|
||||
// ✅ Proper error handling
|
||||
async function sendMessage(message) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ text: message })
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
logger.error('Send message failed:', error);
|
||||
throw error; // Re-throw for caller to handle
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Input validation and parameterized queries
|
||||
function validateUserId(userId) {
|
||||
if (!userId || typeof userId !== 'string') {
|
||||
throw new Error('Invalid user ID');
|
||||
}
|
||||
return userId.replace(/[^a-zA-Z0-9-]/g, '');
|
||||
}
|
||||
```
|
||||
|
||||
### 🔧 Enforcement Rules
|
||||
1. **Automated CI checks**: Every PR automatically checked for code quality
|
||||
2. **Hardcode detection**: Auto-scan all `.js` files for sensitive data
|
||||
3. **Log checking**: Prohibit `console.log` in production code
|
||||
4. **Error handling**: Check async functions for proper error handling
|
||||
|
||||
## 📛 Naming Conventions
|
||||
|
||||
### Issue Title Format
|
||||
```bash
|
||||
[BUG] Short clear description
|
||||
[FEATURE] Short clear description
|
||||
|
||||
Examples:
|
||||
[BUG] Telegram bot not responding to commands
|
||||
[FEATURE] Add Discord platform integration
|
||||
```
|
||||
|
||||
### PR Title Format
|
||||
```bash
|
||||
type(scope): description
|
||||
|
||||
Types: feat, fix, docs, style, refactor, perf, test, chore, ci
|
||||
Scopes: telegram, email, line, core, config, docs
|
||||
|
||||
Examples:
|
||||
feat(telegram): add inline keyboard support
|
||||
fix(email): resolve SMTP timeout issue #123
|
||||
docs: update installation instructions
|
||||
```
|
||||
|
||||
### Branch Naming
|
||||
```bash
|
||||
feature/discord-integration # New feature
|
||||
fix/issue-123 # Bug fix
|
||||
docs/update-readme # Documentation
|
||||
refactor/notification-system # Refactoring
|
||||
```
|
||||
|
||||
**Note**: Detailed naming rules are shown directly in issue/PR templates when you create them on GitHub.
|
||||
|
||||
## 🔄 Workflow
|
||||
|
||||
### Before PR
|
||||
1. Test all affected platforms
|
||||
2. Run security checks: `grep -r "TOKEN\|SECRET\|PASS" --include="*.js" src/`
|
||||
3. Ensure no console.log in production code
|
||||
4. Update docs if API changes
|
||||
|
||||
### Commit Format
|
||||
```bash
|
||||
feat(telegram): add inline keyboard support
|
||||
fix(email): resolve SMTP timeout issue #123
|
||||
docs: update installation instructions
|
||||
refactor(core): simplify notification logic
|
||||
chore: update dependencies
|
||||
```
|
||||
|
||||
## ✅ PR Checklist
|
||||
|
||||
- [ ] **No hardcoded values** (all config in .env or config files)
|
||||
- [ ] **No secrets in code** (tokens, passwords, keys)
|
||||
- [ ] **Input validation added** where needed
|
||||
- [ ] **Error handling implemented** (try/catch blocks)
|
||||
- [ ] **Tested locally** with tmux
|
||||
- [ ] **Tested affected platforms** (Email/Telegram/LINE)
|
||||
- [ ] **Code follows existing patterns**
|
||||
- [ ] **Updated documentation** if needed
|
||||
|
||||
## 🚨 Important Rules
|
||||
|
||||
1. **Never commit .env files**
|
||||
2. **Always validate external input**
|
||||
3. **Keep platform code isolated** in `src/channels/`
|
||||
4. **Follow existing patterns** - check similar code first
|
||||
5. **Test with tmux** before submitting
|
||||
|
||||
## 📞 Get Help
|
||||
|
||||
- Issues: [GitHub Issues](https://github.com/JessyTsui/Claude-Code-Remote/issues)
|
||||
- Twitter: [@Jiaxi_Cui](https://x.com/Jiaxi_Cui)
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Report something that is broken
|
||||
title: '[BUG] '
|
||||
labels: 'bug'
|
||||
---
|
||||
|
||||
<!--
|
||||
🏷️ ISSUE TITLE NAMING RULES:
|
||||
Format: [BUG] Short clear description of the problem
|
||||
|
||||
✅ GOOD EXAMPLES:
|
||||
- [BUG] Telegram bot not responding to commands
|
||||
- [BUG] Email notifications fail with SMTP timeout error
|
||||
- [BUG] LINE webhook returns 401 unauthorized
|
||||
- [BUG] Desktop notifications not showing on macOS
|
||||
- [BUG] Installation fails on Windows with Node 18
|
||||
- [BUG] tmux session detection not working
|
||||
- [BUG] Hook configuration file not found
|
||||
|
||||
❌ BAD EXAMPLES:
|
||||
- Bug report (no [BUG] prefix)
|
||||
- [BUG] It doesn't work (too vague)
|
||||
- Telegram issue (no [BUG] prefix, not descriptive)
|
||||
- [BUG] Problem (not descriptive enough)
|
||||
|
||||
📋 AVAILABLE ISSUE TYPES:
|
||||
1. 🐛 Bug Report (this template) - Report broken functionality
|
||||
2. ✨ Feature Request - Request new features
|
||||
3. ❓ Question - Ask usage questions
|
||||
4. 🔒 Security Report - Report security vulnerabilities
|
||||
5. ⚡ Performance Issue - Report performance problems
|
||||
6. 🔧 Enhancement - Suggest improvements to existing features
|
||||
7. 💬 Discussion - General discussions and brainstorming
|
||||
-->
|
||||
|
||||
## Bug Type (select one)
|
||||
- [ ] Installation issue
|
||||
- [ ] Platform not working (Email/Telegram/LINE)
|
||||
- [ ] Notification not received
|
||||
- [ ] Command injection failed
|
||||
- [ ] Configuration error
|
||||
|
||||
## What happened?
|
||||
<!-- Clear description -->
|
||||
|
||||
## Steps to reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Expected behavior
|
||||
<!-- What should happen? -->
|
||||
|
||||
## Environment
|
||||
- **Node version**:
|
||||
- **OS**:
|
||||
- **Platform**: Email / Telegram / LINE / All
|
||||
|
||||
## Error logs
|
||||
```
|
||||
paste error here
|
||||
```
|
||||
|
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: false
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
name: 💬 Discussion
|
||||
about: General discussions and brainstorming
|
||||
title: '[DISCUSSION] '
|
||||
labels: 'discussion'
|
||||
---
|
||||
|
||||
<!--
|
||||
🏷️ ISSUE TITLE NAMING RULES:
|
||||
Format: [DISCUSSION] Short description of the discussion topic
|
||||
|
||||
✅ GOOD EXAMPLES:
|
||||
- [DISCUSSION] Should we support WhatsApp integration?
|
||||
- [DISCUSSION] Best practices for notification rate limiting
|
||||
- [DISCUSSION] Architecture discussion for multi-tenant support
|
||||
- [DISCUSSION] Ideas for improving user onboarding
|
||||
- [DISCUSSION] Feedback on new configuration format
|
||||
- [DISCUSSION] Community guidelines for contributors
|
||||
- [DISCUSSION] Roadmap planning for next major version
|
||||
|
||||
❌ BAD EXAMPLES:
|
||||
- Discussion (no [DISCUSSION] prefix)
|
||||
- [DISCUSSION] Question (use [QUESTION] for specific questions)
|
||||
- Ideas (no [DISCUSSION] prefix)
|
||||
- [DISCUSSION] Help (use [QUESTION] for help requests)
|
||||
|
||||
📋 AVAILABLE ISSUE TYPES:
|
||||
1. 🐛 Bug Report - Report broken functionality
|
||||
2. ✨ Feature Request - Request new features
|
||||
3. ❓ Question - Ask usage questions
|
||||
4. 🔒 Security Report - Report security vulnerabilities
|
||||
5. ⚡ Performance Issue - Report performance problems
|
||||
6. 🔧 Enhancement - Suggest improvements to existing features
|
||||
7. 💬 Discussion (this template) - General discussions and brainstorming
|
||||
-->
|
||||
|
||||
## Discussion Topic
|
||||
<!-- What would you like to discuss? -->
|
||||
|
||||
## Context/Background
|
||||
<!-- Provide relevant background information -->
|
||||
|
||||
## Key Questions
|
||||
<!-- What specific questions should we consider? -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Potential Options/Ideas
|
||||
<!-- What are some possible approaches or solutions? -->
|
||||
- Option A:
|
||||
- Option B:
|
||||
- Option C:
|
||||
|
||||
## Impact/Considerations
|
||||
<!-- What should we keep in mind? -->
|
||||
- **Users**:
|
||||
- **Development**:
|
||||
- **Maintenance**:
|
||||
- **Performance**:
|
||||
|
||||
## Looking for
|
||||
- [ ] Community feedback and opinions
|
||||
- [ ] Technical input from maintainers
|
||||
- [ ] Ideas and suggestions
|
||||
- [ ] Help with decision making
|
||||
- [ ] Brainstorming session
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
name: 🔧 Enhancement
|
||||
about: Suggest improvements to existing features
|
||||
title: '[ENHANCEMENT] '
|
||||
labels: 'enhancement'
|
||||
---
|
||||
|
||||
<!--
|
||||
🏷️ ISSUE TITLE NAMING RULES:
|
||||
Format: [ENHANCEMENT] Short description of the enhancement
|
||||
|
||||
✅ GOOD EXAMPLES:
|
||||
- [ENHANCEMENT] Improve error messages for failed notifications
|
||||
- [ENHANCEMENT] Add configuration validation on startup
|
||||
- [ENHANCEMENT] Better logging for debugging command execution
|
||||
- [ENHANCEMENT] Improve Telegram bot command help text
|
||||
- [ENHANCEMENT] Add retry mechanism for failed email sends
|
||||
- [ENHANCEMENT] Better handling of network timeouts
|
||||
- [ENHANCEMENT] Improve notification formatting options
|
||||
|
||||
❌ BAD EXAMPLES:
|
||||
- Enhancement (no [ENHANCEMENT] prefix)
|
||||
- [ENHANCEMENT] Better (not specific enough)
|
||||
- Improve something (no [ENHANCEMENT] prefix)
|
||||
- [ENHANCEMENT] Fix (use [BUG] for fixes)
|
||||
|
||||
📋 AVAILABLE ISSUE TYPES:
|
||||
1. 🐛 Bug Report - Report broken functionality
|
||||
2. ✨ Feature Request - Request new features
|
||||
3. ❓ Question - Ask usage questions
|
||||
4. 🔒 Security Report - Report security vulnerabilities
|
||||
5. ⚡ Performance Issue - Report performance problems
|
||||
6. 🔧 Enhancement (this template) - Suggest improvements to existing features
|
||||
7. 💬 Discussion - General discussions and brainstorming
|
||||
-->
|
||||
|
||||
## What feature needs enhancement?
|
||||
<!-- Which existing feature should be improved? -->
|
||||
|
||||
## Current behavior
|
||||
<!-- How does it work now? -->
|
||||
|
||||
## Suggested improvement
|
||||
<!-- What should be enhanced and how? -->
|
||||
|
||||
## Why is this enhancement needed?
|
||||
<!-- What problem does this solve? -->
|
||||
|
||||
## Priority
|
||||
- [ ] Nice to have
|
||||
- [ ] Would improve user experience
|
||||
- [ ] Important for workflow
|
||||
- [ ] Critical improvement needed
|
||||
|
||||
## Implementation suggestions (optional)
|
||||
<!-- If you have ideas on how to implement this -->
|
||||
|
||||
## Environment
|
||||
- **Node version**:
|
||||
- **OS**:
|
||||
- **Platform**: Email / Telegram / LINE / All
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
name: ✨ Feature Request
|
||||
about: Suggest a new feature
|
||||
title: '[FEATURE] '
|
||||
labels: 'enhancement'
|
||||
---
|
||||
|
||||
<!--
|
||||
🏷️ ISSUE TITLE NAMING RULES:
|
||||
Format: [FEATURE] Short clear description of the feature
|
||||
|
||||
✅ GOOD EXAMPLES:
|
||||
- [FEATURE] Add Discord platform integration
|
||||
- [FEATURE] Auto-retry failed notifications
|
||||
- [FEATURE] Export command history to CSV
|
||||
- [FEATURE] Add support for Slack webhooks
|
||||
- [FEATURE] Implement notification scheduling
|
||||
- [FEATURE] Add multi-language support
|
||||
- [FEATURE] Command rate limiting
|
||||
- [FEATURE] Notification templates customization
|
||||
|
||||
❌ BAD EXAMPLES:
|
||||
- Feature request (no [FEATURE] prefix)
|
||||
- [FEATURE] New feature (too vague)
|
||||
- Discord support (no [FEATURE] prefix)
|
||||
- [FEATURE] Improvement (not specific enough)
|
||||
|
||||
📋 AVAILABLE ISSUE TYPES:
|
||||
1. 🐛 Bug Report - Report broken functionality
|
||||
2. ✨ Feature Request (this template) - Request new features
|
||||
3. ❓ Question - Ask usage questions
|
||||
4. 🔒 Security Report - Report security vulnerabilities
|
||||
5. ⚡ Performance Issue - Report performance problems
|
||||
6. 🔧 Enhancement - Suggest improvements to existing features
|
||||
7. 💬 Discussion - General discussions and brainstorming
|
||||
-->
|
||||
|
||||
## Feature Type (select one)
|
||||
- [ ] New platform integration (Discord/Slack/WhatsApp)
|
||||
- [ ] Notification enhancement
|
||||
- [ ] Command/control improvement
|
||||
- [ ] Performance optimization
|
||||
- [ ] Security enhancement
|
||||
|
||||
## What feature do you want?
|
||||
<!-- Clear description -->
|
||||
|
||||
## Why do you need this?
|
||||
<!-- What problem does it solve? -->
|
||||
|
||||
## How should it work?
|
||||
<!-- Describe the solution -->
|
||||
|
||||
## Priority
|
||||
- [ ] Nice to have
|
||||
- [ ] Important
|
||||
- [ ] Critical for my workflow
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: ⚡ Performance Issue
|
||||
about: Report performance problems
|
||||
title: '[PERFORMANCE] '
|
||||
labels: 'performance'
|
||||
---
|
||||
|
||||
<!--
|
||||
🏷️ ISSUE TITLE NAMING RULES:
|
||||
Format: [PERFORMANCE] Short description of the performance issue
|
||||
|
||||
✅ GOOD EXAMPLES:
|
||||
- [PERFORMANCE] Email notifications take too long to send
|
||||
- [PERFORMANCE] High memory usage during command execution
|
||||
- [PERFORMANCE] Telegram bot response time over 5 seconds
|
||||
- [PERFORMANCE] Application startup takes too long
|
||||
- [PERFORMANCE] CPU usage spikes during notification processing
|
||||
- [PERFORMANCE] Database queries running slowly
|
||||
- [PERFORMANCE] Large file uploads causing timeout
|
||||
|
||||
❌ BAD EXAMPLES:
|
||||
- Performance issue (no [PERFORMANCE] prefix)
|
||||
- [PERFORMANCE] Slow (not specific enough)
|
||||
- App is slow (no [PERFORMANCE] prefix)
|
||||
- [PERFORMANCE] Problem (too vague)
|
||||
|
||||
📋 AVAILABLE ISSUE TYPES:
|
||||
1. 🐛 Bug Report - Report broken functionality
|
||||
2. ✨ Feature Request - Request new features
|
||||
3. ❓ Question - Ask usage questions
|
||||
4. 🔒 Security Report - Report security vulnerabilities
|
||||
5. ⚡ Performance Issue (this template) - Report performance problems
|
||||
6. 🔧 Enhancement - Suggest improvements to existing features
|
||||
7. 💬 Discussion - General discussions and brainstorming
|
||||
-->
|
||||
|
||||
## Performance Issue Type (select one)
|
||||
- [ ] Slow response time
|
||||
- [ ] High memory usage
|
||||
- [ ] High CPU usage
|
||||
- [ ] Long startup time
|
||||
- [ ] Database performance
|
||||
- [ ] Network latency
|
||||
- [ ] File I/O performance
|
||||
|
||||
## Current behavior
|
||||
<!-- What is happening now? Include metrics if available -->
|
||||
|
||||
## Expected performance
|
||||
<!-- What should the performance be? -->
|
||||
|
||||
## When does this occur?
|
||||
- [ ] Always
|
||||
- [ ] During high load
|
||||
- [ ] With specific commands
|
||||
- [ ] With large files/data
|
||||
- [ ] At startup
|
||||
- [ ] Other:
|
||||
|
||||
## Performance metrics (if available)
|
||||
- **Response time**:
|
||||
- **Memory usage**:
|
||||
- **CPU usage**:
|
||||
- **Load time**:
|
||||
|
||||
## Steps to reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Environment
|
||||
- **Node version**:
|
||||
- **OS**:
|
||||
- **Platform**: Email / Telegram / LINE / All
|
||||
- **System specs**:
|
||||
|
||||
## Additional context
|
||||
<!-- Any other information about the performance issue -->
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
name: ❓ Question
|
||||
about: Ask usage questions
|
||||
title: '[QUESTION] '
|
||||
labels: 'question'
|
||||
---
|
||||
|
||||
<!--
|
||||
🏷️ ISSUE TITLE NAMING RULES:
|
||||
Format: [QUESTION] Short description of your question
|
||||
|
||||
✅ GOOD EXAMPLES:
|
||||
- [QUESTION] How to configure Telegram bot with custom commands?
|
||||
- [QUESTION] Can I use multiple email accounts for notifications?
|
||||
- [QUESTION] How to set up LINE webhook for notifications?
|
||||
- [QUESTION] What Node.js versions are supported?
|
||||
- [QUESTION] How to troubleshoot failed email notifications?
|
||||
- [QUESTION] Can notifications be scheduled for specific times?
|
||||
- [QUESTION] How to configure environment variables on Windows?
|
||||
|
||||
❌ BAD EXAMPLES:
|
||||
- Question (no [QUESTION] prefix)
|
||||
- [QUESTION] Help (not specific enough)
|
||||
- How to setup (no [QUESTION] prefix)
|
||||
- [QUESTION] Problem (use [BUG] for problems)
|
||||
|
||||
📋 AVAILABLE ISSUE TYPES:
|
||||
1. 🐛 Bug Report - Report broken functionality
|
||||
2. ✨ Feature Request - Request new features
|
||||
3. ❓ Question (this template) - Ask usage questions
|
||||
4. 🔒 Security Report - Report security vulnerabilities
|
||||
5. ⚡ Performance Issue - Report performance problems
|
||||
6. 🔧 Enhancement - Suggest improvements to existing features
|
||||
7. 💬 Discussion - General discussions and brainstorming
|
||||
-->
|
||||
|
||||
## Question Category (select one)
|
||||
- [ ] Installation and setup
|
||||
- [ ] Configuration
|
||||
- [ ] Platform integration (Email/Telegram/LINE)
|
||||
- [ ] Command usage
|
||||
- [ ] Troubleshooting
|
||||
- [ ] Best practices
|
||||
|
||||
## Your Question
|
||||
<!-- What do you want to know? -->
|
||||
|
||||
## What have you tried?
|
||||
<!-- What steps have you already taken? -->
|
||||
|
||||
## Context
|
||||
<!-- Provide relevant details about your setup -->
|
||||
|
||||
## Environment (if relevant)
|
||||
- **Node version**:
|
||||
- **OS**:
|
||||
- **Platform**: Email / Telegram / LINE / All
|
||||
|
||||
## Additional Information
|
||||
<!-- Any other relevant information -->
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
name: 🔒 Security Report
|
||||
about: Report a security vulnerability
|
||||
title: '[SECURITY] '
|
||||
labels: 'security'
|
||||
---
|
||||
|
||||
<!--
|
||||
🏷️ ISSUE TITLE NAMING RULES:
|
||||
Format: [SECURITY] Short description of the security issue
|
||||
|
||||
✅ GOOD EXAMPLES:
|
||||
- [SECURITY] Hardcoded credentials in config file
|
||||
- [SECURITY] Command injection vulnerability in email handler
|
||||
- [SECURITY] Exposed API keys in environment variables
|
||||
- [SECURITY] Unauthorized access to notification settings
|
||||
- [SECURITY] XSS vulnerability in notification content
|
||||
- [SECURITY] Path traversal in file upload feature
|
||||
- [SECURITY] SQL injection in database queries
|
||||
|
||||
❌ BAD EXAMPLES:
|
||||
- Security issue (no [SECURITY] prefix)
|
||||
- [SECURITY] Problem (not descriptive enough)
|
||||
- Vulnerability (no [SECURITY] prefix)
|
||||
- [SECURITY] Bug (too vague)
|
||||
|
||||
📋 AVAILABLE ISSUE TYPES:
|
||||
1. 🐛 Bug Report - Report broken functionality
|
||||
2. ✨ Feature Request - Request new features
|
||||
3. ❓ Question - Ask usage questions
|
||||
4. 🔒 Security Report (this template) - Report security vulnerabilities
|
||||
5. ⚡ Performance Issue - Report performance problems
|
||||
6. 🔧 Enhancement - Suggest improvements to existing features
|
||||
7. 💬 Discussion - General discussions and brainstorming
|
||||
-->
|
||||
|
||||
## Severity Level (select one)
|
||||
- [ ] 🔴 Critical - Immediate action required
|
||||
- [ ] 🟠 High - Should be fixed soon
|
||||
- [ ] 🟡 Medium - Should be addressed
|
||||
- [ ] 🟢 Low - Minor security concern
|
||||
|
||||
## Vulnerability Type (select one)
|
||||
- [ ] Authentication/Authorization
|
||||
- [ ] Code injection (Command/SQL/XSS)
|
||||
- [ ] Data exposure/leak
|
||||
- [ ] Hardcoded secrets/credentials
|
||||
- [ ] Input validation
|
||||
- [ ] Path traversal
|
||||
- [ ] Other
|
||||
|
||||
## Description
|
||||
<!-- Clear description of the security issue -->
|
||||
|
||||
## Steps to reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Impact
|
||||
<!-- What could an attacker achieve? -->
|
||||
|
||||
## Suggested fix
|
||||
<!-- If you have suggestions for fixing this -->
|
||||
|
||||
## Environment
|
||||
- **Node version**:
|
||||
- **OS**:
|
||||
- **Platform**: Email / Telegram / LINE / All
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<!--
|
||||
🏷️ PR TITLE NAMING RULES:
|
||||
Format: type(scope): description
|
||||
|
||||
📋 AVAILABLE PR TYPES (choose one for title):
|
||||
- feat: New feature or enhancement
|
||||
- fix: Bug fix
|
||||
- docs: Documentation only changes
|
||||
- style: Code formatting, whitespace, semicolons
|
||||
- refactor: Code refactoring (no functionality change)
|
||||
- perf: Performance improvements
|
||||
- test: Adding or updating tests
|
||||
- chore: Maintenance, dependencies, build tools
|
||||
- ci: CI/CD configuration changes
|
||||
|
||||
📍 SCOPES (optional but recommended):
|
||||
- telegram: Telegram platform
|
||||
- email: Email platform
|
||||
- line: LINE platform
|
||||
- discord: Discord platform
|
||||
- core: Core functionality
|
||||
- config: Configuration files
|
||||
- docs: Documentation
|
||||
|
||||
✅ GOOD PR TITLE EXAMPLES:
|
||||
- feat(telegram): add inline keyboard support
|
||||
- fix(email): resolve SMTP timeout issue #123
|
||||
- feat(discord): add Discord platform integration
|
||||
- docs: update installation instructions
|
||||
- refactor(core): simplify notification logic
|
||||
- perf(telegram): optimize message sending speed
|
||||
- fix(line): handle webhook authentication error
|
||||
- chore: update dependencies to latest versions
|
||||
- style(core): fix code formatting and indentation
|
||||
- test(email): add unit tests for SMTP connection
|
||||
- ci: add automated security scanning
|
||||
|
||||
❌ BAD PR TITLE EXAMPLES:
|
||||
- Add feature (no type, no scope)
|
||||
- Fix bug (too vague, no scope)
|
||||
- Update code (not descriptive)
|
||||
- telegram fix (wrong format)
|
||||
- New Discord support (missing type prefix)
|
||||
-->
|
||||
|
||||
## PR Type (REQUIRED: select at least one)
|
||||
- [ ] 🐛 **fix**: Bug fix (non-breaking change)
|
||||
- [ ] ✨ **feat**: New feature (non-breaking change)
|
||||
- [ ] 🔌 **feat**: Platform integration (new platform support)
|
||||
- [ ] 💥 **feat**: Breaking change (changes existing functionality)
|
||||
- [ ] 📚 **docs**: Documentation only changes
|
||||
- [ ] ♻️ **refactor**: Code refactoring (no functionality change)
|
||||
- [ ] ⚡ **perf**: Performance improvements
|
||||
- [ ] 🎨 **style**: Code formatting, whitespace, semicolons
|
||||
- [ ] 🔧 **chore**: Maintenance, dependencies, build tools
|
||||
- [ ] 🚨 **test**: Adding or updating tests
|
||||
- [ ] 🔄 **ci**: CI/CD configuration changes
|
||||
|
||||
## What does this PR do?
|
||||
<!-- Clear description -->
|
||||
|
||||
## Related Issue
|
||||
<!-- Fixes #123 or Closes #123 -->
|
||||
|
||||
## Code Quality Checklist (ALL REQUIRED)
|
||||
- [ ] **No hardcoded secrets** (use process.env.* or config files)
|
||||
- [ ] **No console.log** in production code (use logger.*)
|
||||
- [ ] **Error handling** implemented (try/catch blocks)
|
||||
- [ ] **Input validation** where needed
|
||||
- [ ] **Tested locally** with tmux
|
||||
|
||||
## Platform Testing
|
||||
- [ ] Email
|
||||
- [ ] Telegram
|
||||
- [ ] LINE
|
||||
- [ ] Desktop notifications
|
||||
|
||||
## How did you test this?
|
||||
1.
|
||||
2.
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Security audit
|
||||
run: npm audit --audit-level=moderate || true
|
||||
|
||||
- name: Validate JSON configs
|
||||
run: |
|
||||
echo "🔍 Validating JSON configuration files..."
|
||||
for file in $(find . -name "*.json" -not -path "./node_modules/*" -not -path "./.git/*"); do
|
||||
echo "Checking $file"
|
||||
if ! python3 -m json.tool "$file" > /dev/null 2>&1; then
|
||||
echo "❌ Invalid JSON: $file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "✅ All JSON files are valid"
|
||||
|
||||
- name: Check tmux availability
|
||||
run: |
|
||||
if command -v tmux &> /dev/null; then
|
||||
echo "✅ tmux is available: $(tmux -V)"
|
||||
else
|
||||
echo "Installing tmux..."
|
||||
sudo apt-get update && sudo apt-get install -y tmux
|
||||
fi
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
name: Project Management
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, labeled]
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Weekly cleanup on Sunday
|
||||
|
||||
jobs:
|
||||
# Auto-label issues
|
||||
auto-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'issues'
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Auto-label based on content
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const item = context.payload.issue;
|
||||
const body = (item.body || '').toLowerCase();
|
||||
const title = (item.title || '').toLowerCase();
|
||||
const labels = [];
|
||||
|
||||
// Platform labels
|
||||
if (title.includes('telegram') || body.includes('telegram')) labels.push('platform:telegram');
|
||||
if (title.includes('email') || body.includes('email')) labels.push('platform:email');
|
||||
if (title.includes('line') || body.includes('line')) labels.push('platform:line');
|
||||
if (title.includes('discord') || body.includes('discord')) labels.push('platform:discord');
|
||||
|
||||
// Priority labels
|
||||
if (title.includes('critical') || body.includes('critical')) labels.push('priority:high');
|
||||
if (title.includes('urgent') || body.includes('urgent')) labels.push('priority:high');
|
||||
|
||||
// Type labels
|
||||
if (title.includes('[bug]')) labels.push('type:bug');
|
||||
if (title.includes('[feature]')) labels.push('type:enhancement');
|
||||
if (title.includes('[question]')) labels.push('type:question');
|
||||
if (title.includes('[security]')) labels.push('type:security');
|
||||
if (title.includes('[performance]')) labels.push('type:performance');
|
||||
if (title.includes('[enhancement]')) labels.push('type:enhancement');
|
||||
if (title.includes('[discussion]')) labels.push('type:discussion');
|
||||
|
||||
if (labels.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: item.number,
|
||||
labels: labels
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
# Weekly maintenance
|
||||
weekly-cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'schedule'
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Close stale issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
labels: 'question',
|
||||
sort: 'updated',
|
||||
direction: 'asc',
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
for (const issue of issues) {
|
||||
if (new Date(issue.updated_at) < thirtyDaysAgo) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: 'This question has been inactive for 30 days and will be closed. Feel free to reopen if you still need help.'
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: 'closed'
|
||||
});
|
||||
}
|
||||
}
|
||||
51
README.md
51
README.md
|
|
@ -25,8 +25,8 @@ Control [Claude Code](https://claude.ai/code) remotely via multiple messaging pl
|
|||
## ✨ Features
|
||||
|
||||
- **📧 Multiple Messaging Platforms**:
|
||||
- Email notifications with full execution trace and reply-to-send commands
|
||||
- Telegram Bot with interactive buttons and slash commands ✅ **NEW**
|
||||
- Email notifications with full execution trace and reply-to-send commands 
|
||||
- Telegram Bot with interactive buttons and slash commands 
|
||||
- LINE messaging with token-based commands
|
||||
- Desktop notifications with sound alerts
|
||||
- **🔄 Two-way Control**: Reply to messages or emails to send new commands
|
||||
|
|
@ -42,13 +42,13 @@ Control [Claude Code](https://claude.ai/code) remotely via multiple messaging pl
|
|||
## 📅 Changelog
|
||||
|
||||
### August 2025
|
||||
- **2025-08-02**: Add full execution trace to email notifications ([#14](https://github.com/JessyTsui/Claude-Code-Remote/pull/14))
|
||||
- **2025-08-01**: Enhanced Multi-Channel Notification System (by @laihenyi @JessyTsui)
|
||||
- **2025-08-02**: Add full execution trace to email notifications ([#14](https://github.com/JessyTsui/Claude-Code-Remote/pull/14) by [@vaclisinc](https://github.com/vaclisinc))
|
||||
- **2025-08-01**: Enhanced Multi-Channel Notification System ([#1](https://github.com/JessyTsui/Claude-Code-Remote/pull/1) by [@laihenyi](https://github.com/laihenyi) [@JessyTsui](https://github.com/JessyTsui))
|
||||
- ✅ **Telegram Integration Completed** - Interactive buttons, real-time commands, smart personal/group chat handling
|
||||
- ✅ **Multi-Channel Notifications** - Simultaneous delivery to Desktop, Telegram, Email, LINE
|
||||
- ✅ **Smart Sound Alerts** - Always-on audio feedback with customizable sounds
|
||||
- ✅ **Intelligent Session Management** - Auto-detection, real conversation content, 24-hour tokens
|
||||
- **2025-08-01**: Fix #9 #12: Add configuration to disable subagent notifications ([#10](https://github.com/JessyTsui/Claude-Code-Remote/pull/10))
|
||||
- **2025-08-01**: Fix #9 #12: Add configuration to disable subagent notifications ([#10](https://github.com/JessyTsui/Claude-Code-Remote/pull/10) by [@vaclisinc](https://github.com/vaclisinc))
|
||||
- **2025-08-01**: Implement terminal-style UI for email notifications ([#8](https://github.com/JessyTsui/Claude-Code-Remote/pull/8) by [@vaclisinc](https://github.com/vaclisinc))
|
||||
- **2025-08-01**: Fix working directory issue - enable claude-remote to run from any directory ([#7](https://github.com/JessyTsui/Claude-Code-Remote/pull/7) by [@vaclisinc](https://github.com/vaclisinc))
|
||||
|
||||
|
|
@ -141,6 +141,22 @@ TELEGRAM_WEBHOOK_URL=https://your-ngrok-url.app
|
|||
SESSION_MAP_PATH=/your/path/to/Claude-Code-Remote/src/data/session-map.json
|
||||
```
|
||||
|
||||
**Optional Telegram settings:**
|
||||
```env
|
||||
# Force IPv4 connections to Telegram API (default: false)
|
||||
# Enable this if you experience connectivity issues with IPv6
|
||||
TELEGRAM_FORCE_IPV4=true
|
||||
```
|
||||
|
||||
**Network Configuration Notes:**
|
||||
- **IPv4 vs IPv6**: Some network environments may have unstable IPv6 connectivity to Telegram's API servers
|
||||
- **When to use `TELEGRAM_FORCE_IPV4=true`**:
|
||||
- Connection timeouts or failures when sending messages
|
||||
- Inconsistent webhook delivery
|
||||
- Network environments that don't properly support IPv6
|
||||
- **Default behavior**: Uses system default (usually IPv6 when available, fallback to IPv4)
|
||||
- **Performance impact**: Minimal - only affects initial connection establishment
|
||||
|
||||
#### Option C: Configure LINE
|
||||
|
||||
**Required LINE settings:**
|
||||
|
|
@ -188,7 +204,26 @@ export CLAUDE_HOOKS_CONFIG=/your/path/to/Claude-Code-Remote/claude-hooks.json
|
|||
|
||||
> **Note**: Subagent notifications are disabled by default. To enable them, set `enableSubagentNotifications: true` in your config. See [Subagent Notifications Guide](./docs/SUBAGENT_NOTIFICATIONS.md) for details.
|
||||
|
||||
### 5. Start Services
|
||||
### 5. Start tmux Session with Claude Code
|
||||
|
||||
**IMPORTANT**: Claude Code Remote requires Claude to run in a tmux session for command injection to work.
|
||||
|
||||
```bash
|
||||
# Start a new tmux session
|
||||
tmux new-session -d -s claude-session
|
||||
|
||||
# Attach to the session
|
||||
tmux attach-session -t claude-session
|
||||
|
||||
# Inside tmux, start Claude Code with hooks enabled
|
||||
claude-code --config /path/to/your/claude/settings.json
|
||||
|
||||
# Detach from tmux (Ctrl+B, then D) to leave Claude running in background
|
||||
```
|
||||
|
||||
> **Note**: Make sure your `~/.claude/settings.json` or project-specific config includes the hooks configuration from Step 4.
|
||||
|
||||
### 6. Start Services
|
||||
|
||||
#### For All Platforms (Recommended)
|
||||
```bash
|
||||
|
|
@ -221,7 +256,7 @@ npm run line
|
|||
node start-line-webhook.js
|
||||
```
|
||||
|
||||
### 6. Test Your Setup
|
||||
### 7. Test Your Setup
|
||||
|
||||
**Quick Test:**
|
||||
```bash
|
||||
|
|
@ -436,4 +471,4 @@ MIT License - Feel free to use and modify!
|
|||
|
||||
⭐ **Star this repo** if it helps you code more efficiently!
|
||||
|
||||
> 💡 **Tip**: Enable multiple notification channels for redundancy - never miss a Claude completion again!
|
||||
> 💡 **Tip**: Enable multiple notification channels for redundancy - never miss a Claude completion again!
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node /Users/jessytsui/dev/Claude-Code-Remote/claude-hook-notify.js completed",
|
||||
"command": "node /Users/path/to/Claude-Code-Remote/claude-hook-notify.js completed",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
|
|
@ -18,11 +18,11 @@
|
|||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node /Users/jessytsui/dev/Claude-Code-Remote/claude-hook-notify.js waiting",
|
||||
"command": "node /Users/path/to/Claude-Code-Remote/claude-hook-notify.js waiting",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,32 @@
|
|||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"smtp": {
|
||||
"host": "smtp.gmail.com",
|
||||
"port": 465,
|
||||
"secure": true,
|
||||
"auth": {
|
||||
"user": "2322176165lsa@gmail.com",
|
||||
"pass": "frkxntpirpfrhqre"
|
||||
}
|
||||
},
|
||||
"imap": {
|
||||
"host": "imap.gmail.com",
|
||||
"port": 993,
|
||||
"secure": true,
|
||||
"auth": {
|
||||
"user": "2322176165lsa@gmail.com",
|
||||
"pass": "frkxntpirpfrhqre"
|
||||
}
|
||||
},
|
||||
"from": "2322176165lsa@gmail.com",
|
||||
"to": "2322176165@qq.com",
|
||||
"template": {
|
||||
"checkInterval": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
"discord": {
|
||||
"type": "chat",
|
||||
|
|
@ -24,6 +49,7 @@
|
|||
"botToken": "",
|
||||
"chatId": "",
|
||||
"groupId": "",
|
||||
"forceIPv4": false,
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const TelegramChannel = require('./src/channels/telegram/telegram');
|
|||
|
||||
class SmartMonitor {
|
||||
constructor() {
|
||||
this.sessionName = process.env.TMUX_SESSION || 'claude-real';
|
||||
this.sessionName = process.env.TMUX_SESSION || 'claude-session';
|
||||
this.lastOutput = '';
|
||||
this.processedResponses = new Set(); // 記錄已處理的回應
|
||||
this.checkInterval = 1000; // Check every 1 second
|
||||
|
|
@ -290,4 +290,4 @@ process.on('SIGTERM', () => {
|
|||
});
|
||||
|
||||
// Start monitoring
|
||||
monitor.start();
|
||||
monitor.start();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,18 @@ class TelegramChannel extends NotificationChannel {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate network options for axios requests
|
||||
* @returns {Object} Network options object
|
||||
*/
|
||||
_getNetworkOptions() {
|
||||
const options = {};
|
||||
if (this.config.forceIPv4) {
|
||||
options.family = 4;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
_generateToken() {
|
||||
// Generate short Token (uppercase letters + numbers, 8 digits)
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
|
@ -66,6 +78,105 @@ class TelegramChannel extends NotificationChannel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters for Telegram Markdown
|
||||
* @param {string} text - Text to escape
|
||||
* @returns {string} - Escaped text
|
||||
*/
|
||||
_escapeMarkdown(text) {
|
||||
if (!text) return '';
|
||||
// Minimal escaping to avoid message rejection
|
||||
// Over-escaping causes Telegram to reject the message
|
||||
return text
|
||||
.replace(/\*/g, '\\*') // Escape asterisks
|
||||
.replace(/_/g, '\\_') // Escape underscores
|
||||
.replace(/\[/g, '\\[') // Escape square brackets
|
||||
.replace(/\]/g, '\\]') // Escape square brackets
|
||||
.replace(/`/g, '\\`'); // Escape backticks
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a safe plain text version without markdown formatting
|
||||
* @param {string} text - Text to make safe
|
||||
* @returns {string} - Safe text
|
||||
*/
|
||||
_createSafeText(text) {
|
||||
if (!text) return '';
|
||||
// Remove problematic characters entirely to ensure message sends
|
||||
return text
|
||||
.replace(/[_*\[\]()~`>#+=|{}.!\\-]/g, '') // Remove special chars
|
||||
.replace(/\s+/g, ' ') // Collapse multiple spaces
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate message length including formatting
|
||||
* @param {string} message - Message to calculate
|
||||
* @returns {number} - Message length
|
||||
*/
|
||||
_calculateMessageLength(message) {
|
||||
return message.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split long text into chunks that fit Telegram limits
|
||||
* @param {string} text - Text to split
|
||||
* @param {number} maxLength - Maximum length per chunk
|
||||
* @returns {string[]} - Array of text chunks
|
||||
*/
|
||||
_splitTextIntoChunks(text, maxLength = 3000) {
|
||||
if (text.length <= maxLength) {
|
||||
return [text];
|
||||
}
|
||||
|
||||
const chunks = [];
|
||||
let currentChunk = '';
|
||||
const lines = text.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
// If adding this line would exceed the limit
|
||||
if (currentChunk.length + line.length + 1 > maxLength) {
|
||||
if (currentChunk) {
|
||||
chunks.push(currentChunk.trim());
|
||||
currentChunk = '';
|
||||
}
|
||||
|
||||
// If single line is too long, split it by words
|
||||
if (line.length > maxLength) {
|
||||
const words = line.split(' ');
|
||||
let wordChunk = '';
|
||||
|
||||
for (const word of words) {
|
||||
if (wordChunk.length + word.length + 1 > maxLength) {
|
||||
if (wordChunk) {
|
||||
chunks.push(wordChunk.trim());
|
||||
wordChunk = word;
|
||||
} else {
|
||||
// Single word is too long, truncate it
|
||||
chunks.push(word.substring(0, maxLength - 3) + '...');
|
||||
}
|
||||
} else {
|
||||
wordChunk += (wordChunk ? ' ' : '') + word;
|
||||
}
|
||||
}
|
||||
if (wordChunk) {
|
||||
currentChunk = wordChunk;
|
||||
}
|
||||
} else {
|
||||
currentChunk = line;
|
||||
}
|
||||
} else {
|
||||
currentChunk += (currentChunk ? '\n' : '') + line;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentChunk) {
|
||||
chunks.push(currentChunk.trim());
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
async _getBotUsername() {
|
||||
if (this.botUsername) {
|
||||
return this.botUsername;
|
||||
|
|
@ -73,7 +184,8 @@ class TelegramChannel extends NotificationChannel {
|
|||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/getMe`
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/getMe`,
|
||||
this._getNetworkOptions()
|
||||
);
|
||||
|
||||
if (response.data.ok && response.data.result.username) {
|
||||
|
|
@ -142,16 +254,118 @@ class TelegramChannel extends NotificationChannel {
|
|||
}
|
||||
};
|
||||
|
||||
// Check if we need to use multi-message sending based on original response length
|
||||
const fullResponse = notification.metadata?.claudeResponse || '';
|
||||
const shouldUseMultiMessage = fullResponse.length > 1500; // Lower threshold for better experience
|
||||
|
||||
if (shouldUseMultiMessage) {
|
||||
console.log(`[DEBUG] Using multi-message sending (original response: ${fullResponse.length} chars)`);
|
||||
const success = await this._sendMultipleMessages(notification, sessionId, token, chatId, buttons);
|
||||
if (!success) {
|
||||
// Clean up failed session
|
||||
await this._removeSession(sessionId);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Use single message sending
|
||||
console.log(`[DEBUG] Using single message sending (${messageText.length} chars)`);
|
||||
|
||||
try {
|
||||
// Log the message details for debugging
|
||||
console.log(`[DEBUG] =====================================================`);
|
||||
console.log(`[DEBUG] Sending Telegram message, length: ${messageText.length}`);
|
||||
console.log(`[DEBUG] Chat ID: ${chatId}`);
|
||||
console.log(`[DEBUG] Session ID: ${sessionId}`);
|
||||
console.log(`[DEBUG] Message preview:`, messageText.substring(0, 200) + '...');
|
||||
console.log(`[DEBUG] =====================================================`);
|
||||
|
||||
const response = await axios.post(
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`,
|
||||
requestData
|
||||
requestData,
|
||||
{
|
||||
...this._getNetworkOptions(),
|
||||
timeout: 15000 // 15 second timeout
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] ✅ Telegram message sent successfully!`);
|
||||
this.logger.info(`Telegram message sent successfully, Session: ${sessionId}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to send Telegram message:', error.response?.data || error.message);
|
||||
// Enhanced error logging
|
||||
const errorData = error.response?.data;
|
||||
const errorMessage = errorData?.description || error.message;
|
||||
const errorCode = errorData?.error_code;
|
||||
|
||||
console.log(`[DEBUG] ❌ Telegram send error occurred:`);
|
||||
console.log(`[DEBUG] Error Code: ${errorCode}`);
|
||||
console.log(`[DEBUG] Error Message: ${errorMessage}`);
|
||||
console.log(`[DEBUG] Full error response:`, JSON.stringify(errorData, null, 2));
|
||||
console.log(`[DEBUG] Original message length: ${messageText.length}`);
|
||||
|
||||
this.logger.error(`Failed to send Telegram message (${errorCode}): ${errorMessage}`);
|
||||
|
||||
// Try multiple fallback strategies
|
||||
console.log(`[DEBUG] Attempting fallback strategies...`);
|
||||
|
||||
// Strategy 1: Try without parse_mode (plain text)
|
||||
try {
|
||||
console.log(`[DEBUG] Trying Strategy 1: Plain text without markdown`);
|
||||
const plainTextMessage = this._generateMinimalMessage(notification, token,
|
||||
notification.type === 'completed' ? '✅' : '⏳',
|
||||
notification.type === 'completed' ? 'Completed' : 'Waiting');
|
||||
|
||||
await axios.post(
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`,
|
||||
{
|
||||
chat_id: chatId,
|
||||
text: plainTextMessage,
|
||||
reply_markup: {
|
||||
inline_keyboard: buttons
|
||||
}
|
||||
},
|
||||
{
|
||||
...this._getNetworkOptions(),
|
||||
timeout: 15000
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] ✅ Strategy 1 succeeded: Plain text message sent`);
|
||||
this.logger.info(`Telegram plain text fallback message sent successfully, Session: ${sessionId}`);
|
||||
return true;
|
||||
|
||||
} catch (fallbackError1) {
|
||||
console.log(`[DEBUG] ❌ Strategy 1 failed:`, fallbackError1.response?.data?.description || fallbackError1.message);
|
||||
|
||||
// Strategy 2: Try absolute minimal message without buttons
|
||||
try {
|
||||
console.log(`[DEBUG] Trying Strategy 2: Minimal message without buttons`);
|
||||
const minimalMessage = `Claude Task Ready\\nToken: ${token}`;
|
||||
|
||||
await axios.post(
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`,
|
||||
{
|
||||
chat_id: chatId,
|
||||
text: minimalMessage
|
||||
},
|
||||
{
|
||||
...this._getNetworkOptions(),
|
||||
timeout: 15000
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] ✅ Strategy 2 succeeded: Minimal message sent`);
|
||||
this.logger.info(`Telegram minimal fallback message sent successfully, Session: ${sessionId}`);
|
||||
return true;
|
||||
|
||||
} catch (fallbackError2) {
|
||||
console.log(`[DEBUG] ❌ Strategy 2 failed:`, fallbackError2.response?.data?.description || fallbackError2.message);
|
||||
console.log(`[DEBUG] ❌ All fallback strategies failed`);
|
||||
this.logger.error('All Telegram fallback strategies failed');
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up failed session
|
||||
await this._removeSession(sessionId);
|
||||
return false;
|
||||
|
|
@ -163,35 +377,315 @@ class TelegramChannel extends NotificationChannel {
|
|||
const emoji = type === 'completed' ? '✅' : '⏳';
|
||||
const status = type === 'completed' ? 'Completed' : 'Waiting for Input';
|
||||
|
||||
let messageText = `${emoji} *Claude Task ${status}*\n`;
|
||||
messageText += `*Project:* ${notification.project}\n`;
|
||||
messageText += `*Session Token:* \`${token}\`\n\n`;
|
||||
|
||||
if (notification.metadata) {
|
||||
if (notification.metadata.userQuestion) {
|
||||
messageText += `📝 *Your Question:*\n${notification.metadata.userQuestion.substring(0, 200)}`;
|
||||
if (notification.metadata.userQuestion.length > 200) {
|
||||
messageText += '...';
|
||||
}
|
||||
messageText += '\n\n';
|
||||
try {
|
||||
// Method 1: Try with minimal markdown formatting
|
||||
let messageText = this._generateFormattedMessage(notification, token, emoji, status);
|
||||
|
||||
if (messageText.length <= 4000) {
|
||||
console.log(`[DEBUG] Generated formatted message length: ${messageText.length}`);
|
||||
return messageText;
|
||||
}
|
||||
|
||||
if (notification.metadata.claudeResponse) {
|
||||
messageText += `🤖 *Claude Response:*\n${notification.metadata.claudeResponse.substring(0, 300)}`;
|
||||
if (notification.metadata.claudeResponse.length > 300) {
|
||||
messageText += '...';
|
||||
}
|
||||
messageText += '\n\n';
|
||||
// Method 2: If too long, try plain text version
|
||||
console.log(`[DEBUG] Formatted message too long (${messageText.length}), trying plain text`);
|
||||
messageText = this._generatePlainTextMessage(notification, token, emoji, status);
|
||||
|
||||
if (messageText.length <= 4000) {
|
||||
console.log(`[DEBUG] Generated plain text message length: ${messageText.length}`);
|
||||
return messageText;
|
||||
}
|
||||
|
||||
// Method 3: If still too long, use minimal fallback
|
||||
console.log(`[DEBUG] Plain text still too long (${messageText.length}), using minimal fallback`);
|
||||
return this._generateMinimalMessage(notification, token, emoji, status);
|
||||
|
||||
} catch (error) {
|
||||
console.log(`[DEBUG] Error generating message: ${error.message}, using safe fallback`);
|
||||
return this._generateMinimalMessage(notification, token, emoji, status);
|
||||
}
|
||||
}
|
||||
|
||||
_generateFormattedMessage(notification, token, emoji, status) {
|
||||
let messageText = `${emoji} *Claude Task ${status}*\n`;
|
||||
messageText += `*Project:* ${notification.project || 'Unknown'}\n`;
|
||||
messageText += `*Token:* \`${token}\`\n\n`;
|
||||
|
||||
// Add user question if available (limited length)
|
||||
if (notification.metadata?.userQuestion) {
|
||||
const question = notification.metadata.userQuestion.substring(0, 150);
|
||||
messageText += `📝 *Question:* ${this._escapeMarkdown(question)}${question.length < notification.metadata.userQuestion.length ? '...' : ''}\n\n`;
|
||||
}
|
||||
|
||||
messageText += `💬 *To send a new command:*\n`;
|
||||
messageText += `Reply with: \`/cmd ${token} <your command>\`\n`;
|
||||
messageText += `Example: \`/cmd ${token} Please analyze this code\``;
|
||||
|
||||
// Add Claude response if available (limited length)
|
||||
if (notification.metadata?.claudeResponse) {
|
||||
const maxResponseLength = 3000 - messageText.length - 200; // Reserve space for instructions
|
||||
let response = notification.metadata.claudeResponse;
|
||||
|
||||
if (response.length > maxResponseLength) {
|
||||
response = response.substring(0, maxResponseLength - 10) + '...';
|
||||
}
|
||||
|
||||
messageText += `🤖 *Response:*\n${this._escapeMarkdown(response)}\n\n`;
|
||||
}
|
||||
|
||||
messageText += `💬 Use: \`/cmd ${token} <command>\``;
|
||||
return messageText;
|
||||
}
|
||||
|
||||
_generatePlainTextMessage(notification, token, emoji, status) {
|
||||
let messageText = `${emoji} Claude Task ${status}\n`;
|
||||
messageText += `Project: ${notification.project || 'Unknown'}\n`;
|
||||
messageText += `Token: ${token}\n\n`;
|
||||
|
||||
// Add user question (plain text, limited)
|
||||
if (notification.metadata?.userQuestion) {
|
||||
const question = this._createSafeText(notification.metadata.userQuestion.substring(0, 150));
|
||||
messageText += `Question: ${question}${question.length < notification.metadata.userQuestion.length ? '...' : ''}\n\n`;
|
||||
}
|
||||
|
||||
// Add Claude response (plain text, limited)
|
||||
if (notification.metadata?.claudeResponse) {
|
||||
const maxResponseLength = 3000 - messageText.length - 100;
|
||||
let response = this._createSafeText(notification.metadata.claudeResponse);
|
||||
|
||||
if (response.length > maxResponseLength) {
|
||||
response = response.substring(0, maxResponseLength - 10) + '...';
|
||||
}
|
||||
|
||||
messageText += `Response: ${response}\n\n`;
|
||||
}
|
||||
|
||||
messageText += `Use: /cmd ${token} <command>`;
|
||||
return messageText;
|
||||
}
|
||||
|
||||
_generateMinimalMessage(notification, token, emoji, status) {
|
||||
return `${emoji} Claude Task ${status}\nToken: ${token}\nUse: /cmd ${token} <command>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send multiple messages for long responses
|
||||
* @param {Object} notification - Notification data
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {string} token - Session token
|
||||
* @param {string} chatId - Telegram chat ID
|
||||
* @param {Array} buttons - Inline keyboard buttons
|
||||
* @returns {Promise<boolean>} - Success status
|
||||
*/
|
||||
async _sendMultipleMessages(notification, sessionId, token, chatId, buttons) {
|
||||
const type = notification.type;
|
||||
const emoji = type === 'completed' ? '✅' : '⏳';
|
||||
const status = type === 'completed' ? 'Completed' : 'Waiting for Input';
|
||||
|
||||
try {
|
||||
const fullResponse = notification.metadata?.claudeResponse || '';
|
||||
const userQuestion = notification.metadata?.userQuestion || '';
|
||||
|
||||
console.log(`[DEBUG] Multi-message sending: Response length ${fullResponse.length} chars`);
|
||||
|
||||
// Split the response content into optimal chunks
|
||||
const responseChunks = this._splitTextIntoChunks(fullResponse, 3200); // Slightly smaller for better formatting
|
||||
const totalParts = Math.max(1, responseChunks.length);
|
||||
|
||||
console.log(`[DEBUG] Split response into ${totalParts} parts`);
|
||||
|
||||
// Create the first message with basic info
|
||||
let firstMessage = `${emoji} *Claude Task ${status}* \\[1/${totalParts}\\]\n`;
|
||||
firstMessage += `*Project:* ${this._escapeMarkdown(notification.project || 'Unknown')}\n`;
|
||||
firstMessage += `*Token:* \`${token}\`\n\n`;
|
||||
|
||||
if (userQuestion) {
|
||||
const question = userQuestion.substring(0, 150);
|
||||
firstMessage += `📝 *Question:* ${this._escapeMarkdown(question)}${question.length < userQuestion.length ? '...' : ''}\n\n`;
|
||||
}
|
||||
|
||||
// Add response header and first part
|
||||
firstMessage += `🤖 *Claude Response:*\n`;
|
||||
|
||||
// Calculate space for first response part
|
||||
const commandInstructions = `\n\n💬 *Commands:* \`/cmd ${token} <your command>\``;
|
||||
const baseLength = firstMessage.length + commandInstructions.length;
|
||||
const maxFirstResponseLength = 3800 - baseLength;
|
||||
|
||||
// Add first part of response
|
||||
let firstResponsePart = '';
|
||||
if (responseChunks.length > 0) {
|
||||
firstResponsePart = responseChunks[0];
|
||||
if (firstResponsePart.length > maxFirstResponseLength) {
|
||||
// Further truncate first part if needed
|
||||
firstResponsePart = firstResponsePart.substring(0, maxFirstResponseLength - 50) + '...';
|
||||
}
|
||||
}
|
||||
|
||||
firstMessage += this._escapeMarkdown(firstResponsePart);
|
||||
|
||||
// Add continuation indicator
|
||||
if (totalParts > 1) {
|
||||
firstMessage += `\n\n*\\[Continued in next message\\]*`;
|
||||
}
|
||||
|
||||
firstMessage += commandInstructions;
|
||||
|
||||
// Send the first message
|
||||
console.log(`[DEBUG] Sending first message (${firstMessage.length} chars)`);
|
||||
const firstMessageResult = await this._sendSingleMessage(chatId, firstMessage, buttons, sessionId, 'Markdown');
|
||||
|
||||
if (!firstMessageResult.success) {
|
||||
console.log(`[DEBUG] Failed to send first message, trying fallback`);
|
||||
// Try plain text fallback for first message
|
||||
const plainFirstMessage = firstMessage
|
||||
.replace(/\*([^*]+)\*/g, '$1') // Remove bold
|
||||
.replace(/\`([^`]+)\`/g, '$1') // Remove code
|
||||
.replace(/\\(.)/g, '$1'); // Remove escapes
|
||||
|
||||
const fallbackResult = await this._sendSingleMessage(chatId, plainFirstMessage, buttons, sessionId, null);
|
||||
if (!fallbackResult.success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const firstMessageId = firstMessageResult.messageId;
|
||||
let successfulParts = 1;
|
||||
|
||||
// Send remaining parts
|
||||
for (let i = 1; i < responseChunks.length; i++) {
|
||||
const partNumber = i + 1;
|
||||
|
||||
let continuationMessage = `*\\[Part ${partNumber}/${totalParts}\\]*\n\n`;
|
||||
continuationMessage += this._escapeMarkdown(responseChunks[i]);
|
||||
|
||||
console.log(`[DEBUG] Sending part ${partNumber}/${totalParts} (${continuationMessage.length} chars)`);
|
||||
|
||||
const success = await this._sendSingleMessage(
|
||||
chatId,
|
||||
continuationMessage,
|
||||
null, // No buttons for continuation messages
|
||||
sessionId,
|
||||
'Markdown',
|
||||
firstMessageId // Reply to first message to create thread
|
||||
);
|
||||
|
||||
if (success.success) {
|
||||
successfulParts++;
|
||||
} else {
|
||||
console.log(`[DEBUG] Failed to send part ${partNumber}/${totalParts}, trying plain text`);
|
||||
// Try plain text fallback
|
||||
const plainMessage = continuationMessage.replace(/\*([^*]+)\*/g, '$1').replace(/\\(.)/g, '$1');
|
||||
const fallbackSuccess = await this._sendSingleMessage(
|
||||
chatId, plainMessage, null, sessionId, null, firstMessageId
|
||||
);
|
||||
if (fallbackSuccess.success) {
|
||||
successfulParts++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add delay to prevent rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
}
|
||||
|
||||
console.log(`[DEBUG] Multi-message sending completed: ${successfulParts}/${totalParts} parts sent successfully`);
|
||||
|
||||
// Send a final summary if not all parts were sent
|
||||
if (successfulParts < totalParts) {
|
||||
const summaryMessage = `⚠️ *Message Status:* ${successfulParts}/${totalParts} parts sent successfully\n\nSome parts may have been skipped due to formatting issues\\.`;
|
||||
await this._sendSingleMessage(chatId, summaryMessage, null, sessionId, 'Markdown', firstMessageId);
|
||||
}
|
||||
|
||||
return successfulParts > 0; // Success if at least first part was sent
|
||||
|
||||
} catch (error) {
|
||||
console.log(`[DEBUG] Error in _sendMultipleMessages: ${error.message}`);
|
||||
console.log(`[DEBUG] Stack trace:`, error.stack);
|
||||
this.logger.error('Failed to send multiple messages:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a single message with enhanced error handling
|
||||
* @param {string} chatId - Telegram chat ID
|
||||
* @param {string} messageText - Message text
|
||||
* @param {Array} buttons - Inline keyboard buttons (optional)
|
||||
* @param {string} sessionId - Session ID for logging
|
||||
* @param {string} parseMode - Parse mode (optional)
|
||||
* @param {number} replyToMessageId - Reply to message ID (optional)
|
||||
* @returns {Promise<Object>} - {success: boolean, messageId: number}
|
||||
*/
|
||||
async _sendSingleMessage(chatId, messageText, buttons = null, sessionId = 'unknown', parseMode = null, replyToMessageId = null) {
|
||||
const requestData = {
|
||||
chat_id: chatId,
|
||||
text: messageText
|
||||
};
|
||||
|
||||
if (parseMode) {
|
||||
requestData.parse_mode = parseMode;
|
||||
}
|
||||
|
||||
if (buttons) {
|
||||
requestData.reply_markup = {
|
||||
inline_keyboard: buttons
|
||||
};
|
||||
}
|
||||
|
||||
if (replyToMessageId) {
|
||||
requestData.reply_to_message_id = replyToMessageId;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[DEBUG] Sending message (${messageText.length} chars) to chat ${chatId}`);
|
||||
|
||||
const response = await axios.post(
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`,
|
||||
requestData,
|
||||
{
|
||||
...this._getNetworkOptions(),
|
||||
timeout: 15000
|
||||
}
|
||||
);
|
||||
|
||||
const messageId = response.data.result.message_id;
|
||||
console.log(`[DEBUG] ✅ Message sent successfully, ID: ${messageId}`);
|
||||
|
||||
return { success: true, messageId: messageId };
|
||||
|
||||
} catch (error) {
|
||||
const errorData = error.response?.data;
|
||||
const errorMessage = errorData?.description || error.message;
|
||||
const errorCode = errorData?.error_code;
|
||||
|
||||
console.log(`[DEBUG] ❌ Failed to send message: ${errorCode} - ${errorMessage}`);
|
||||
|
||||
// Try fallback without formatting
|
||||
if (parseMode && errorCode === 400) {
|
||||
console.log(`[DEBUG] Retrying without parse mode...`);
|
||||
try {
|
||||
const fallbackData = { ...requestData };
|
||||
delete fallbackData.parse_mode;
|
||||
fallbackData.text = this._createSafeText(messageText);
|
||||
|
||||
const fallbackResponse = await axios.post(
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`,
|
||||
fallbackData,
|
||||
{
|
||||
...this._getNetworkOptions(),
|
||||
timeout: 15000
|
||||
}
|
||||
);
|
||||
|
||||
const fallbackMessageId = fallbackResponse.data.result.message_id;
|
||||
console.log(`[DEBUG] ✅ Fallback message sent successfully, ID: ${fallbackMessageId}`);
|
||||
|
||||
return { success: true, messageId: fallbackMessageId };
|
||||
|
||||
} catch (fallbackError) {
|
||||
console.log(`[DEBUG] ❌ Fallback also failed: ${fallbackError.response?.data?.description || fallbackError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false, messageId: null };
|
||||
}
|
||||
}
|
||||
|
||||
async _createSession(sessionId, notification, token) {
|
||||
const session = {
|
||||
id: sessionId,
|
||||
|
|
@ -229,4 +723,4 @@ class TelegramChannel extends NotificationChannel {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = TelegramChannel;
|
||||
module.exports = TelegramChannel;
|
||||
|
|
|
|||
|
|
@ -33,13 +33,25 @@ class TelegramWebhookHandler {
|
|||
_setupRoutes() {
|
||||
// Telegram webhook endpoint
|
||||
this.app.post('/webhook/telegram', this._handleWebhook.bind(this));
|
||||
|
||||
|
||||
// Health check endpoint
|
||||
this.app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', service: 'telegram-webhook' });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate network options for axios requests
|
||||
* @returns {Object} Network options object
|
||||
*/
|
||||
_getNetworkOptions() {
|
||||
const options = {};
|
||||
if (this.config.forceIPv4) {
|
||||
options.family = 4;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
async _handleWebhook(req, res) {
|
||||
try {
|
||||
const update = req.body;
|
||||
|
|
@ -226,7 +238,8 @@ class TelegramWebhookHandler {
|
|||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/getMe`
|
||||
`${this.apiBaseUrl}/bot${this.config.botToken}/getMe`,
|
||||
this._getNetworkOptions()
|
||||
);
|
||||
|
||||
if (response.data.ok && response.data.result.username) {
|
||||
|
|
@ -277,7 +290,8 @@ class TelegramWebhookHandler {
|
|||
chat_id: chatId,
|
||||
text: text,
|
||||
...options
|
||||
}
|
||||
},
|
||||
this._getNetworkOptions()
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to send message:', error.response?.data || error.message);
|
||||
|
|
@ -291,7 +305,8 @@ class TelegramWebhookHandler {
|
|||
{
|
||||
callback_query_id: callbackQueryId,
|
||||
text: text
|
||||
}
|
||||
},
|
||||
this._getNetworkOptions()
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to answer callback query:', error.response?.data || error.message);
|
||||
|
|
@ -305,9 +320,10 @@ class TelegramWebhookHandler {
|
|||
{
|
||||
url: webhookUrl,
|
||||
allowed_updates: ['message', 'callback_query']
|
||||
}
|
||||
},
|
||||
this._getNetworkOptions()
|
||||
);
|
||||
|
||||
|
||||
this.logger.info('Webhook set successfully:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
|
|
@ -323,4 +339,4 @@ class TelegramWebhookHandler {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = TelegramWebhookHandler;
|
||||
module.exports = TelegramWebhookHandler;
|
||||
|
|
|
|||
|
|
@ -96,10 +96,12 @@ class ConfigManager {
|
|||
},
|
||||
telegram: {
|
||||
type: 'chat',
|
||||
enabled: false,
|
||||
enabled: process.env.TELEGRAM_ENABLED === 'true',
|
||||
config: {
|
||||
token: '',
|
||||
chatId: ''
|
||||
botToken: process.env.TELEGRAM_BOT_TOKEN || '',
|
||||
chatId: process.env.TELEGRAM_CHAT_ID || '',
|
||||
groupId: process.env.TELEGRAM_GROUP_ID || '',
|
||||
forceIPv4: process.env.TELEGRAM_FORCE_IPV4 === 'true'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -538,5 +538,464 @@
|
|||
"sessionId": "10cd6e52-91a8-476a-af0a-1fe2c2929ab6",
|
||||
"tmuxSession": "claude-hook-test",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"M4769IDR": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756981849,
|
||||
"expiresAt": 1757068249,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "56e2eb19-9b1e-4b49-b3b3-f99149a9e244",
|
||||
"tmuxSession": "test-session",
|
||||
"description": "completed - Claude-Code-Remote-Test"
|
||||
},
|
||||
"UG0HP8QJ": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756982044,
|
||||
"expiresAt": 1757068444,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "925e4a47-174f-4b48-a666-12ca707c18d1",
|
||||
"tmuxSession": "claude-code-remote",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"OGKAMMS0": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756982145,
|
||||
"expiresAt": 1757068545,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "2cf5a9ef-eb34-4cf0-8118-a2471f557320",
|
||||
"tmuxSession": "0",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"IJYBTZ1M": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756982185,
|
||||
"expiresAt": 1757068585,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "6474c5f8-4234-417b-8149-060e6a091035",
|
||||
"tmuxSession": "0",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"PQECZXRL": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756983489,
|
||||
"expiresAt": 1757069889,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "1b68620f-7199-40fe-b6fa-e57b3f36e346",
|
||||
"tmuxSession": "claude-hook-test",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"BTEAWB0A": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756983654,
|
||||
"expiresAt": 1757070054,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "6a21e7b9-f95b-419f-8267-c7ab939ebb64",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"W2JST4BL": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756984158,
|
||||
"expiresAt": 1757070558,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "70221d98-958e-4084-91ca-971127620375",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"7YGTUNQI": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756984735,
|
||||
"expiresAt": 1757071135,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "b21eecfb-5566-4329-b7f6-e9ee5b8c5b87",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"JN9R6ORI": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756984808,
|
||||
"expiresAt": 1757071208,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "0eb2f38b-b5f3-4cd2-a096-fdbb50f6b4a3",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"G15979CU": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756984876,
|
||||
"expiresAt": 1757071276,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "05abff6e-1f11-4662-bede-9cc40dba095b",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"XO7ZYYCD": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756985082,
|
||||
"expiresAt": 1757071482,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "650de5dc-e5a5-406c-9ff0-e6b658b7639d",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"297WCHFD": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756985194,
|
||||
"expiresAt": 1757071594,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "e3cdc11d-2aef-4a0e-b8cc-0112889498a9",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"TUBTGR8Z": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756985273,
|
||||
"expiresAt": 1757071673,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "e7ca8ded-58ed-4a39-b676-d8fedef37e74",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"5YCUAO3D": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756985483,
|
||||
"expiresAt": 1757071883,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "9c7ab226-b2b3-4a52-b884-37ba050e71ac",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"2VCH8RD0": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756986019,
|
||||
"expiresAt": 1757072419,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "d5eebf40-762c-42f0-9a9e-6f38eb1e131f",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"3WMICU56": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756987089,
|
||||
"expiresAt": 1757073489,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "2a135963-ca49-425f-aa41-433154c4f70d",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"05BC2ZC2": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756987248,
|
||||
"expiresAt": 1757073648,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "84037cac-6d82-454e-ae12-66b59baf9d8f",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"M9SZFQ16": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756987334,
|
||||
"expiresAt": 1757073734,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "562fcfeb-8b0d-4ae2-87c5-6a6fafe7c53f",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"6G3CXZ8Y": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756987424,
|
||||
"expiresAt": 1757073824,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "3cf3c431-0aed-4097-a594-47144bf08a62",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"I5LFQWV0": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756987485,
|
||||
"expiresAt": 1757073885,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "ba716dd3-e2c2-4886-9e42-17c1b8d060ae",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"JLROEO9E": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756987634,
|
||||
"expiresAt": 1757074034,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "3db165f7-e6ea-4d47-82d0-c11f8e32127e",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"K38PPZ6B": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756988634,
|
||||
"expiresAt": 1757075034,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "379f517e-b533-47b8-a9f4-134565e03e6a",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"JFRFCCUN": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990126,
|
||||
"expiresAt": 1757076526,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "868c8e44-ed03-40bd-bfa1-05c62b852e52",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"TDO4OJHV": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990188,
|
||||
"expiresAt": 1757076588,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "10dfce8a-7602-4f0d-8c4f-d7292ba769cf",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"L1WV5FAM": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990266,
|
||||
"expiresAt": 1757076666,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "6f1193e8-da56-4dde-b898-e7e756a86b51",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"U9ERQKL6": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990314,
|
||||
"expiresAt": 1757076714,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "7e3faaa7-73a4-4cfc-a9b6-34994c7f847f",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"O6D5M0TE": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990348,
|
||||
"expiresAt": 1757076748,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "e31e9b64-a65b-4336-ad80-d465482842a6",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"A08Q192M": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990442,
|
||||
"expiresAt": 1757076842,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "51d7c124-9978-4598-ab91-988fd489918f",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"WANYF73Z": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990505,
|
||||
"expiresAt": 1757076905,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "281cf8ad-6e3b-4add-ba99-81d781b5da94",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"X6ILHKQN": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990584,
|
||||
"expiresAt": 1757076984,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "0e4ac6fa-8808-4a68-9ebe-b97537c596a9",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"JSJBJIU3": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756990621,
|
||||
"expiresAt": 1757077021,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "dbd1f477-0550-463b-b147-fcdf78720177",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"IS1F9FN9": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991169,
|
||||
"expiresAt": 1757077569,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "2fa9255d-eb9a-4b37-8852-8204d5730d42",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"CJRIHOH9": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991395,
|
||||
"expiresAt": 1757077795,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "c5d96613-00de-459c-a8f0-52f020d135fc",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"19318YCF": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991584,
|
||||
"expiresAt": 1757077984,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "ffba11b1-0aaf-4639-ba4f-6db27249ae22",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"KTP2NLKA": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991689,
|
||||
"expiresAt": 1757078089,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "c4b49e6a-c4ca-446a-b28c-09863478c9fc",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"3HB36PXA": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991822,
|
||||
"expiresAt": 1757078222,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "9a14ee0e-cf2d-44f5-bec8-548853fc97e1",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"WLR6CVMN": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991860,
|
||||
"expiresAt": 1757078260,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "22cd07f1-34c2-4f58-b470-d7e7d235a92b",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"634ZIJ55": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991894,
|
||||
"expiresAt": 1757078294,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "7016be36-709a-48c5-8f64-2589aae4e5e9",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"HG4Z82VG": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756991984,
|
||||
"expiresAt": 1757078384,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "c42a911c-2b47-4b41-8280-c62889ab2384",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"OIO9DPTN": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756992035,
|
||||
"expiresAt": 1757078435,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "e29d64f2-19f5-4e5b-92c8-6bd1b766bc0d",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"PWB2DEYR": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756992167,
|
||||
"expiresAt": 1757078567,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "197cd53a-4a64-42a9-8262-8e3b429f08b8",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"TJ5VIF92": {
|
||||
"type": "pty",
|
||||
"createdAt": 1756992584,
|
||||
"expiresAt": 1757078984,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "019ab1c3-bde2-4d00-b21e-23167fe52c16",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"J56R0D84": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757031172,
|
||||
"expiresAt": 1757117572,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "cb99ffc3-ee1f-44be-9438-666e9b7fcd54",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"2A26K43N": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757031544,
|
||||
"expiresAt": 1757117944,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "f930e41f-eb86-49d0-85ab-2fcc06aa27e9",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"30W8P8Y1": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757031609,
|
||||
"expiresAt": 1757118009,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "945a07d5-5403-433e-8f90-951e639e0c0c",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"ZNE082NM": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757031634,
|
||||
"expiresAt": 1757118034,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "c30b0f1f-1d4f-438f-a7c8-81fb9d8ddd7c",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"G73CZLO4": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757031693,
|
||||
"expiresAt": 1757118093,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "c4d9c060-83f4-4fc1-b83a-41e3045df66a",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"6GAO026F": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757031822,
|
||||
"expiresAt": 1757118222,
|
||||
"cwd": "/home/lsamc/develop/ecllipse",
|
||||
"sessionId": "6feee05d-cdf5-41ab-9eb7-3e2b39be97cd",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - ecllipse"
|
||||
},
|
||||
"0ZDCUMDH": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757031847,
|
||||
"expiresAt": 1757118247,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "6b330566-d096-4862-9afe-aa32e038e9e9",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"RJMOLNH2": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757032064,
|
||||
"expiresAt": 1757118464,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "f394b1b0-9ea2-4106-b27a-75842319ed31",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
},
|
||||
"E1E1RJD2": {
|
||||
"type": "pty",
|
||||
"createdAt": 1757032743,
|
||||
"expiresAt": 1757119143,
|
||||
"cwd": "/home/lsamc/.local/src/Claude-Code-Remote",
|
||||
"sessionId": "f0acac1d-9b96-4baa-8622-cb030c300767",
|
||||
"tmuxSession": "claude-session",
|
||||
"description": "completed - Claude-Code-Remote"
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ const Logger = require('../core/logger');
|
|||
class ControllerInjector {
|
||||
constructor(config = {}) {
|
||||
this.logger = new Logger('ControllerInjector');
|
||||
this.mode = config.mode || process.env.INJECTION_MODE || 'pty';
|
||||
this.mode = config.mode || process.env.INJECTION_MODE || 'tmux';
|
||||
this.defaultSession = config.defaultSession || process.env.TMUX_SESSION || 'claude-code';
|
||||
}
|
||||
|
||||
|
|
@ -107,4 +107,4 @@ class ControllerInjector {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = ControllerInjector;
|
||||
module.exports = ControllerInjector;
|
||||
|
|
|
|||
|
|
@ -609,92 +609,170 @@ class TmuxMonitor extends EventEmitter {
|
|||
extractConversation(text, sessionName = null) {
|
||||
const lines = text.split('\n');
|
||||
|
||||
console.log(`[DEBUG] extractConversation called for session: ${sessionName}`);
|
||||
console.log(`[DEBUG] Total lines in buffer: ${lines.length}`);
|
||||
console.log(`[DEBUG] Last 5 lines:`, lines.slice(-5));
|
||||
|
||||
// Save full buffer for debugging (first 50 and last 50 lines)
|
||||
if (lines.length > 10) {
|
||||
console.log(`[DEBUG] Buffer preview - First 10 lines:`, lines.slice(0, 10));
|
||||
console.log(`[DEBUG] Buffer preview - Last 10 lines:`, lines.slice(-10));
|
||||
}
|
||||
|
||||
let userQuestion = '';
|
||||
let claudeResponse = '';
|
||||
let responseLines = [];
|
||||
let inResponse = false;
|
||||
|
||||
// Find the most recent user question and Claude response
|
||||
let inUserInput = false;
|
||||
let userQuestionLines = [];
|
||||
let lastUserIndex = -1;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
// First pass: Find the most recent user input
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
// Detect user input (line starting with "> " followed by content)
|
||||
if (line.startsWith('> ') && line.length > 2) {
|
||||
userQuestionLines = [line.substring(2).trim()];
|
||||
inUserInput = true;
|
||||
inResponse = false; // Reset response capture
|
||||
responseLines = []; // Clear previous response
|
||||
|
||||
// Record user input timestamp if session name provided
|
||||
if (sessionName) {
|
||||
this.traceCapture.recordUserInput(sessionName);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Continue capturing multi-line user input
|
||||
if (inUserInput && !line.startsWith('⏺') && line.length > 0) {
|
||||
userQuestionLines.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// End of user input
|
||||
if (inUserInput && (line.startsWith('⏺') || line.length === 0)) {
|
||||
inUserInput = false;
|
||||
userQuestion = userQuestionLines.join(' ');
|
||||
}
|
||||
|
||||
// Detect Claude response (line starting with "⏺ " or other response indicators)
|
||||
if (line.startsWith('⏺ ') ||
|
||||
(inResponse && line.length > 0 &&
|
||||
!line.startsWith('╭') && !line.startsWith('│') && !line.startsWith('╰') &&
|
||||
!line.startsWith('> ') && !line.includes('? for shortcuts'))) {
|
||||
|
||||
if (line.startsWith('⏺ ')) {
|
||||
inResponse = true;
|
||||
responseLines = [line.substring(2).trim()]; // Remove "⏺ " prefix
|
||||
} else if (inResponse) {
|
||||
responseLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop capturing response when we hit another prompt or box boundary
|
||||
if (inResponse && (line.startsWith('╭') || line.startsWith('│ > ') || line.includes('? for shortcuts'))) {
|
||||
inResponse = false;
|
||||
lastUserIndex = i;
|
||||
userQuestion = line.substring(2).trim();
|
||||
console.log(`[DEBUG] Found user question at line ${i}: ${userQuestion}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we found a user question, look for the response after it
|
||||
if (lastUserIndex >= 0) {
|
||||
let foundResponseStart = false;
|
||||
|
||||
for (let i = lastUserIndex + 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
// Skip empty lines and system lines, but be more selective
|
||||
if (!line ||
|
||||
line.includes('? for shortcuts') ||
|
||||
line.match(/^[╭╰│─]+$/) ||
|
||||
line.startsWith('$') ||
|
||||
line.startsWith('#') ||
|
||||
line.match(/^\[.*\]$/) || // System messages in brackets
|
||||
line.includes('(esc to interrupt)') || // Claude Code interrupt message
|
||||
line.match(/^\+\s*Cascading/) || // Cascading status message
|
||||
line.match(/^_{3,}$/)) { // Multiple underscores (display artifacts)
|
||||
console.log(`[DEBUG] Skipping system line: ${line}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multiple patterns to detect Claude responses
|
||||
const isClaudeResponse =
|
||||
line.startsWith('⏺ ') || // Traditional marker
|
||||
line.startsWith('I\'ll ') || // Common Claude opening
|
||||
line.startsWith('I can ') ||
|
||||
line.startsWith('Let me ') ||
|
||||
line.startsWith('Here') ||
|
||||
line.startsWith('Sure') ||
|
||||
line.startsWith('Yes') ||
|
||||
line.startsWith('No') ||
|
||||
line.includes('```') || // Code blocks
|
||||
/^[A-Z][a-z]/.test(line); // Sentences starting with capital letter
|
||||
|
||||
if (isClaudeResponse && !foundResponseStart) {
|
||||
foundResponseStart = true;
|
||||
responseLines = [line.startsWith('⏺ ') ? line.substring(2).trim() : line];
|
||||
console.log(`[DEBUG] Found response start at line ${i}: ${line}`);
|
||||
} else if (foundResponseStart) {
|
||||
// Only stop for clear prompt indicators, not for special characters or formatting
|
||||
if (line.startsWith('> ') && line.length > 2) { // New user input
|
||||
console.log(`[DEBUG] Stopping response collection at new user input: ${line}`);
|
||||
break;
|
||||
}
|
||||
// Stop at command prompt box start (new interaction)
|
||||
if (line.startsWith('╭') && line.includes('─')) {
|
||||
console.log(`[DEBUG] Stopping response collection at prompt box: ${line}`);
|
||||
break;
|
||||
}
|
||||
// Continue collecting response, ignoring formatting artifacts
|
||||
responseLines.push(line);
|
||||
console.log(`[DEBUG] Added response line ${responseLines.length}: ${line.substring(0, 50)}...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Look for any Claude response pattern in a larger range
|
||||
if (responseLines.length === 0) {
|
||||
console.log(`[DEBUG] No response found after user input, trying fallback method`);
|
||||
const recentLines = lines.slice(-40); // Increased range
|
||||
|
||||
let inFallbackResponse = false;
|
||||
for (let i = 0; i < recentLines.length; i++) {
|
||||
const line = recentLines[i].trim();
|
||||
|
||||
// Skip problematic lines even in fallback
|
||||
if (!line ||
|
||||
line.includes('(esc to interrupt)') ||
|
||||
line.match(/^\+\s*Cascading/) ||
|
||||
line.match(/^_{3,}$/) ||
|
||||
line.includes('? for shortcuts')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for Claude response indicators
|
||||
if (line.startsWith('⏺ ') ||
|
||||
(line.length > 5 &&
|
||||
!line.startsWith('> ') &&
|
||||
!line.startsWith('$') &&
|
||||
!line.startsWith('#') &&
|
||||
!line.match(/^\[.*\]$/))) {
|
||||
|
||||
const cleanLine = line.startsWith('⏺ ') ? line.substring(2).trim() : line;
|
||||
responseLines.push(cleanLine);
|
||||
inFallbackResponse = true;
|
||||
console.log(`[DEBUG] Fallback found response line: ${cleanLine.substring(0, 50)}...`);
|
||||
} else if (inFallbackResponse && line.length > 0) {
|
||||
// Continue collecting if we're in a response and line isn't a clear break
|
||||
responseLines.push(line);
|
||||
console.log(`[DEBUG] Fallback continued response: ${line.substring(0, 50)}...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Super fallback: Get any meaningful content if still empty
|
||||
if (responseLines.length === 0) {
|
||||
console.log(`[DEBUG] Still no response, trying super fallback - scanning all meaningful lines`);
|
||||
const meaningfulLines = [];
|
||||
|
||||
for (let i = Math.max(0, lastUserIndex + 1); i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
// Collect any line that seems like content (not system/formatting)
|
||||
if (line.length > 5 &&
|
||||
!line.includes('? for shortcuts') &&
|
||||
!line.match(/^[╭╰│─\s]+$/) &&
|
||||
!line.includes('(esc to interrupt)') &&
|
||||
!line.match(/^\+\s*Cascading/) &&
|
||||
!line.match(/^_{3,}$/) &&
|
||||
!line.startsWith('> ') &&
|
||||
!line.startsWith('$') &&
|
||||
!line.startsWith('#')) {
|
||||
|
||||
meaningfulLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (meaningfulLines.length > 0) {
|
||||
responseLines = meaningfulLines;
|
||||
console.log(`[DEBUG] Super fallback collected ${meaningfulLines.length} meaningful lines`);
|
||||
}
|
||||
}
|
||||
|
||||
// Join response lines and clean up
|
||||
claudeResponse = responseLines.join('\n').trim();
|
||||
|
||||
// Remove box characters but preserve formatting
|
||||
claudeResponse = claudeResponse
|
||||
.replace(/[╭╰│]/g, '')
|
||||
.replace(/[╭╰]/g, '')
|
||||
.replace(/^\s*│\s*/gm, '')
|
||||
// Don't collapse multiple spaces - preserve code formatting
|
||||
// .replace(/\s+/g, ' ')
|
||||
.replace(/│\s*$/gm, '')
|
||||
.trim();
|
||||
|
||||
// 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) {
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith('> ') && line.length > 2) {
|
||||
userQuestion = line.substring(2).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`[DEBUG] Final extraction result:`);
|
||||
console.log(`[DEBUG] User Question: "${userQuestion}"`);
|
||||
console.log(`[DEBUG] Claude Response: "${claudeResponse.substring(0, 100)}${claudeResponse.length > 100 ? '...' : ''}"`);
|
||||
console.log(`[DEBUG] Response lines count: ${responseLines.length}`);
|
||||
|
||||
return {
|
||||
userQuestion: userQuestion || 'No user input',
|
||||
claudeResponse: claudeResponse || 'No Claude response'
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ async function testInjection() {
|
|||
}
|
||||
}
|
||||
|
||||
testInjection().catch(console.error); < /dev/null
|
||||
testInjection().catch(console.error);
|
||||
|
|
|
|||
Loading…
Reference in New Issue