Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Control [Claude Code](https://claude.ai/code) remotely via email. Start tasks lo
## 📅 Changelog

### January 2025
- **2025-01-15**: Implement terminal-style UI for email notifications ([#8](https://github.com/JessyTsui/Claude-Code-Remote/pull/8) by [@vaclisinc](https://github.com/vaclisinc))
- **2025-01-15**: 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-01-14**: Fix self-reply loop issue when using same email for send/receive ([#4](https://github.com/JessyTsui/Claude-Code-Remote/pull/4) 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-07-31**: Fix self-reply loop issue when using same email for send/receive ([#4](https://github.com/JessyTsui/Claude-Code-Remote/pull/4) by [@vaclisinc](https://github.com/vaclisinc))

### July 2025
- **2025-07-28**: Remove hardcoded values and implement environment-based configuration ([#2](https://github.com/JessyTsui/Claude-Code-Remote/pull/2) by [@kevinsslin](https://github.com/kevinsslin))
Expand Down Expand Up @@ -99,6 +99,7 @@ SESSION_MAP_PATH=/your/absolute/path/to/Claude-Code-Remote/src/data/session-map.
```

📌 **Gmail users**: Create an [App Password](https://myaccount.google.com/security) instead of using your regular password.
> Note: You may need to enable two-step verification in your google account first before create app password.

### Step 3: Set Up Claude Code Hooks

Expand Down Expand Up @@ -137,6 +138,8 @@ Add this configuration (replace `/your/absolute/path/` with your actual path):
}
```

> **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.

### Step 4: Test Your Setup

```bash
Expand Down
56 changes: 56 additions & 0 deletions claude-remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,62 @@ class ClaudeCodeRemoteCLI {

// Automatically capture current tmux session conversation content
const metadata = await this.captureCurrentConversation();

// Handle subagent notifications
if (type === 'waiting') {
const Config = require('./src/core/config');
const config = new Config();
config.load();
const enableSubagentNotifications = config.get('enableSubagentNotifications', false);

if (!enableSubagentNotifications) {
// Instead of skipping, track the subagent activity
const SubagentTracker = require('./src/utils/subagent-tracker');
const tracker = new SubagentTracker();

// Use tmux session as the tracking key
const trackingKey = metadata.tmuxSession || 'default';

// Capture more detailed information about the subagent activity
const activityDetails = {
userQuestion: metadata.userQuestion || 'No question captured',
claudeResponse: metadata.claudeResponse || 'No response captured',
timestamp: new Date().toISOString(),
tmuxSession: metadata.tmuxSession
};

// Don't truncate the response too aggressively
if (activityDetails.claudeResponse && activityDetails.claudeResponse.length > 1000) {
activityDetails.claudeResponse = activityDetails.claudeResponse.substring(0, 1000) + '...[see full output in tmux]';
}

tracker.addActivity(trackingKey, {
type: 'SubagentStop',
description: metadata.userQuestion || 'Subagent task',
details: activityDetails
});

this.logger.info(`Subagent activity tracked for tmux session: ${trackingKey}`);
process.exit(0);
}
}

// For completed notifications, include subagent activities
if (type === 'completed') {
const SubagentTracker = require('./src/utils/subagent-tracker');
const tracker = new SubagentTracker();
const trackingKey = metadata.tmuxSession || 'default';

// Get and format subagent activities
const subagentSummary = tracker.formatActivitiesForEmail(trackingKey);
if (subagentSummary) {
metadata.subagentActivities = subagentSummary;
this.logger.info('Including subagent activities in completion email');

// Clear activities after including them
tracker.clearActivities(trackingKey);
}
}

const result = await this.notifier.notify(type, metadata);

Expand Down
2 changes: 2 additions & 0 deletions config/defaults/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"waiting": "Hero"
},
"enabled": true,
"enableSubagentNotifications": false,
"subagentActivityDetail": "medium",
"timeout": 5,
"customMessages": {
"completed": null,
Expand Down
23 changes: 21 additions & 2 deletions src/channels/email/smtp.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ class EmailChannel extends NotificationChannel {
this._initializeTransporter();
}

_escapeHtml(text) {
if (!text) return '';
const htmlEntities = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
};
return text.replace(/[&<>"']/g, char => htmlEntities[char]);
}

_ensureDirectories() {
if (!fs.existsSync(this.sessionsDir)) {
fs.mkdirSync(this.sessionsDir, { recursive: true });
Expand Down Expand Up @@ -286,7 +298,8 @@ class EmailChannel extends NotificationChannel {
userQuestion: userQuestion || 'No specified task',
claudeResponse: claudeResponse || notification.message,
projectDir: projectDir,
shortQuestion: shortQuestion || 'No specific question'
shortQuestion: shortQuestion || 'No specific question',
subagentActivities: notification.metadata?.subagentActivities || ''
};

let subject = enhancedSubject;
Expand All @@ -297,7 +310,9 @@ class EmailChannel extends NotificationChannel {
Object.keys(variables).forEach(key => {
const placeholder = new RegExp(`{{${key}}}`, 'g');
subject = subject.replace(placeholder, variables[key]);
html = html.replace(placeholder, variables[key]);
// Escape HTML entities for HTML content
html = html.replace(placeholder, this._escapeHtml(variables[key]));
// No escaping needed for plain text
text = text.replace(placeholder, variables[key]);
});

Expand Down Expand Up @@ -356,6 +371,8 @@ class EmailChannel extends NotificationChannel {
</div>
</div>

{{subagentActivities}}

<!-- Continue Instructions -->
<div style="margin: 30px 0 20px 0; border-top: 1px solid #333; padding-top: 20px;">
<span style="color: #999;">$</span> <span style="color: #00ff00;">claude-code help --continue</span><br>
Expand Down Expand Up @@ -398,6 +415,8 @@ Status: {{type}}
🤖 Claude's Response:
{{claudeResponse}}

{{subagentActivities}}

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

Expand Down
136 changes: 1 addition & 135 deletions src/data/sent-messages.json
Original file line number Diff line number Diff line change
@@ -1,137 +1,3 @@
{
"messages": [
{
"messageId": "<52d15aa1-d5a4-4d7d-8f01-4752d7d5fc6f-1754021037319@claude-code-remote>",
"sessionId": "52d15aa1-d5a4-4d7d-8f01-4752d7d5fc6f",
"token": "49WUF9NS",
"type": "notification",
"sentAt": "2025-08-01T04:03:59.850Z"
},
{
"messageId": "<eba8744e-8cc1-4fad-9dc8-69d558c51cca-1754021210179@claude-code-remote>",
"sessionId": "eba8744e-8cc1-4fad-9dc8-69d558c51cca",
"token": "N9PHUN4Q",
"type": "notification",
"sentAt": "2025-08-01T04:06:52.776Z"
},
{
"messageId": "<859daa99-1ea9-4c40-aa68-c3967a0d7e4e-1754021233658@claude-code-remote>",
"sessionId": "859daa99-1ea9-4c40-aa68-c3967a0d7e4e",
"token": "GXWFSL3S",
"type": "notification",
"sentAt": "2025-08-01T04:07:15.556Z"
},
{
"messageId": "<a1ed6757-6782-4b22-a486-aab5a9d60a3c-1754021267945@claude-code-remote>",
"sessionId": "a1ed6757-6782-4b22-a486-aab5a9d60a3c",
"token": "6EZXA6IN",
"type": "notification",
"sentAt": "2025-08-01T04:07:49.959Z"
},
{
"messageId": "<2122d57c-8434-44f0-b4e6-7eafb40ed49d-1754021285815@claude-code-remote>",
"sessionId": "2122d57c-8434-44f0-b4e6-7eafb40ed49d",
"token": "ZQY1UOIJ",
"type": "notification",
"sentAt": "2025-08-01T04:08:07.833Z"
},
{
"messageId": "<2b30b1f7-b9c3-4cb4-b889-11c58009bd07-1754021533703@claude-code-remote>",
"sessionId": "2b30b1f7-b9c3-4cb4-b889-11c58009bd07",
"token": "L4KQ8DVJ",
"type": "notification",
"sentAt": "2025-08-01T04:12:15.795Z"
},
{
"messageId": "<e3392712-9a5b-4c96-a23c-5d97c226b415-1754022640364@claude-code-remote>",
"sessionId": "e3392712-9a5b-4c96-a23c-5d97c226b415",
"token": "7ZFMSK5P",
"type": "notification",
"sentAt": "2025-08-01T04:30:41.978Z"
},
{
"messageId": "<5d19da23-5c9d-4855-824e-fbd09a0d986c-1754022731688@claude-code-remote>",
"sessionId": "5d19da23-5c9d-4855-824e-fbd09a0d986c",
"token": "PJOE746Q",
"type": "notification",
"sentAt": "2025-08-01T04:32:13.100Z"
},
{
"messageId": "<ed814c4b-0b97-4e10-8ad1-dae1a7cb7ef9-1754022927776@claude-code-remote>",
"sessionId": "ed814c4b-0b97-4e10-8ad1-dae1a7cb7ef9",
"token": "C9I24JHD",
"type": "notification",
"sentAt": "2025-08-01T04:35:28.987Z"
},
{
"messageId": "<46ae2185-2f70-4355-a3b8-9fc04fa01181-1754023256348@claude-code-remote>",
"sessionId": "46ae2185-2f70-4355-a3b8-9fc04fa01181",
"token": "GGBKJ2DV",
"type": "notification",
"sentAt": "2025-08-01T04:40:58.066Z"
},
{
"messageId": "<49c51c77-8ecc-4a04-a7a8-5955f89411b1-1754023825872@claude-code-remote>",
"sessionId": "49c51c77-8ecc-4a04-a7a8-5955f89411b1",
"token": "DQH198YY",
"type": "notification",
"sentAt": "2025-08-01T04:50:27.443Z"
},
{
"messageId": "<cc5a1eba-7e04-44e6-919f-0395ddf2ee08-1754024054719@claude-code-remote>",
"sessionId": "cc5a1eba-7e04-44e6-919f-0395ddf2ee08",
"token": "GQE6LLIF",
"type": "notification",
"sentAt": "2025-08-01T04:54:16.491Z"
},
{
"messageId": "<63198b90-3b9d-4448-a932-7c39c771554a-1754024273245@claude-code-remote>",
"sessionId": "63198b90-3b9d-4448-a932-7c39c771554a",
"token": "WYKEGTXV",
"type": "notification",
"sentAt": "2025-08-01T04:57:54.938Z"
},
{
"messageId": "<bfd8b8d0-bcc4-4336-a6eb-8ca3f8b6c96e-1754025557415@claude-code-remote>",
"sessionId": "bfd8b8d0-bcc4-4336-a6eb-8ca3f8b6c96e",
"token": "ATUBLIN1",
"type": "notification",
"sentAt": "2025-08-01T05:19:18.866Z"
},
{
"messageId": "<cdbfc3f9-e27e-47d9-82b9-9058f0180fff-1754025634473@claude-code-remote>",
"sessionId": "cdbfc3f9-e27e-47d9-82b9-9058f0180fff",
"token": "2W24RZTM",
"type": "notification",
"sentAt": "2025-08-01T05:20:35.851Z"
},
{
"messageId": "<cf18cb3b-bc72-46ea-985f-2b7f29f0993f-1754025855175@claude-code-remote>",
"sessionId": "cf18cb3b-bc72-46ea-985f-2b7f29f0993f",
"token": "PHAVU604",
"type": "notification",
"sentAt": "2025-08-01T05:24:16.539Z"
},
{
"messageId": "<6da5d6f6-7e68-47eb-8676-175d77f0ddbc-1754026029245@claude-code-remote>",
"sessionId": "6da5d6f6-7e68-47eb-8676-175d77f0ddbc",
"token": "C2APCY3V",
"type": "notification",
"sentAt": "2025-08-01T05:27:10.601Z"
},
{
"messageId": "<9143d717-be05-4869-b40a-2c775addcfc4-1754026119754@claude-code-remote>",
"sessionId": "9143d717-be05-4869-b40a-2c775addcfc4",
"token": "N5P4GUG3",
"type": "notification",
"sentAt": "2025-08-01T05:28:41.439Z"
},
{
"messageId": "<6fb2932d-d4ac-427b-8b7d-54af44fee1f2-1754026220881@claude-code-remote>",
"sessionId": "6fb2932d-d4ac-427b-8b7d-54af44fee1f2",
"token": "CZIKHCFO",
"type": "notification",
"sentAt": "2025-08-01T05:30:22.303Z"
}
]
"messages": []
}
Loading