claude-code-remote-remake/claude-remote.js

1045 lines
41 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Claude-Code-Remote - Claude Code Smart Notification System
* Main entry point for the CLI tool
*/
// Load environment variables from Claude-Code-Remote directory
const path = require('path');
const envPath = path.join(__dirname, '.env');
require('dotenv').config({ path: envPath });
const Logger = require('./src/core/logger');
const Notifier = require('./src/core/notifier');
const ConfigManager = require('./src/core/config');
class ClaudeCodeRemoteCLI {
constructor() {
this.logger = new Logger('CLI');
this.config = new ConfigManager();
this.notifier = new Notifier(this.config);
}
async init() {
// Load configuration
this.config.load();
// Initialize channels
await this.notifier.initializeChannels();
}
async run() {
const args = process.argv.slice(2);
const command = args[0];
try {
await this.init();
switch (command) {
case 'notify':
await this.handleNotify(args.slice(1));
break;
case 'test':
await this.handleTest(args.slice(1));
break;
case 'status':
await this.handleStatus(args.slice(1));
break;
case 'config':
await this.handleConfig(args.slice(1));
break;
case 'install':
await this.handleInstall(args.slice(1));
break;
case 'relay':
await this.handleRelay(args.slice(1));
break;
case 'edit-config':
await this.handleEditConfig(args.slice(1));
break;
case 'setup-email':
await this.handleSetupEmail(args.slice(1));
break;
case 'daemon':
await this.handleDaemon(args.slice(1));
break;
case 'commands':
await this.handleCommands(args.slice(1));
break;
case 'test-paste':
await this.handleTestPaste(args.slice(1));
break;
case 'test-simple':
await this.handleTestSimple(args.slice(1));
break;
case 'test-claude':
await this.handleTestClaude(args.slice(1));
break;
case 'setup-permissions':
await this.handleSetupPermissions(args.slice(1));
break;
case 'diagnose':
await this.handleDiagnose(args.slice(1));
break;
case '--help':
case '-h':
case undefined:
this.showHelp();
break;
default:
console.error(`Unknown command: ${command}`);
this.showHelp();
process.exit(1);
}
} catch (error) {
this.logger.error('CLI error:', error.message);
process.exit(1);
}
}
async handleNotify(args) {
const typeIndex = args.findIndex(arg => arg === '--type');
if (typeIndex === -1 || typeIndex + 1 >= args.length) {
console.error('Usage: claude-remote notify --type <completed|waiting>');
process.exit(1);
}
const type = args[typeIndex + 1];
if (!['completed', 'waiting'].includes(type)) {
console.error('Invalid type. Use: completed or waiting');
process.exit(1);
}
// 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 and execution trace
if (type === 'completed') {
const Config = require('./src/core/config');
const config = new Config();
config.load();
const showSubagentActivitiesInEmail = config.get('showSubagentActivitiesInEmail', false);
if (showSubagentActivitiesInEmail) {
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;
}
// Clear activities after including them in the notification
tracker.clearActivities(trackingKey);
} else {
// Always clear activities even if not showing them
const SubagentTracker = require('./src/utils/subagent-tracker');
const tracker = new SubagentTracker();
const trackingKey = metadata.tmuxSession || 'default';
tracker.clearActivities(trackingKey);
}
}
const result = await this.notifier.notify(type, metadata);
if (result.success) {
this.logger.info(`${type} notification sent successfully`);
process.exit(0);
} else {
this.logger.error(`Failed to send ${type} notification`);
process.exit(1);
}
}
async captureCurrentConversation() {
try {
const { execSync } = require('child_process');
const TmuxMonitor = require('./src/utils/tmux-monitor');
// Get current tmux session name
let currentSession = null;
try {
currentSession = execSync('tmux display-message -p "#S"', {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore']
}).trim();
} catch (e) {
// Not running in tmux, return empty metadata
return {};
}
if (!currentSession) {
return {};
}
// Use TmuxMonitor to capture conversation
const tmuxMonitor = new TmuxMonitor();
const conversation = tmuxMonitor.getRecentConversation(currentSession);
const fullTrace = tmuxMonitor.getFullExecutionTrace(currentSession);
return {
userQuestion: conversation.userQuestion,
claudeResponse: conversation.claudeResponse,
tmuxSession: currentSession,
fullExecutionTrace: fullTrace
};
} catch (error) {
this.logger.debug('Failed to capture conversation:', error.message);
return {};
}
}
async handleTest(args) {
console.log('Testing notification channels...\n');
const results = await this.notifier.test();
for (const [channel, result] of Object.entries(results)) {
const status = result.success ? '✅ PASS' : '❌ FAIL';
console.log(`${channel}: ${status}`);
if (result.error) {
console.log(` Error: ${result.error}`);
}
}
const passCount = Object.values(results).filter(r => r.success).length;
const totalCount = Object.keys(results).length;
console.log(`\nTest completed: ${passCount}/${totalCount} channels passed`);
if (passCount === 0) {
process.exit(1);
}
}
async handleStatus(args) {
const status = this.notifier.getStatus();
console.log('Claude-Code-Remote Status\n');
console.log('Configuration:');
console.log(` Enabled: ${status.enabled ? 'Yes' : 'No'}`);
console.log(` Language: ${status.config.language}`);
console.log(` Sounds: ${status.config.sound.completed} / ${status.config.sound.waiting}`);
console.log('\nChannels:');
// Display all available channels, including disabled ones
const allChannels = this.config._channels || {};
const activeChannels = status.channels || {};
// Merge all channel information
const channelNames = new Set([
...Object.keys(allChannels),
...Object.keys(activeChannels)
]);
for (const name of channelNames) {
const channelConfig = allChannels[name] || {};
const channelStatus = activeChannels[name];
let enabled, configured, relay;
if (channelStatus) {
// Active channel, use actual status
enabled = channelStatus.enabled ? '✅' : '❌';
configured = channelStatus.configured ? '✅' : '❌';
relay = channelStatus.supportsRelay ? '✅' : '❌';
} else {
// Inactive channel, use configuration status
enabled = channelConfig.enabled ? '✅' : '❌';
configured = this._isChannelConfigured(name, channelConfig) ? '✅' : '❌';
relay = this._supportsRelay(name) ? '✅' : '❌';
}
console.log(` ${name}:`);
console.log(` Enabled: ${enabled}`);
console.log(` Configured: ${configured}`);
console.log(` Supports Relay: ${relay}`);
}
}
_isChannelConfigured(name, config) {
switch (name) {
case 'desktop':
return true; // Desktop notifications don't need special configuration
case 'email':
return config.config &&
config.config.smtp &&
config.config.smtp.host &&
config.config.smtp.auth &&
config.config.smtp.auth.user &&
config.config.to;
default:
return false;
}
}
_supportsRelay(name) {
switch (name) {
case 'email':
return true;
case 'desktop':
default:
return false;
}
}
async handleConfig(args) {
// Launch the configuration tool
const ConfigTool = require('./src/tools/config-manager');
const configTool = new ConfigTool(this.config);
await configTool.run(args);
}
async handleInstall(args) {
// Launch the installer
const Installer = require('./src/tools/installer');
const installer = new Installer(this.config);
await installer.run(args);
}
async handleRelay(args) {
const subcommand = args[0];
switch (subcommand) {
case 'start':
await this.startRelay(args.slice(1));
break;
case 'stop':
await this.stopRelay(args.slice(1));
break;
case 'status':
await this.relayStatus(args.slice(1));
break;
case 'cleanup':
await this.cleanupRelay(args.slice(1));
break;
default:
console.error('Usage: claude-remote relay <start|stop|status|cleanup>');
console.log('');
console.log('Commands:');
console.log(' start Start email command relay service');
console.log(' stop Stop email command relay service');
console.log(' status View relay service status');
console.log(' cleanup Clean up completed command history');
process.exit(1);
}
}
async startRelay(args) {
try {
const CommandRelayService = require('./src/relay/command-relay');
const emailConfig = this.config.getChannel('email');
if (!emailConfig || !emailConfig.enabled) {
console.error('❌ Email channel not configured or disabled');
console.log('Please run first: claude-remote config');
process.exit(1);
}
console.log('🚀 Starting email command relay service...');
const relayService = new CommandRelayService(emailConfig.config);
// Listen for events
relayService.on('started', () => {
console.log('✅ Command relay service started');
console.log('📧 Listening for email replies...');
console.log('💡 You can now remotely execute Claude Code commands by replying to emails');
console.log('');
console.log('Press Ctrl+C to stop the service');
});
relayService.on('commandQueued', (command) => {
console.log(`📨 Received new command: ${command.command.substring(0, 50)}...`);
});
relayService.on('commandExecuted', (command) => {
console.log(`✅ Command executed successfully: ${command.id}`);
});
relayService.on('commandFailed', (command, error) => {
console.log(`❌ Command execution failed: ${command.id} - ${error.message}`);
});
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.log('\n🛑 Stopping command relay service...');
await relayService.stop();
console.log('✅ Service stopped');
process.exit(0);
});
// Start service
await relayService.start();
// Keep process running
process.stdin.resume();
} catch (error) {
console.error('❌ Failed to start relay service:', error.message);
process.exit(1);
}
}
async stopRelay(args) {
console.log('💡 Command relay service usually stopped with Ctrl+C');
console.log('If the service is still running, please find the corresponding process and terminate it manually');
}
async relayStatus(args) {
try {
const fs = require('fs');
const path = require('path');
const stateFile = path.join(__dirname, 'src/data/relay-state.json');
console.log('📊 Command relay service status\n');
// Check email configuration
const emailConfig = this.config.getChannel('email');
if (!emailConfig || !emailConfig.enabled) {
console.log('❌ Email channel not configured');
return;
}
console.log('✅ Email configuration enabled');
console.log(`📧 SMTP: ${emailConfig.config.smtp.host}:${emailConfig.config.smtp.port}`);
console.log(`📥 IMAP: ${emailConfig.config.imap.host}:${emailConfig.config.imap.port}`);
console.log(`📬 Recipient: ${emailConfig.config.to}`);
// Check relay status
if (fs.existsSync(stateFile)) {
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
console.log(`\n📋 Command queue: ${state.commandQueue?.length || 0} commands`);
if (state.commandQueue && state.commandQueue.length > 0) {
console.log('\nRecent commands:');
state.commandQueue.slice(-5).forEach(cmd => {
const status = cmd.status === 'completed' ? '✅' :
cmd.status === 'failed' ? '❌' :
cmd.status === 'executing' ? '⏳' : '⏸️';
console.log(` ${status} ${cmd.id}: ${cmd.command.substring(0, 50)}...`);
});
}
} else {
console.log('\n📋 No command history found');
}
} catch (error) {
console.error('❌ Failed to get status:', error.message);
}
}
async cleanupRelay(args) {
try {
const fs = require('fs');
const path = require('path');
const stateFile = path.join(__dirname, 'src/data/relay-state.json');
if (!fs.existsSync(stateFile)) {
console.log('📋 No cleanup needed, no command history found');
return;
}
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
const beforeCount = state.commandQueue?.length || 0;
// Clean up completed commands (keep those within 24 hours)
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000);
state.commandQueue = (state.commandQueue || []).filter(cmd =>
cmd.status !== 'completed' ||
new Date(cmd.completedAt || cmd.queuedAt) > cutoff
);
const afterCount = state.commandQueue.length;
const removedCount = beforeCount - afterCount;
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
console.log(`🧹 Cleanup completed: removed ${removedCount} completed commands`);
console.log(`📋 ${afterCount} commands remaining in queue`);
} catch (error) {
console.error('❌ Cleanup failed:', error.message);
}
}
async handleEditConfig(args) {
const { spawn } = require('child_process');
const path = require('path');
const configType = args[0];
if (!configType) {
console.log('Available configuration files:');
console.log(' user - User personal configuration (config/user.json)');
console.log(' channels - Notification channel configuration (config/channels.json)');
console.log(' default - Default configuration template (config/default.json)');
console.log('');
console.log('Usage: claude-remote edit-config <configuration-type>');
console.log('Example: claude-remote edit-config channels');
return;
}
const configFiles = {
'user': path.join(__dirname, 'config/user.json'),
'channels': path.join(__dirname, 'config/channels.json'),
'default': path.join(__dirname, 'config/default.json')
};
const configFile = configFiles[configType];
if (!configFile) {
console.error('❌ Invalid configuration type:', configType);
console.log('Available types: user, channels, default');
return;
}
// Check if file exists
const fs = require('fs');
if (!fs.existsSync(configFile)) {
console.error('❌ Configuration file does not exist:', configFile);
return;
}
console.log(`📝 Opening configuration file: ${configFile}`);
console.log('💡 Save and close the editor after editing to take effect');
console.log('');
// Determine the editor to use
const editor = process.env.EDITOR || process.env.VISUAL || this._getDefaultEditor();
try {
const editorProcess = spawn(editor, [configFile], {
stdio: 'inherit'
});
editorProcess.on('close', (code) => {
if (code === 0) {
console.log('✅ Configuration file saved');
console.log('💡 Run "claude-remote status" to view updated configuration');
} else {
console.log('❌ Editor exited abnormally');
}
});
editorProcess.on('error', (error) => {
console.error('❌ Unable to start editor:', error.message);
console.log('');
console.log('💡 You can manually edit the configuration file:');
console.log(` ${configFile}`);
});
} catch (error) {
console.error('❌ Failed to start editor:', error.message);
console.log('');
console.log('💡 You can manually edit the configuration file:');
console.log(` ${configFile}`);
}
}
_getDefaultEditor() {
// Determine default editor based on platform
if (process.platform === 'win32') {
return 'notepad';
} else if (process.platform === 'darwin') {
return 'nano'; // Use nano on macOS as most users have it
} else {
return 'nano'; // Linux default to nano
}
}
async handleSetupEmail(args) {
const readline = require('readline');
const fs = require('fs');
const path = require('path');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (prompt) => {
return new Promise((resolve) => {
rl.question(prompt, resolve);
});
};
try {
console.log('🚀 Claude-Code-Remote Email Quick Setup Wizard\n');
// Select email provider
console.log('Please select your email provider:');
console.log('1. Gmail');
console.log('2. QQ Email');
console.log('3. 163 Email');
console.log('4. Outlook/Hotmail');
console.log('5. Custom');
const providerChoice = await question('\nPlease select (1-5): ');
let smtpHost, smtpPort, imapHost, imapPort, secure;
switch (providerChoice) {
case '1':
smtpHost = 'smtp.gmail.com';
smtpPort = 587;
imapHost = 'imap.gmail.com';
imapPort = 993;
secure = false;
console.log('\n📧 Gmail Configuration');
console.log('💡 Need to enable two-factor authentication and generate app password first');
break;
case '2':
smtpHost = 'smtp.qq.com';
smtpPort = 587;
imapHost = 'imap.qq.com';
imapPort = 993;
secure = false;
console.log('\n📧 QQ Email Configuration');
break;
case '3':
smtpHost = 'smtp.163.com';
smtpPort = 587;
imapHost = 'imap.163.com';
imapPort = 993;
secure = false;
console.log('\n📧 163 Email Configuration');
break;
case '4':
smtpHost = 'smtp.live.com';
smtpPort = 587;
imapHost = 'imap-mail.outlook.com';
imapPort = 993;
secure = false;
console.log('\n📧 Outlook Configuration');
break;
case '5':
console.log('\n📧 Custom Configuration');
smtpHost = await question('SMTP Host: ');
smtpPort = parseInt(await question('SMTP Port (default 587): ') || '587');
imapHost = await question('IMAP Host: ');
imapPort = parseInt(await question('IMAP Port (default 993): ') || '993');
const secureInput = await question('Use SSL/TLS? (y/n): ');
secure = secureInput.toLowerCase() === 'y';
break;
default:
console.log('❌ Invalid selection');
rl.close();
return;
}
// Get email account information
console.log('\n📝 Please enter email account information:');
const email = await question('Email address: ');
const password = await question('Password/App password: ');
// Build configuration
const emailConfig = {
type: "email",
enabled: true,
config: {
smtp: {
host: smtpHost,
port: smtpPort,
secure: secure,
auth: {
user: email,
pass: password
}
},
imap: {
host: imapHost,
port: imapPort,
secure: true,
auth: {
user: email,
pass: password
}
},
from: `Claude-Code-Remote <${email}>`,
to: email,
template: {
checkInterval: 30
}
}
};
// Read existing configuration
const channelsFile = path.join(__dirname, 'config/channels.json');
let channels = {};
if (fs.existsSync(channelsFile)) {
channels = JSON.parse(fs.readFileSync(channelsFile, 'utf8'));
}
// Update email configuration
channels.email = emailConfig;
// Save configuration
fs.writeFileSync(channelsFile, JSON.stringify(channels, null, 2));
console.log('\n✅ Email configuration saved!');
console.log('\n🧪 You can now test email functionality:');
console.log(' claude-remote test');
console.log('\n🚀 Start command relay service:');
console.log(' claude-remote relay start');
// Ask if user wants to test immediately
const testNow = await question('\nTest email sending now? (y/n): ');
if (testNow.toLowerCase() === 'y') {
rl.close();
// Reload configuration and test
await this.init();
await this.handleTest([]);
} else {
rl.close();
}
} catch (error) {
console.error('❌ Configuration failed:', error.message);
rl.close();
}
}
async handleDaemon(args) {
const ClaudeCodeRemoteDaemon = require('./src/daemon/taskping-daemon');
const daemon = new ClaudeCodeRemoteDaemon();
const command = args[0];
switch (command) {
case 'start':
await daemon.start();
break;
case 'stop':
await daemon.stop();
break;
case 'restart':
await daemon.restart();
break;
case 'status':
daemon.showStatus();
break;
default:
console.log('Usage: claude-remote daemon <start|stop|restart|status>');
console.log('');
console.log('Commands:');
console.log(' start Start background daemon process');
console.log(' stop Stop background daemon process');
console.log(' restart Restart background daemon process');
console.log(' status View daemon process status');
break;
}
}
async handleCommands(args) {
const ClaudeCommandBridge = require('./src/relay/claude-command-bridge');
const bridge = new ClaudeCommandBridge();
const command = args[0];
switch (command) {
case 'list':
const pending = bridge.getPendingCommands();
console.log(`📋 Pending commands: ${pending.length}\n`);
if (pending.length > 0) {
pending.forEach((cmd, index) => {
console.log(`${index + 1}. ${cmd.id}`);
console.log(` Command: ${cmd.command}`);
console.log(` Time: ${cmd.timestamp}`);
console.log(` Session: ${cmd.sessionId}`);
console.log('');
});
}
break;
case 'status':
const status = bridge.getStatus();
console.log('📊 Command bridge status\n');
console.log(`Pending commands: ${status.pendingCommands}`);
console.log(`Processed commands: ${status.processedCommands}`);
console.log(`Commands directory: ${status.commandsDir}`);
console.log(`Response directory: ${status.responseDir}`);
if (status.recentCommands.length > 0) {
console.log('\nRecent commands:');
status.recentCommands.forEach(cmd => {
console.log(`${cmd.command} (${cmd.timestamp})`);
});
}
break;
case 'cleanup':
bridge.cleanup();
console.log('🧹 Old command files cleaned up');
break;
case 'clear':
const pending2 = bridge.getPendingCommands();
for (const cmd of pending2) {
bridge.markCommandProcessed(cmd.id, 'cancelled', 'Manually cancelled');
}
console.log(`🗑️ Cleared ${pending2.length} pending commands`);
break;
default:
console.log('Usage: claude-remote commands <list|status|cleanup|clear>');
console.log('');
console.log('Commands:');
console.log(' list Show pending email commands');
console.log(' status Show command bridge status');
console.log(' cleanup Clean up old command files');
console.log(' clear Clear all pending commands');
break;
}
}
async handleTestPaste(args) {
const ClipboardAutomation = require('./src/automation/clipboard-automation');
const automation = new ClipboardAutomation();
const testCommand = args.join(' ') || 'echo "Testing email reply auto-paste functionality"';
console.log('🧪 Testing auto-paste functionality');
console.log(`📝 Test command: ${testCommand}`);
console.log('\n⚠ Please ensure Claude Code or Terminal window is open and active');
console.log('⏳ Command will be sent automatically in 3 seconds...\n');
// Countdown
for (let i = 3; i > 0; i--) {
process.stdout.write(`${i}... `);
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('\n');
try {
const success = await automation.sendCommand(testCommand);
if (success) {
console.log('✅ Command has been auto-pasted!');
console.log('💡 If you don\'t see the effect, please check app permissions and window status');
} else {
console.log('❌ Auto-paste failed');
console.log('💡 Please ensure automation permissions are granted and target app is open');
}
} catch (error) {
console.error('❌ Test failed:', error.message);
}
}
async handleSetupPermissions(args) {
const PermissionSetup = require('./setup-permissions');
const setup = new PermissionSetup();
await setup.checkAndSetup();
}
async handleTestSimple(args) {
const SimpleAutomation = require('./src/automation/simple-automation');
const automation = new SimpleAutomation();
const testCommand = args.join(' ') || 'echo "Testing simple automation functionality"';
console.log('🧪 Testing simple automation functionality');
console.log(`📝 Test command: ${testCommand}`);
console.log('\nThis test will:');
console.log('1. 📋 Copy command to clipboard');
console.log('2. 📄 Save command to file');
console.log('3. 🔔 Send notification (including dialog box)');
console.log('4. 🤖 Attempt auto-paste (if permissions granted)');
console.log('\n⏳ Starting test...\n');
try {
const success = await automation.sendCommand(testCommand, 'test-session');
if (success) {
console.log('✅ Test successful!');
console.log('\n📋 Next steps:');
console.log('1. Check if you received notification');
console.log('2. Check if command was copied to clipboard');
console.log('3. If you see dialog box, you can choose to open command file');
console.log('4. Manually paste to Claude Code (if auto-paste didn\'t work)');
const status = automation.getStatus();
console.log(`\n📄 Command file: ${status.commandFile}`);
if (status.commandFileExists) {
console.log('💡 You can run "open -t ' + status.commandFile + '" to view command file');
}
} else {
console.log('❌ Test failed');
}
} catch (error) {
console.error('❌ Error occurred during test:', error.message);
}
}
async handleTestClaude(args) {
const ClaudeAutomation = require('./src/automation/claude-automation');
const automation = new ClaudeAutomation();
const testCommand = args.join(' ') || 'echo "This is an automated test command from email reply"';
console.log('🤖 Testing Claude Code specialized automation');
console.log(`📝 Test command: ${testCommand}`);
console.log('\n⚠ Please ensure:');
console.log(' 1. Claude Code application is open');
console.log(' 2. Or Terminal/iTerm2 etc. terminal applications are open');
console.log(' 3. Necessary accessibility permissions have been granted');
console.log('\n⏳ Full automation test will start in 5 seconds...\n');
// Countdown
for (let i = 5; i > 0; i--) {
process.stdout.write(`${i}... `);
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('\n🚀 Starting automation...\n');
try {
// Check permissions
const hasPermission = await automation.requestPermissions();
if (!hasPermission) {
console.log('⚠️ Permission check failed, but will still attempt execution...');
}
// Execute full automation
const success = await automation.sendCommand(testCommand, 'test-session');
if (success) {
console.log('✅ Full automation test successful!');
console.log('💡 Command should have been automatically input to Claude Code and started execution');
console.log('🔍 Please check Claude Code window to see if command was received');
} else {
console.log('❌ Automation test failed');
console.log('💡 Possible reasons:');
console.log(' • Claude Code or terminal application not found');
console.log(' • Insufficient permissions');
console.log(' • Application not responding');
console.log('\n🔧 Suggestions:');
console.log(' 1. Run "claude-remote setup-permissions" to check permissions');
console.log(' 2. Ensure Claude Code is running in foreground');
console.log(' 3. Try manually clicking input box in Claude Code first');
}
} catch (error) {
console.error('❌ Error occurred during test:', error.message);
}
}
async handleDiagnose(args) {
const AutomationDiagnostic = require('./diagnose-automation');
const diagnostic = new AutomationDiagnostic();
await diagnostic.runDiagnostic();
}
showHelp() {
console.log(`
Claude-Code-Remote - Claude Code Smart Notification System
Usage: claude-remote <command> [options]
Commands:
notify --type <type> Send a notification (completed|waiting)
test Test all notification channels
status Show system status
config Launch configuration manager
setup-email Quick email setup wizard
edit-config <type> Edit configuration files directly
install Install and configure Claude Code hooks
relay <subcommand> Manage email command relay service
daemon <subcommand> Manage background daemon service
commands <subcommand> Manage email commands and bridge
test-paste [command] Test automatic paste functionality
test-simple [command] Test simple automation (recommended)
test-claude [command] Test Claude Code full automation
setup-permissions Setup macOS permissions for automation
diagnose Diagnose automation issues
Options:
-h, --help Show this help message
Relay Subcommands:
relay start Start email command relay service
relay stop Stop email command relay service
relay status Show relay service status
relay cleanup Clean up completed command history
Daemon Subcommands:
daemon start Start background daemon service
daemon stop Stop background daemon service
daemon restart Restart background daemon service
daemon status Show daemon service status
Commands Subcommands:
commands list Show pending email commands
commands status Show command bridge status
commands cleanup Clean up old command files
commands clear Clear all pending commands
Examples:
claude-remote notify --type completed
claude-remote test
claude-remote setup-email # Quick email setup (recommended)
claude-remote edit-config channels # Edit configuration files directly
claude-remote config # Interactive configuration
claude-remote install
claude-remote daemon start # Start background service (recommended)
claude-remote daemon status # View service status
claude-remote test-claude # Test full automation (recommended)
claude-remote commands list # View pending email commands
claude-remote relay start # Run in foreground (need to keep window open)
For more information, visit: https://github.com/Claude-Code-Remote/Claude-Code-Remote
`);
}
}
// Run CLI if this file is executed directly
if (require.main === module) {
const cli = new ClaudeCodeRemoteCLI();
cli.run().catch(error => {
console.error('Fatal error:', error.message);
process.exit(1);
});
}
module.exports = ClaudeCodeRemoteCLI;