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 URL)
|
||||||
# TELEGRAM_WEBHOOK_URL=https://your-domain.com
|
# 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
|
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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
49
README.md
49
README.md
|
|
@ -25,8 +25,8 @@ Control [Claude Code](https://claude.ai/code) remotely via multiple messaging pl
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- **📧 Multiple Messaging Platforms**:
|
- **📧 Multiple Messaging Platforms**:
|
||||||
- Email notifications with full execution trace and reply-to-send commands
|
- Email notifications with full execution trace and reply-to-send commands 
|
||||||
- Telegram Bot with interactive buttons and slash commands ✅ **NEW**
|
- Telegram Bot with interactive buttons and slash commands 
|
||||||
- LINE messaging with token-based commands
|
- LINE messaging with token-based commands
|
||||||
- Desktop notifications with sound alerts
|
- Desktop notifications with sound alerts
|
||||||
- **🔄 Two-way Control**: Reply to messages or emails to send new commands
|
- **🔄 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
|
## 📅 Changelog
|
||||||
|
|
||||||
### August 2025
|
### August 2025
|
||||||
- **2025-08-02**: Add full execution trace to email notifications ([#14](https://github.com/JessyTsui/Claude-Code-Remote/pull/14))
|
- **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 (by @laihenyi @JessyTsui)
|
- **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
|
- ✅ **Telegram Integration Completed** - Interactive buttons, real-time commands, smart personal/group chat handling
|
||||||
- ✅ **Multi-Channel Notifications** - Simultaneous delivery to Desktop, Telegram, Email, LINE
|
- ✅ **Multi-Channel Notifications** - Simultaneous delivery to Desktop, Telegram, Email, LINE
|
||||||
- ✅ **Smart Sound Alerts** - Always-on audio feedback with customizable sounds
|
- ✅ **Smart Sound Alerts** - Always-on audio feedback with customizable sounds
|
||||||
- ✅ **Intelligent Session Management** - Auto-detection, real conversation content, 24-hour tokens
|
- ✅ **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**: 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))
|
- **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
|
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
|
#### Option C: Configure LINE
|
||||||
|
|
||||||
**Required LINE settings:**
|
**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.
|
> **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)
|
#### For All Platforms (Recommended)
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -221,7 +256,7 @@ npm run line
|
||||||
node start-line-webhook.js
|
node start-line-webhook.js
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Test Your Setup
|
### 7. Test Your Setup
|
||||||
|
|
||||||
**Quick Test:**
|
**Quick Test:**
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"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
|
"timeout": 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"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
|
"timeout": 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,32 @@
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "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": {
|
"discord": {
|
||||||
"type": "chat",
|
"type": "chat",
|
||||||
|
|
@ -24,6 +49,7 @@
|
||||||
"botToken": "",
|
"botToken": "",
|
||||||
"chatId": "",
|
"chatId": "",
|
||||||
"groupId": "",
|
"groupId": "",
|
||||||
|
"forceIPv4": false,
|
||||||
"whitelist": []
|
"whitelist": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const TelegramChannel = require('./src/channels/telegram/telegram');
|
||||||
|
|
||||||
class SmartMonitor {
|
class SmartMonitor {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.sessionName = process.env.TMUX_SESSION || 'claude-real';
|
this.sessionName = process.env.TMUX_SESSION || 'claude-session';
|
||||||
this.lastOutput = '';
|
this.lastOutput = '';
|
||||||
this.processedResponses = new Set(); // 記錄已處理的回應
|
this.processedResponses = new Set(); // 記錄已處理的回應
|
||||||
this.checkInterval = 1000; // Check every 1 second
|
this.checkInterval = 1000; // Check every 1 second
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,18 @@ class TelegramChannel extends NotificationChannel {
|
||||||
return true;
|
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() {
|
_generateToken() {
|
||||||
// Generate short Token (uppercase letters + numbers, 8 digits)
|
// Generate short Token (uppercase letters + numbers, 8 digits)
|
||||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
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() {
|
async _getBotUsername() {
|
||||||
if (this.botUsername) {
|
if (this.botUsername) {
|
||||||
return this.botUsername;
|
return this.botUsername;
|
||||||
|
|
@ -73,7 +184,8 @@ class TelegramChannel extends NotificationChannel {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
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) {
|
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 {
|
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(
|
const response = await axios.post(
|
||||||
`${this.apiBaseUrl}/bot${this.config.botToken}/sendMessage`,
|
`${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}`);
|
this.logger.info(`Telegram message sent successfully, Session: ${sessionId}`);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} 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
|
// Clean up failed session
|
||||||
await this._removeSession(sessionId);
|
await this._removeSession(sessionId);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -163,35 +377,315 @@ class TelegramChannel extends NotificationChannel {
|
||||||
const emoji = type === 'completed' ? '✅' : '⏳';
|
const emoji = type === 'completed' ? '✅' : '⏳';
|
||||||
const status = type === 'completed' ? 'Completed' : 'Waiting for Input';
|
const status = type === 'completed' ? 'Completed' : 'Waiting for Input';
|
||||||
|
|
||||||
let messageText = `${emoji} *Claude Task ${status}*\n`;
|
try {
|
||||||
messageText += `*Project:* ${notification.project}\n`;
|
// Method 1: Try with minimal markdown formatting
|
||||||
messageText += `*Session Token:* \`${token}\`\n\n`;
|
let messageText = this._generateFormattedMessage(notification, token, emoji, status);
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notification.metadata.claudeResponse) {
|
|
||||||
messageText += `🤖 *Claude Response:*\n${notification.metadata.claudeResponse.substring(0, 300)}`;
|
|
||||||
if (notification.metadata.claudeResponse.length > 300) {
|
|
||||||
messageText += '...';
|
|
||||||
}
|
|
||||||
messageText += '\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\``;
|
|
||||||
|
|
||||||
|
if (messageText.length <= 4000) {
|
||||||
|
console.log(`[DEBUG] Generated formatted message length: ${messageText.length}`);
|
||||||
return messageText;
|
return messageText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
async _createSession(sessionId, notification, token) {
|
||||||
const session = {
|
const session = {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,18 @@ class TelegramWebhookHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
async _handleWebhook(req, res) {
|
||||||
try {
|
try {
|
||||||
const update = req.body;
|
const update = req.body;
|
||||||
|
|
@ -226,7 +238,8 @@ class TelegramWebhookHandler {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
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) {
|
if (response.data.ok && response.data.result.username) {
|
||||||
|
|
@ -277,7 +290,8 @@ class TelegramWebhookHandler {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
text: text,
|
text: text,
|
||||||
...options
|
...options
|
||||||
}
|
},
|
||||||
|
this._getNetworkOptions()
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to send message:', error.response?.data || error.message);
|
this.logger.error('Failed to send message:', error.response?.data || error.message);
|
||||||
|
|
@ -291,7 +305,8 @@ class TelegramWebhookHandler {
|
||||||
{
|
{
|
||||||
callback_query_id: callbackQueryId,
|
callback_query_id: callbackQueryId,
|
||||||
text: text
|
text: text
|
||||||
}
|
},
|
||||||
|
this._getNetworkOptions()
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to answer callback query:', error.response?.data || error.message);
|
this.logger.error('Failed to answer callback query:', error.response?.data || error.message);
|
||||||
|
|
@ -305,7 +320,8 @@ class TelegramWebhookHandler {
|
||||||
{
|
{
|
||||||
url: webhookUrl,
|
url: webhookUrl,
|
||||||
allowed_updates: ['message', 'callback_query']
|
allowed_updates: ['message', 'callback_query']
|
||||||
}
|
},
|
||||||
|
this._getNetworkOptions()
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info('Webhook set successfully:', response.data);
|
this.logger.info('Webhook set successfully:', response.data);
|
||||||
|
|
|
||||||
|
|
@ -96,10 +96,12 @@ class ConfigManager {
|
||||||
},
|
},
|
||||||
telegram: {
|
telegram: {
|
||||||
type: 'chat',
|
type: 'chat',
|
||||||
enabled: false,
|
enabled: process.env.TELEGRAM_ENABLED === 'true',
|
||||||
config: {
|
config: {
|
||||||
token: '',
|
botToken: process.env.TELEGRAM_BOT_TOKEN || '',
|
||||||
chatId: ''
|
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",
|
"sessionId": "10cd6e52-91a8-476a-af0a-1fe2c2929ab6",
|
||||||
"tmuxSession": "claude-hook-test",
|
"tmuxSession": "claude-hook-test",
|
||||||
"description": "completed - Claude-Code-Remote"
|
"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 {
|
class ControllerInjector {
|
||||||
constructor(config = {}) {
|
constructor(config = {}) {
|
||||||
this.logger = new Logger('ControllerInjector');
|
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';
|
this.defaultSession = config.defaultSession || process.env.TMUX_SESSION || 'claude-code';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -609,62 +609,152 @@ class TmuxMonitor extends EventEmitter {
|
||||||
extractConversation(text, sessionName = null) {
|
extractConversation(text, sessionName = null) {
|
||||||
const lines = text.split('\n');
|
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 userQuestion = '';
|
||||||
let claudeResponse = '';
|
let claudeResponse = '';
|
||||||
let responseLines = [];
|
let responseLines = [];
|
||||||
let inResponse = false;
|
let lastUserIndex = -1;
|
||||||
|
|
||||||
// Find the most recent user question and Claude response
|
// First pass: Find the most recent user input
|
||||||
let inUserInput = false;
|
for (let i = lines.length - 1; i >= 0; i--) {
|
||||||
let userQuestionLines = [];
|
const line = lines[i].trim();
|
||||||
|
if (line.startsWith('> ') && line.length > 2) {
|
||||||
|
lastUserIndex = i;
|
||||||
|
userQuestion = line.substring(2).trim();
|
||||||
|
console.log(`[DEBUG] Found user question at line ${i}: ${userQuestion}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
// 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();
|
const line = lines[i].trim();
|
||||||
|
|
||||||
// Detect user input (line starting with "> " followed by content)
|
// Skip empty lines and system lines, but be more selective
|
||||||
if (line.startsWith('> ') && line.length > 2) {
|
if (!line ||
|
||||||
userQuestionLines = [line.substring(2).trim()];
|
line.includes('? for shortcuts') ||
|
||||||
inUserInput = true;
|
line.match(/^[╭╰│─]+$/) ||
|
||||||
inResponse = false; // Reset response capture
|
line.startsWith('$') ||
|
||||||
responseLines = []; // Clear previous response
|
line.startsWith('#') ||
|
||||||
|
line.match(/^\[.*\]$/) || // System messages in brackets
|
||||||
// Record user input timestamp if session name provided
|
line.includes('(esc to interrupt)') || // Claude Code interrupt message
|
||||||
if (sessionName) {
|
line.match(/^\+\s*Cascading/) || // Cascading status message
|
||||||
this.traceCapture.recordUserInput(sessionName);
|
line.match(/^_{3,}$/)) { // Multiple underscores (display artifacts)
|
||||||
}
|
console.log(`[DEBUG] Skipping system line: ${line}`);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue capturing multi-line user input
|
// Multiple patterns to detect Claude responses
|
||||||
if (inUserInput && !line.startsWith('⏺') && line.length > 0) {
|
const isClaudeResponse =
|
||||||
userQuestionLines.push(line);
|
line.startsWith('⏺ ') || // Traditional marker
|
||||||
continue;
|
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)
|
||||||
// End of user input
|
if (line.startsWith('╭') && line.includes('─')) {
|
||||||
if (inUserInput && (line.startsWith('⏺') || line.length === 0)) {
|
console.log(`[DEBUG] Stopping response collection at prompt box: ${line}`);
|
||||||
inUserInput = false;
|
break;
|
||||||
userQuestion = userQuestionLines.join(' ');
|
|
||||||
}
|
}
|
||||||
|
// Continue collecting response, ignoring formatting artifacts
|
||||||
// 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);
|
responseLines.push(line);
|
||||||
|
console.log(`[DEBUG] Added response line ${responseLines.length}: ${line.substring(0, 50)}...`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop capturing response when we hit another prompt or box boundary
|
// Fallback: Look for any Claude response pattern in a larger range
|
||||||
if (inResponse && (line.startsWith('╭') || line.startsWith('│ > ') || line.includes('? for shortcuts'))) {
|
if (responseLines.length === 0) {
|
||||||
inResponse = false;
|
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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -673,27 +763,15 @@ class TmuxMonitor extends EventEmitter {
|
||||||
|
|
||||||
// Remove box characters but preserve formatting
|
// Remove box characters but preserve formatting
|
||||||
claudeResponse = claudeResponse
|
claudeResponse = claudeResponse
|
||||||
.replace(/[╭╰│]/g, '')
|
.replace(/[╭╰]/g, '')
|
||||||
.replace(/^\s*│\s*/gm, '')
|
.replace(/^\s*│\s*/gm, '')
|
||||||
// Don't collapse multiple spaces - preserve code formatting
|
.replace(/│\s*$/gm, '')
|
||||||
// .replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
// Don't limit response length - we want the full response
|
console.log(`[DEBUG] Final extraction result:`);
|
||||||
// if (claudeResponse.length > 500) {
|
console.log(`[DEBUG] User Question: "${userQuestion}"`);
|
||||||
// claudeResponse = claudeResponse.substring(0, 497) + '...';
|
console.log(`[DEBUG] Claude Response: "${claudeResponse.substring(0, 100)}${claudeResponse.length > 100 ? '...' : ''}"`);
|
||||||
// }
|
console.log(`[DEBUG] Response lines count: ${responseLines.length}`);
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userQuestion: userQuestion || 'No user input',
|
userQuestion: userQuestion || 'No user input',
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,4 @@ async function testInjection() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testInjection().catch(console.error); < /dev/null
|
testInjection().catch(console.error);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue