UNPKG

dedpaste

Version:

CLI pastebin application using Cloudflare Workers and R2

2,017 lines 79.5 kB
#!/usr/bin/env node

import { program } from 'commander';
import fetch from 'node-fetch';
import { lookup } from 'mime-types';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import { promises as fsPromises } from 'fs';
import { homedir, tmpdir } from 'os';
import inquirer from 'inquirer';
// Import clipboardy with error handling
let clipboard;
try {
  clipboard = await import('clipboardy');
  // Clipboard module loaded successfully
} catch (error) {
  console.error(`Failed to load clipboardy: ${error.message}`);
  // Fallback implementation if clipboard fails to load
  clipboard = {
    writeSync: (text) => {
      console.error('Clipboard access is not available. URL could not be copied.');
      console.log(`Manual copy: ${text}`);
    }
  };
}

// Import our core modules
import {
  generateKeyPair,
  addFriendKey,
  listKeys,
  getKey,
  removeKey,
  updateLastUsed,
  addPgpKey,
  addKeybaseKey
} from './keyManager.js';

import {
  encryptContent,
  decryptContent
} from './encryptionUtils.js';

import {
  interactiveKeyManagement,
  interactiveListKeys,
  interactiveAddFriend,
  interactiveRemoveKey,
  interactiveExportKey,
  interactiveSend
} from './interactiveMode.js';

// Import PGP and Keybase utilities
import {
  fetchPgpKey,
  importPgpKey,
  addPgpKeyFromServer
} from './pgpUtils.js';

import {
  fetchKeybaseUser,
  fetchKeybasePgpKey,
  verifyKeybaseProofs,
  addKeybaseKey as fetchAndAddKeybaseKey
} from './keybaseUtils.js';

const require = createRequire(import.meta.url);
const packageJson = require('../package.json');

// Default API URL - can be changed via environment variable
const API_URL = process.env.DEDPASTE_API_URL || 'https://paste.d3d.dev';

program
  .name('dedpaste')
  .description('CLI client for DedPaste, a simple pastebin service')
  .version(packageJson.version)
  .addHelpText('before', `
DedPaste - Secure pastebin with end-to-end encryption

USAGE:
  $ dedpaste                     Create a paste from stdin
  $ dedpaste < file.txt          Create a paste from a file
  $ dedpaste --file path/to/file Create a paste from a specific file
  $ dedpaste --temp              Create a one-time paste (deleted after viewing)
  $ dedpaste --encrypt           Create an encrypted paste
  $ dedpaste keys                Manage encryption keys
  $ dedpaste keys:enhanced       Manage keys with enhanced interactive UI
  $ dedpaste send                Create and send an encrypted paste
  $ dedpaste get <url-or-id>     Retrieve and display a paste
  $ dedpaste completion          Generate shell completion scripts

EXAMPLES:
  $ echo "Hello, world!" | dedpaste
  $ dedpaste --file secret.txt --temp --encrypt
  $ dedpaste keys --gen-key
  $ dedpaste keys:enhanced       # Use the enhanced UI for key management
  $ dedpaste send --encrypt --for alice --temp
  $ dedpaste get https://paste.d3d.dev/AbCdEfGh
  $ dedpaste completion --bash > ~/.dedpaste-completion.bash
`);

// Add a command to manage keys
// Add a special direct command for enhanced mode
program
  .command('keys:enhanced')
  .description('Manage encryption keys in enhanced interactive TUI mode')
  .action(async () => {
    try {
      const { spawn } = await import('child_process');
      const { fileURLToPath } = await import('url');
      const path = await import('path');
      
      // Get the path to the current module directory
      const __filename = fileURLToPath(import.meta.url);
      const __dirname = path.dirname(__filename);
      
      const enhancedLauncherPath = path.join(__dirname, 'enhancedModeLauncher.js');
      
      console.log('Starting enhanced mode...');
      
      // Spawn the launcher as a separate process
      const enhancedProcess = spawn('node', [enhancedLauncherPath], {
        stdio: 'inherit',
        env: process.env
      });
      
      // Handle process events
      process.on('SIGINT', () => {
        console.log('\nTerminating enhanced mode...');
        enhancedProcess.kill('SIGTERM');
        // Allow clean exit
        setTimeout(() => process.exit(0), 300);
      });
      
      // Wait for the process to complete with a timeout
      await new Promise((resolve, reject) => {
        // Set a timeout to prevent hanging
        const timeout = setTimeout(() => {
          console.log('\nEnhanced mode is taking too long. Terminating...');
          enhancedProcess.kill('SIGTERM');
          reject(new Error('Enhanced mode timed out'));
        }, 60000); // 60 second timeout
        
        enhancedProcess.on('exit', (code) => {
          clearTimeout(timeout);
          if (code === 0) {
            resolve();
          } else {
            reject(new Error(`Enhanced mode exited with code ${code}`));
          }
        });
        
        enhancedProcess.on('error', (err) => {
          clearTimeout(timeout);
          reject(err);
        });
      });
    } catch (error) {
      console.error(`Error running enhanced mode: ${error.message}`);
      process.exit(1);
    }
  });

program
  .command('keys')
  .description('Manage encryption keys for secure communication')
  .option('--interactive', 'Use interactive menu-driven mode for key management')
  .option('--enhanced', 'Use enhanced interactive TUI mode with color and advanced features')
  .option('--list', 'List all your keys and friends\' keys with fingerprints')
  .option('--add-friend <name>', 'Add a friend\'s public key (requires --key-file)')
  .option('--key-file <path>', 'Path to key file for import/export operations')
  .option('--export', 'Export your public key to share with friends')
  .option('--remove <name>', 'Remove a friend\'s key from your keyring')
  .option('--gen-key', 'Generate a new RSA key pair for encryption')
  .option('--my-key', 'Output your public key to the console for sharing')
  .option('--diagnostics', 'Run diagnostics on your key configuration and show a report')
  .option('--search <query>', 'Search for keys by name, email, or fingerprint')
  .option('--details <id>', 'Show detailed information about a specific key')
  .option('--backup <dir>', 'Backup all keys to the specified directory')
  // PGP options
  .option('--pgp-key <email-or-id>', 'Fetch and add a PGP key from a keyserver')
  .option('--pgp-name <name>', 'Custom name for the PGP key (optional)')
  .option('--import-pgp-key <path>', 'Import a PGP private key for encryption/decryption')
  .option('--pgp-passphrase <phrase>', 'Passphrase for PGP private key')
  .option('--native-pgp', 'Use native PGP encryption instead of converting to PEM')
  .option('--from-gpg <key-id>', 'Import a key from your GPG keyring')
  .option('--gpg-import <path>', 'Import a key to your GPG keyring')
  // Keybase options
  .option('--keybase <username>', 'Fetch and add a Keybase user\'s PGP key')
  .option('--keybase-name <name>', 'Custom name for the Keybase user\'s key (optional)')
  .option('--no-verify', 'Skip verification of Keybase proofs')
  // Debugging and logging options
  .option('--verbose', 'Enable verbose logging (same as --log-level debug)')
  .option('--debug', 'Enable debug mode with extensive logging (same as --log-level trace)')
  .option('--log-level <level>', 'Set logging level (error, warn, info, debug, trace)')
  .option('--log-file <path>', 'Log to the specified file')
  .option('--no-log-file', 'Disable logging to file')
  .addHelpText('after', `
Examples:
  $ dedpaste keys --gen-key                               # Generate a new key pair
  $ dedpaste keys --list                                  # List all your keys
  $ dedpaste keys --add-friend alice --key-file alice_public.pem  # Add a friend's key
  $ dedpaste keys --my-key                                # Display your public key
  $ dedpaste keys --interactive                           # Use interactive mode
  $ dedpaste keys:enhanced                               # Use enhanced UI (recommended)
  
PGP Integration:
  $ dedpaste keys --pgp-key user@example.com              # Add a PGP key from keyservers
  $ dedpaste keys --pgp-key 0x1234ABCD                    # Add a PGP key using key ID
  $ dedpaste keys --pgp-key user@example.com --pgp-name alice  # Add with custom name
  
Keybase Integration:
  $ dedpaste keys --keybase username                      # Add a Keybase user's key
  $ dedpaste keys --keybase username --keybase-name bob   # Add with custom name
  $ dedpaste keys --keybase username --no-verify          # Skip verification of proofs
  
Key Storage:
  - Your keys are stored in ~/.dedpaste/keys/
  - Friend keys are stored in ~/.dedpaste/friends/
  - PGP keys are stored in ~/.dedpaste/pgp/
  - Keybase keys are stored in ~/.dedpaste/keybase/
  - Key database is at ~/.dedpaste/keydb.json
`)
  .action(async (options) => {
    try {
      // Initialize logger based on options
      let logLevel = 'info';
      if (options.verbose) logLevel = 'debug';
      if (options.debug) logLevel = 'trace';
      if (options.logLevel) logLevel = options.logLevel;
      
      const logOptions = {
        level: logLevel,
        logToFile: options.logFile !== false,
        logFile: options.logFile || 'dedpaste.log'
      };
      
      // Import and initialize logger
      const { initialize: initLogger } = await import('./logger.js');
      const logger = await initLogger(logOptions);
      
      // Log the start of execution
      logger.info('Starting key management operation', { options });
      
      // Enhanced interactive mode takes precedence but we need to ensure it doesn't hang
      if (options.enhanced) {
        logger.debug('Enhanced mode requested - using non-blocking startup', { options });
        console.log('Starting enhanced mode...');
        console.log('Debug info: enhanced flag =', options.enhanced);
        
        // Create a completely isolated process for enhanced mode
        // This avoids all potential circular dependencies and module loading issues
        
        try {
          // Use child_process to launch the enhanced mode in a separate process
          // This guarantees that any hanging in module initialization won't affect the main CLI
          const childProcess = await import('child_process');
          const { spawn } = childProcess;
          
          // Get the current module directory where our launcher is located
          const cliDir = path.dirname(fileURLToPath(import.meta.url));
          const launcherPath = path.join(cliDir, 'enhancedModeLauncher.js');
          
          // Verify that the launcher exists
          if (!fs.existsSync(launcherPath)) {
            logger.error('Enhanced mode launcher not found', { path: launcherPath });
            throw new Error(`Enhanced mode launcher not found at: ${launcherPath}`);
          }

          
          // Spawn the launcher as a separate process with the right working directory
          const enhancedProcess = spawn('node', [launcherPath, '--debug'], {
            stdio: 'inherit',
            env: process.env,
            cwd: cliDir // Use CLI directory as working directory
          });
          
          // Process termination handling
          process.on('SIGINT', () => {
            console.log('\nTerminating enhanced mode...');
            enhancedProcess.kill('SIGTERM');
            // Allow clean exit
            setTimeout(() => process.exit(0), 300);
          });
          
          // Wait for the process to complete with a timeout
          await new Promise((resolve, reject) => {
            // Set a timeout to prevent hanging
            const timeout = setTimeout(() => {
              console.log('\nEnhanced mode is taking too long. Terminating...');
              enhancedProcess.kill('SIGTERM');
              reject(new Error('Enhanced mode timed out'));
            }, 30000); // 30 second timeout
            
            enhancedProcess.on('exit', (code) => {
              clearTimeout(timeout);
              if (code === 0) {
                resolve();
              } else {
                reject(new Error(`Enhanced mode exited with code ${code}`));
              }
            });
            
            enhancedProcess.on('error', (err) => {
              clearTimeout(timeout);
              reject(err);
            });
          });
          
          // No temp file to clean up since we're using a dedicated launcher file
          
        } catch (enhancedError) {
          logger.error('Enhanced mode failed', { error: enhancedError.message, stack: enhancedError.stack });
          console.error(`Error running enhanced mode: ${enhancedError.message}`);
          console.error('Stack trace:', enhancedError.stack);
          console.log('Falling back to standard interactive mode...');
          
          // Fall back to regular interactive mode
          const result = await interactiveKeyManagement();
          if (result.message) {
            console.log(result.message);
          }
        }
        
        return;
      }
      
      // Regular interactive mode
      if (options.interactive) {
        logger.debug('Entering interactive mode');
        const result = await interactiveKeyManagement();
        if (result.message) {
          console.log(result.message);
        }
        return;
      }
      
      // Run diagnostics
      if (options.diagnostics) {
        logger.debug('Running key diagnostics');
        const { runKeyDiagnostics, formatDiagnosticsReport } = await import('./keyDiagnostics.js');
        
        console.log('Running key system diagnostics... Please wait...');
        const results = await runKeyDiagnostics();
        const report = formatDiagnosticsReport(results);
        
        console.log(report);
        
        if (results.status !== 'ok') {
          logger.warn('Diagnostics found issues', { 
            errors: results.errors.length, 
            warnings: results.warnings.length 
          });
          
          // Suggest fixing issues
          console.log('\nTo fix issues automatically, use:');
          console.log('dedpaste keys --enhanced');
          console.log('Then select "Run diagnostics" and follow the prompts.');
        }
        
        return;
      }
      
      // Search for keys
      if (options.search) {
        logger.debug('Searching for keys', { query: options.search });
        
        const { searchKeys } = await import('./unifiedKeyManager.js');
        const keys = await searchKeys(options.search, { includeGpg: true });
        
        if (keys.length === 0) {
          console.log(`No keys found matching "${options.search}"`);
          return;
        }
        
        console.log(`\nFound ${keys.length} keys matching "${options.search}":\n`);
        
        for (const key of keys) {
          console.log(`- ${key.name} (${key.type.toUpperCase()})`);
          
          if (key.email) {
            console.log(`  Email: ${key.email}`);
          }
          
          if (key.username) {
            console.log(`  Username: ${key.username}`);
          }
          
          console.log(`  Fingerprint: ${key.fingerprint}`);
          console.log(`  Source: ${key.source}`);
          
          if (key.created) {
            console.log(`  Created: ${new Date(key.created).toLocaleString()}`);
          }
          
          console.log('');
        }
        
        return;
      }
      
      // Show detailed key information
      if (options.details) {
        logger.debug('Showing key details', { keyId: options.details });
        
        const { getKeyById, readKeyContent } = await import('./unifiedKeyManager.js');
        const keyInfo = await getKeyById(options.details, { includeGpg: true });
        
        if (!keyInfo) {
          console.error(`Key "${options.details}" not found`);
          process.exit(1);
        }
        
        console.log(`\nKey details for "${keyInfo.name}":\n`);
        console.log(`- ID: ${keyInfo.id}`);
        console.log(`- Name: ${keyInfo.name}`);
        console.log(`- Type: ${keyInfo.type.toUpperCase()}`);
        console.log(`- Source: ${keyInfo.source}`);
        console.log(`- Fingerprint: ${keyInfo.fingerprint}`);
        
        if (keyInfo.email) {
          console.log(`- Email: ${keyInfo.email}`);
        }
        
        if (keyInfo.username) {
          console.log(`- Username: ${keyInfo.username}`);
        }
        
        if (keyInfo.created) {
          console.log(`- Created: ${new Date(keyInfo.created).toLocaleString()}`);
        }
        
        if (keyInfo.lastUsed) {
          console.log(`- Last Used: ${new Date(keyInfo.lastUsed).toLocaleString()}`);
        }
        
        if (keyInfo.path) {
          if (typeof keyInfo.path === 'object') {
            console.log(`- Public Key Path: ${keyInfo.path.public}`);
            console.log(`- Private Key Path: ${keyInfo.path.private}`);
          } else {
            console.log(`- Key Path: ${keyInfo.path}`);
          }
        }
        
        // Ask if user wants to see key content
        if (keyInfo.source === 'self' || keyInfo.source === 'friend' || 
            keyInfo.source === 'pgp' || keyInfo.source === 'keybase') {
          
          const { default: inquirer } = await import('inquirer');
          
          const { showContent } = await inquirer.prompt([{
            type: 'confirm',
            name: 'showContent',
            message: 'Show key content?',
            default: false
          }]);
          
          if (showContent) {
            // For self keys, ask if they want to see private key
            let showPrivate = false;
            if (keyInfo.source === 'self') {
              const { viewPrivate } = await inquirer.prompt([{
                type: 'confirm',
                name: 'viewPrivate',
                message: 'Show private key? (This is sensitive information)',
                default: false
              }]);
              
              showPrivate = viewPrivate;
            }
            
            // Read the key content
            const keyContent = await readKeyContent(keyInfo, { private: showPrivate });
            
            if (keyContent) {
              console.log('\n--- Key Content ---\n');
              console.log(keyContent);
              console.log('\n-------------------\n');
            } else {
              console.error('Could not read key content');
            }
          }
        }
        
        return;
      }
      
      // Backup keys
      if (options.backup) {
        logger.debug('Backing up keys', { directory: options.backup });
        
        const backupDir = options.backup;
        
        // Create backup directory if it doesn't exist
        await fsPromises.mkdir(backupDir, { recursive: true });
        
        // Get timestamp for backup files
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        
        // Get all keys
        const db = await loadKeyDatabase();
        let backupCount = 0;
        
        // Backup self key if it exists
        if (db.keys.self) {
          const privateKeyDest = path.join(backupDir, `self-private-${timestamp}.pem`);
          const publicKeyDest = path.join(backupDir, `self-public-${timestamp}.pem`);
          
          await fsPromises.copyFile(db.keys.self.private, privateKeyDest);
          await fsPromises.copyFile(db.keys.self.public, publicKeyDest);
          
          backupCount += 2;
          console.log(`Backed up self key to ${privateKeyDest} and ${publicKeyDest}`);
        }
        
        // Backup friend keys
        for (const [name, info] of Object.entries(db.keys.friends)) {
          const friendKeyDest = path.join(backupDir, `friend-${name}-${timestamp}.pem`);
          await fsPromises.copyFile(info.public, friendKeyDest);
          backupCount++;
          console.log(`Backed up friend key "${name}" to ${friendKeyDest}`);
        }
        
        // Backup PGP keys
        for (const [name, info] of Object.entries(db.keys.pgp || {})) {
          const pgpKeyDest = path.join(backupDir, `pgp-${name}-${timestamp}.asc`);
          await fsPromises.copyFile(info.path, pgpKeyDest);
          backupCount++;
          console.log(`Backed up PGP key "${name}" to ${pgpKeyDest}`);
        }
        
        // Backup Keybase keys
        for (const [name, info] of Object.entries(db.keys.keybase || {})) {
          const keybaseKeyDest = path.join(backupDir, `keybase-${name}-${timestamp}.asc`);
          await fsPromises.copyFile(info.path, keybaseKeyDest);
          backupCount++;
          console.log(`Backed up Keybase key "${name}" to ${keybaseKeyDest}`);
        }
        
        // Backup key database
        const dbDest = path.join(backupDir, `keydb-${timestamp}.json`);
        await fsPromises.copyFile(path.join(homedir(), '.dedpaste', 'keydb.json'), dbDest);
        console.log(`Backed up key database to ${dbDest}`);
        
        console.log(`\nBackup complete: ${backupCount} keys backed up to ${backupDir}`);
        return;
      }
      
      // Import a key from GPG keyring
      if (options.fromGpg) {
        logger.debug('Importing key from GPG keyring', { keyId: options.fromGpg });
        
        const { importKey } = await import('./unifiedKeyManager.js');
        
        const name = options.pgpName || options.fromGpg;
        console.log(`Importing key ${options.fromGpg} from GPG keyring...`);
        
        const result = await importKey({
          source: 'gpg-keyring',
          keyId: options.fromGpg,
          name: name
        });
        
        if (result.success) {
          console.log(`\n✓ Imported key from GPG keyring: ${result.name}`);
          console.log(`  - Type: ${result.type}`);
          console.log(`  - Fingerprint: ${result.fingerprint}`);
          if (result.email) console.log(`  - Email: ${result.email}`);
          console.log(`  - Stored at: ${result.path}`);
        } else {
          console.error(`Error importing key from GPG keyring: ${result.error}`);
          process.exit(1);
        }
        
        return;
      }
      
      // Import a key to GPG keyring
      if (options.gpgImport) {
        logger.debug('Importing key to GPG keyring', { file: options.gpgImport });
        
        const { importKey } = await import('./unifiedKeyManager.js');
        
        console.log(`Importing key from ${options.gpgImport} to GPG keyring...`);
        
        const result = await importKey({
          source: 'gpg-import',
          file: options.gpgImport
        });
        
        if (result.success) {
          console.log(`\n✓ Key imported to GPG keyring successfully`);
          console.log(`  - Key ID: ${result.keyId}`);
          console.log('\nOutput from GPG:');
          console.log(result.output);
        } else {
          console.error(`Error importing key to GPG keyring: ${result.error}`);
          process.exit(1);
        }
        
        return;
      }
      
      // List keys
      if (options.list) {
        logger.debug('Listing all keys');
        await interactiveListKeys();
        return;
      }
      
      // Add a friend's key
      if (options.addFriend) {
        if (!options.keyFile) {
          console.error('Error: --key-file is required when adding a friend');
          process.exit(1);
        }
        
        logger.debug('Adding friend key', { name: options.addFriend, file: options.keyFile });
        
        try {
          const keyContent = await fsPromises.readFile(options.keyFile, 'utf8');
          const keyPath = await addFriendKey(options.addFriend, keyContent);
          console.log(`Added ${options.addFriend}'s public key at ${keyPath}`);
        } catch (error) {
          logger.error('Failed to add friend key', { error: error.message });
          console.error(`Error adding friend's key: ${error.message}`);
          process.exit(1);
        }
        return;
      }
      
      // Export public key
      if (options.export) {
        logger.debug('Exporting public key');
        const result = await interactiveExportKey();
        if (result.message) {
          console.log(result.message);
        }
        return;
      }
      
      // Remove a key
      if (options.remove) {
        logger.debug('Removing key', { name: options.remove });
        
        try {
          // Try to remove from any collection
          const success = await removeKey('any', options.remove);
          if (success) {
            console.log(`Removed ${options.remove}'s key successfully`);
          } else {
            console.error(`Key "${options.remove}" not found`);
            process.exit(1);
          }
        } catch (error) {
          logger.error('Failed to remove key', { error: error.message });
          console.error(`Error removing key: ${error.message}`);
          process.exit(1);
        }
        return;
      }
      
      // Fetch and add a PGP key from keyservers
      if (options.pgpKey) {
        logger.debug('Fetching PGP key', { identifier: options.pgpKey });
        
        try {
          console.log(`Fetching PGP key for "${options.pgpKey}" from keyservers...`);
          const name = options.pgpName || options.pgpKey;
          const result = await addPgpKeyFromServer(options.pgpKey, name);
          console.log(`
✓ Added PGP key:
  - Name: ${result.name}
  - Email: ${result.email || 'Not specified'}
  - Key ID: ${result.keyId}
  - Stored at: ${result.path}
`);
        } catch (error) {
          logger.error('Failed to fetch PGP key', { error: error.message });
          console.error(`Error fetching PGP key: ${error.message}`);
          process.exit(1);
        }
        return;
      }
      
      // Import a PGP private key
      if (options.importPgpKey) {
        // Check if passphrase was provided
        if (!options.pgpPassphrase) {
          console.error('Error: --pgp-passphrase is required when importing a PGP private key');
          process.exit(1);
        }
        
        logger.debug('Importing PGP private key', { file: options.importPgpKey });
        
        try {
          // Read the PGP private key file
          const pgpPrivateKeyContent = await fsPromises.readFile(options.importPgpKey, 'utf8');
          
          // Import the PGP private key
          console.log(`Importing PGP private key from ${options.importPgpKey}...`);
          const result = await importPgpPrivateKey(pgpPrivateKeyContent, options.pgpPassphrase);
          
          // Save the private and public key to the PGP key directory
          const { PGP_KEY_DIR } = await ensureDirectories();
          const privateKeyPath = path.join(PGP_KEY_DIR, `private.pem`);
          const publicKeyPath = path.join(PGP_KEY_DIR, `public.pem`);
          
          // Write the keys to files
          await fsPromises.writeFile(privateKeyPath, result.privateKey);
          await fsPromises.writeFile(publicKeyPath, result.publicKey);
          
          // Update the key database
          const db = await loadKeyDatabase();
          db.keys.pgp.self = {
            private: privateKeyPath,
            public: publicKeyPath,
            original: options.importPgpKey,
            fingerprint: result.keyId,
            name: result.name,
            email: result.email,
            created: new Date().toISOString()
          };
          await saveKeyDatabase(db);
          
          console.log(`
✓ Imported PGP private key:
  - Name: ${result.name}
  - Email: ${result.email || 'Not specified'}
  - Key ID: ${result.keyId}
  - Converted private key: ${privateKeyPath}
  - Converted public key: ${publicKeyPath}
  - Original PGP key: ${options.importPgpKey}
`);
        } catch (error) {
          logger.error('Failed to import PGP private key', { error: error.message });
          console.error(`Error importing PGP private key: ${error.message}`);
          process.exit(1);
        }
        return;
      }
      
      // Fetch and add a Keybase user's key
      if (options.keybase) {
        logger.debug('Fetching Keybase key', { username: options.keybase });
        
        try {
          console.log(`Fetching Keybase key for user "${options.keybase}"...`);
          
          if (options.verify) {
            console.log('Verifying user proofs on Keybase...');
          }
          
          const name = options.keybaseName || `keybase:${options.keybase}`;
          const result = await fetchAndAddKeybaseKey(options.keybase, name, options.verify);
          
          console.log(`
✓ Added Keybase key:
  - Name: ${result.name}
  - Keybase username: ${result.username}
  - Email: ${result.email || 'Not specified'}
  - Key ID: ${result.keyId}
  - Stored at: ${result.path}
`);
        } catch (error) {
          logger.error('Failed to fetch Keybase key', { error: error.message });
          console.error(`Error fetching Keybase key: ${error.message}`);
          process.exit(1);
        }
        return;
      }
      
      // Generate a new key pair
      if (options.genKey) {
        logger.debug('Generating new key pair');
        
        const { privateKeyPath, publicKeyPath } = await generateKeyPair();
        console.log(`
✓ Generated new key pair:
  - Private key: ${privateKeyPath}
  - Public key: ${publicKeyPath}
`);
        return;
      }
      
      // Output public key to console
      if (options.myKey) {
        logger.debug('Showing self public key');
        
        const selfKey = await getKey('self');
        if (!selfKey) {
          console.error('No personal key found. Generate one with --gen-key first.');
          process.exit(1);
        }
        
        try {
          const publicKeyContent = await fsPromises.readFile(selfKey.public, 'utf8');
          console.log('\nYour public key:');
          console.log('----------------');
          console.log(publicKeyContent);
          console.log('----------------');
          console.log('\nShare this key with your friends so they can send you encrypted pastes.');
        } catch (error) {
          logger.error('Failed to read public key', { error: error.message });
          console.error(`Error reading public key: ${error.message}`);
          process.exit(1);
        }
        return;
      }
      
      // If no specific action is provided, show help
      program.commands.find(cmd => cmd.name() === 'keys').help();
    } catch (error) {
      console.error('Error:', error.message);
      process.exit(1);
    }
  });

// Add a command to send a paste
program
  .command('send')
  .description('Create and send an encrypted paste to friends')
  .option('-t, --temp', 'Create a one-time paste that is deleted after being viewed')
  .option('--type <content-type>', 'Specify the content type of the paste (e.g., application/json)')
  .option('-f, --file <path>', 'Upload a file from the specified path instead of stdin')
  .option('-o, --output', 'Print only the URL (without any additional text, useful for scripts)')
  .option('-e, --encrypt', 'Encrypt the content before uploading (requires key setup)')
  .option('--for <friend>', 'Encrypt for a specific friend (requires adding their key first)')
  .option('--list-friends', 'List available friends you can encrypt messages for')
  .option('--key-file <path>', 'Path to public key for encryption (alternative to stored keys)')
  .option('--gen-key', 'Generate a new key pair for encryption if you don\'t have one')
  .option('--interactive', 'Use interactive mode with guided prompts for message creation')
  .option('--enhanced', 'Use enhanced interactive mode with advanced key selection features')
  .option('--debug', 'Debug mode: show encrypted content without uploading')
  .option('-c, --copy', 'Copy the URL to clipboard automatically')
  // PGP options
  .option('--pgp', 'Use PGP encryption instead of hybrid RSA/AES')
  .option('--pgp-key-file <path>', 'Use a specific PGP public key file for encryption')
  .option('--pgp-armor', 'Output ASCII-armored PGP instead of binary format')
  .addHelpText('after', `
Examples:
  $ echo "Secret message" | dedpaste send --encrypt                # Encrypt for yourself (RSA/AES)
  $ echo "For Alice only" | dedpaste send --encrypt --for alice    # Encrypt for a friend
  $ dedpaste send --file secret.txt --encrypt --temp               # Encrypt a file as one-time paste
  $ dedpaste send --interactive --encrypt                          # Interactive encrypted message
  $ dedpaste send --enhanced --encrypt                             # Enhanced interactive mode with full key support
  $ dedpaste send --list-friends                                   # List available recipients
  
PGP Options:
  $ dedpaste send --encrypt --for alice@example.com --pgp          # Encrypt for PGP key (REQUIRED: specify recipient)
  $ dedpaste send --encrypt --for alice --pgp --pgp-key-file friend.asc  # Use specific PGP key file
  $ dedpaste send --enhanced --encrypt --pgp                       # Use enhanced mode with GPG keyring support
  
IMPORTANT: PGP encryption always requires specifying a recipient with --for
  
Encryption:
  - Standard encryption uses RSA for key exchange and AES-256-GCM for content
  - PGP encryption (--pgp) uses OpenPGP standard compatible with GnuPG/GPG
  - Each paste uses a different key for forward secrecy
  - Encrypted pastes include metadata about sender and recipient
  - Use --debug to test encryption without uploading
`)
  .action(async (options) => {
    // Ensure temp flag is properly set by checking command line arguments
    if (!options.temp && (process.argv.includes('--temp') || process.argv.includes('-t'))) {
      options.temp = true;
    }
    
    try {
      // List friends if requested
      if (options.listFriends) {
        const db = await listKeys();
        const friendNames = Object.keys(db.keys.friends);
        
        if (friendNames.length === 0) {
          console.log('No friend keys found. Add one with "dedpaste keys add-friend"');
          return;
        }
        
        console.log('\nAvailable friends:');
        for (const name of friendNames) {
          const friend = db.keys.friends[name];
          const lastUsed = new Date(friend.last_used).toLocaleString();
          console.log(`  - ${name} (last used: ${lastUsed})`);
        }
        return;
      }
      
      let content;
      let contentType;
      let recipientName = options.for;
      
      // Interactive mode
      if (options.interactive) {
        // Check if we should use the enhanced interactive mode
        if (options.enhanced) {
          console.log('Using enhanced interactive mode for sending...');
          const { enhancedInteractiveSend } = await import('./enhancedInteractiveMode.js');
          const result = await enhancedInteractiveSend();
          
          if (!result.success) {
            console.error(`Error: ${result.message}`);
            process.exit(1);
          }
          
          content = result.content;
          recipientName = result.recipient;
          options.temp = result.temp;
          options.pgp = result.pgp; // Use PGP flag from enhanced mode
          contentType = 'text/plain';
        } else {
          // Use standard interactive mode
          const result = await interactiveSend();
          content = result.content;
          recipientName = result.recipient;
          options.temp = result.temp;
          contentType = 'text/plain';
        }
      } else {
        // Fix for file option parsing - ensure it's correctly recognized
      if (!options.file && (process.argv.includes('--file') || process.argv.includes('-f'))) {
        // If Commander didn't parse it correctly, get the file path manually
        const fileArgIndex = Math.max(
          process.argv.indexOf('--file'), 
          process.argv.indexOf('-f')
        );
        
        if (fileArgIndex !== -1 && fileArgIndex < process.argv.length - 1) {
          options.file = process.argv[fileArgIndex + 1];
        }
      }
      
      // Additional PGP flag check for send command
      if (process.argv.includes('--pgp') && !options.pgp) {
        options.pgp = true;
      }
      
      // Determine if we're reading from a file or stdin
      if (options.file) {
        // Read from the specified file
        if (!fs.existsSync(options.file)) {
          console.error(`Error: File '${options.file}' does not exist`);
          process.exit(1);
        }
        
        content = fs.readFileSync(options.file);
        // Try to detect the content type from the file extension
        contentType = options.type || lookup(options.file) || 'text/plain';
      } else {
        // Read from stdin
        const stdinBuffer = [];
        for await (const chunk of process.stdin) {
          stdinBuffer.push(chunk);
        }
        
        if (stdinBuffer.length === 0) {
          console.error('Error: No input provided. Pipe content to dedpaste or use --file option.');
          process.exit(1);
        }
        
        content = Buffer.concat(stdinBuffer);
        contentType = options.type || 'text/plain';
      }
      }
      
      // Parse command line arguments and options
    
    // Check if encryption is requested (manually check for the flag)
    const shouldEncrypt = process.argv.includes('--encrypt') || process.argv.includes('-e');
    
    // Ensure file option is recognized correctly - check both parsed options and raw arguments
    if (!options.file && (process.argv.includes('--file') || process.argv.includes('-f'))) {
      // If Commander didn't parse it correctly, get the file path manually
      const fileArgIndex = Math.max(
        process.argv.indexOf('--file'), 
        process.argv.indexOf('-f')
      );
      
      if (fileArgIndex !== -1 && fileArgIndex < process.argv.length - 1) {
        options.file = process.argv[fileArgIndex + 1];
      }
    }
    
    // Handle encryption if requested
    if (shouldEncrypt) {
        
      // Generate new keys if requested
      if (options.genKey) {
        console.log('Generating new key pair');
        const { publicKeyPath, privateKeyPath } = await generateKeyPair();
        console.log(`
✓ Generated new key pair:
  - Private key: ${privateKeyPath}
  - Public key: ${publicKeyPath}
`);
      }
      
      // Encrypt the content
      try {
        // Check if PGP mode is requested
        const usePgp = options.pgp;
        
        // Validate encryption parameters
        
        // For PGP encryption, always require a recipient
        if (usePgp && !recipientName) {
          console.error(`Error: PGP encryption requires specifying a recipient with --for`);
          console.error(`Please use: dedpaste send --encrypt --pgp --for <recipient_name>`);
          process.exit(1);
        }
        
        // If PGP key file is provided, read it and use it directly
        if (options.pgpKeyFile) {
          if (!recipientName) {
            console.error(`Error: When using --pgp-key-file, you must specify a recipient with --for`);
            process.exit(1);
          }
          const pgpKeyContent = await fsPromises.readFile(options.pgpKeyFile, 'utf8');
          content = await createPgpEncryptedMessage(content, pgpKeyContent, recipientName);
        } else {
          // Use the standard encryption flow with PGP option
          content = await encryptContent(content, recipientName, usePgp);
        }
        
        // Log PGP mode if used
        if (usePgp || options.pgpKeyFile) {
          console.log('Using PGP encryption');
        }
        
        // Set content type to application/json for encrypted content
        contentType = 'application/json';
      } catch (error) {
        console.error(`Encryption failed: ${error.message}`);
        
        // Provide helpful suggestions for common errors
        if (error.message.includes('PGP key detected') && !options.pgp) {
          console.log('\nSuggestion: This key appears to be a PGP key. Try adding the --pgp flag:');
          console.log(`dedpaste send --encrypt --for ${recipientName} --pgp`);
        } else if (error.message.includes('requires specifying a recipient')) {
          console.log('\nFor PGP encryption, always specify a recipient:');
          console.log(`dedpaste send --encrypt --pgp --for <recipient_name>`);
        }
        
        process.exit(1);
      }
    } else {
      console.log('No encryption requested');
    }
      
      // Determine the endpoint based on whether it's a temporary paste and encrypted
      let endpoint;
      if (shouldEncrypt) {
        endpoint = options.temp ? '/e/temp' : '/e/upload';
      } else {
        endpoint = options.temp ? '/temp' : '/upload';
      }
      
      // In debug mode, just show the encrypted content
      if (options.debug && shouldEncrypt) {
        console.log('Debug mode: Showing encrypted content without uploading');
        console.log('Encrypted content (JSON):');
        console.log(content.toString());
        return;
      }
      
      // Make the API request
      try {
        const response = await fetch(`${API_URL}${endpoint}`, {
          method: 'POST',
          headers: {
            'Content-Type': contentType,
            'User-Agent': `dedpaste-cli/${packageJson.version}`
          },
          body: content
        });
        
        if (!response.ok) {
          console.error(`Error: ${response.status} ${response.statusText}`);
          const errorText = await response.text();
          console.error(errorText);
          process.exit(1);
        }
        
        const url = await response.text();
        
        // Copy to clipboard if requested
        if (options.copy) {
          try {
            const cleanUrl = url.trim();
            if (clipboard.default) {
              clipboard.default.writeSync(cleanUrl);
            } else {
              clipboard.writeSync(cleanUrl);
            }
          } catch (error) {
            console.error(`Unable to copy to clipboard: ${error.message}`);
          }
        }
        
        // Output the result
        if (options.output) {
          console.log(url.trim());
        } else {
          let encryptionMessage = '';
          if (shouldEncrypt) {
            if (recipientName) {
              encryptionMessage = `🔒 This paste is encrypted for ${recipientName} and can only be decrypted with their private key\n`;
            } else {
              encryptionMessage = '🔒 This paste is encrypted and can only be decrypted with your private key\n';
            }
          }
          
          console.log(`
✓ Paste created successfully!
${options.temp ? '⚠️  This is a one-time paste that will be deleted after first view\n' : ''}
${encryptionMessage}
${options.copy ? '📋 URL copied to clipboard: ' : '📋 '} ${url.trim()}
`);
        }
      } catch (error) {
        console.error(`Network error: ${error.message}`);
        console.error('If you just want to test encryption without uploading, use the --debug flag');
        process.exit(1);
      }
    } catch (error) {
      console.error('Error:', error.message);
      process.exit(1);
    }
  });

// Update the 'get' command to use the new decryption function
program
  .command('get')
  .description('Retrieve and decrypt a paste by URL or ID')
  .argument('<url-or-id>', 'URL or ID of the paste to retrieve (e.g., https://paste.d3d.dev/AbCdEfGh or just AbCdEfGh)')
  .option('--key-file <path>', 'Path to private key for decryption (if not using default key)')
  .option('--pgp-key-file <path>', 'Path to PGP private key for decryption')
  .option('--pgp-passphrase <passphrase>', 'Passphrase for PGP private key')
  .option('--use-gpg-keyring', 'Try to decrypt PGP messages using the system GPG keyring', true)
  .option('--no-gpg-keyring', 'Disable automatic GPG keyring decryption')
  .option('--show-metadata', 'Show detailed metadata about the encrypted message')
  .option('--interactive', 'Use interactive mode for decryption (will prompt for passphrase)')
  .addHelpText('after', `
Examples:
  $ dedpaste get https://paste.d3d.dev/AbCdEfGh        # Get a regular paste by URL
  $ dedpaste get AbCdEfGh                              # Get a regular paste by ID
  $ dedpaste get https://paste.d3d.dev/e/AbCdEfGh      # Get and decrypt an encrypted paste
  $ dedpaste get e/AbCdEfGh                            # Get and decrypt an encrypted paste by ID
  $ dedpaste get e/AbCdEfGh --interactive              # Use interactive mode with password prompt
  $ dedpaste get e/AbCdEfGh --show-metadata            # Show detailed metadata about the paste
  
PGP Decryption:
  $ dedpaste get e/AbCdEfGh --pgp-key-file my.pgp      # Decrypt with PGP private key
  $ dedpaste get e/AbCdEfGh --pgp-key-file my.pgp --pgp-passphrase "secret"
  $ dedpaste get e/AbCdEfGh                            # Automatically try GPG keyring for PGP content
  $ dedpaste get e/AbCdEfGh --no-gpg-keyring           # Disable automatic GPG keyring usage
  $ dedpaste get e/AbCdEfGh --interactive              # Interactive mode with passphrase prompt
  
URL Format:
  - Regular pastes: https://paste.d3d.dev/{id}
  - Encrypted pastes: https://paste.d3d.dev/e/{id}
  - The CLI automatically detects encrypted pastes and attempts decryption
  
Decryption:
  - Encrypted pastes are automatically decrypted if you have the correct private key
  - PGP encrypted pastes will first try the system GPG keyring (if available)
  - If GPG keyring fails, a PGP private key and passphrase will be required
  - Metadata about sender and creation time is displayed when available
  - One-time pastes are deleted from the server after viewing
  
GPG Keyring Integration:
  - By default, dedpaste will attempt to use your GPG keyring for PGP-encrypted pastes
  - This allows decryption without explicitly providing a private key file
  - Your system's gpg command is used to perform the decryption
  - To disable this feature, use the --no-gpg-keyring flag
`)
  .action(async (urlOrId, options) => {
    try {
      // Extract ID and check if it's an encrypted paste
      let id = urlOrId;
      let isEncrypted = false;
      
      // Parse the URL or ID
      if (urlOrId.startsWith('http')) {
        // It's a URL
        const url = new URL(urlOrId);
        const path = url.pathname;
        
        // Check if it's an encrypted paste
        const encryptedMatch = path.match(/^\/e\/([a-zA-Z0-9]{8})$/);
        if (encryptedMatch) {
          id = encryptedMatch[1];
          isEncrypted = true;
        } else {
          const regularMatch = path.match(/^\/([a-zA-Z0-9]{8})$/);
          if (regularMatch) {
            id = regularMatch[1];
          } else {
            console.error('Invalid paste URL format');
            process.exit(1);
          }
        }
      } else if (urlOrId.startsWith('e/') && urlOrId.length === 10) {
        // It's an encrypted ID in the format "e/AbCdEfGh"
        id = urlOrId.substring(2);
        isEncrypted = true;
      } else if (id.length === 8) {
        // It's just a regular ID
        isEncrypted = false;
      } else {
        console.error('Invalid paste ID format');
        console.error('Valid formats: https://paste.d3d.dev/AbCdEfGh, AbCdEfGh, https://paste.d3d.dev/e/AbCdEfGh, e/AbCdEfGh');
        process.exit(1);
      }
      
      // Determine the URL to fetch
      const fetchUrl = isEncrypted
        ? `${API_URL}/e/${id}`
        : `${API_URL}/${id}`;
      
      console.log(`Fetching paste from ${fetchUrl}...`);
      
      // Fetch the paste
      const response = await fetch(fetchUrl);
      
      if (!response.ok) {
        console.error(`Error: ${response.status} ${response.statusText}`);
        const errorText = await response.text();
        console.error(errorText);
        
        // Provide more helpful error messages
        if (response.status === 404) {
          console.error('The paste was not found. It may have been deleted or expired.');
        } else if (response.status === 429) {
          console.error('Too many requests. Please try again later.');
        }
        
        process.exit(1);
      }
      
      // Get the content
      const content = await response.arrayBuffer();
      const contentBuffer = Buffer.from(content);
      
      // If it's encrypted, decrypt it
      if (isEncrypted) {
        console.log('🔒 This paste is encrypted');
        
        try {
          let result;
          
          // Determine whether to use GPG keyring
          const useGpgKeyring = options.useGpgKeyring !== false;
          
          // Check if we're in interactive mode
          if (options.interactive) {
            console.log('Using interactive mode for decryption...');
            
            // Import the inquirer module
            const inquirer = await import('inquirer');
            
            // Check if PGP key file is provided or should be prompted
            let pgpKeyFile = options.pgpKeyFile;
            let passphrase = options.pgpPassphrase;
            
            // If no key file is provided but we're in interactive mode, ask if user wants to use one
            if (!pgpKeyFile) {
              const { usePgpKey } = await inquirer.default.prompt([{
                type: 'confirm',
                name: 'usePgpKey',
                message: 'Do you want to use a PGP private key file for decryption?',
                default: false
              }]);
              
              if (usePgpKey) {
                const { keyFile } = await inquirer.default.prompt([{
                  type: 'input',
                  name: 'keyFile',
                  message: 'Enter the path to your PGP private key file:',
                  validate: (input) => {
                    if (!input) return 'Path cannot be empty';
                    if (!fs.existsSync(input)) return 'File does not exist';
                    return true;
                  }
                }]);
                
                pgpKeyFile = keyFile;
              }
            }
            
            // If we're using a PGP key file and don't have a passphrase, prompt for it
            if (pgpKeyFile && !passphrase) {
              const { keyPassphrase } = await inquirer.default.prompt([{
                type: 'password',
                name: 'keyPassphrase',
                message: 'Enter the passphrase for your PGP private key:',
                mask: '*'
              }]);
              
              passphrase = keyPassphrase;
            }
            
            // Now we have all the information needed for decryption
            result = await decryptContent(contentBuffer, pgpKeyFile, passphrase, useGpgKeyring);
          } else {
            // Non-interactive mode
            
            // Check if PGP key file is provided
            if (options.pgpKeyFile) {
              // Use PGP decryption with specified key
              console.log('Using PGP decryption with provided key file');
              
              // Check for passphrase
              if (!options.pgpPassphrase) {
                console.error('Error: --pgp-passphrase is required when using --pgp-key-file in non-interactive mode');
                console.error('Tip: Use --interactive to be prompted for the passphrase');
                process.exit(1);
              }
              
              // Decrypt with PGP, with GPG keyring as fallback if enabled
              result = await decryptContent(contentBuffer, options.pgpKeyFile, options.pgpPassphrase, useGpgKeyring);
            } else {
              // Use standard decryption, with GPG keyring for PGP content if enabled
              if (useGpgKeyring) {
                console.log('GPG keyring integration is enabled for PGP content');
              }
              
              result = await decryptContent(contentBuffer, null, null, useGpgKeyring);
            }
          }
          
          // Display metadata if available
          if (result.metadata) {
            if (options.showMetadata) {
              // Show full metadata in a more structured format
              console.log('\n✨ Paste Metadata:');
              console.log('----------------');
              
              // Version info
              console.log(`Format Version: ${result.metadata.version || 'Unknown'}`);
              
              // Decryption method
              if (result.metadata.decryptedWith) {
                console.log(`Decrypted With: ${result.metadata.decryptedWith}`);
              }
              
              // Sender info
              if (result.metadata.sender) {
                console.log(`Sender: ${result.metadata.sender}`);
              }
              
              // Recipient info
              if (result.metadata.recipient) {
                console.log('\nRecipient Info:');
                const recipient = result.metadata.recipient;
                
                if (recipient.name) console.log(`- Name: ${recipient.name}`);
                if (recipient.email) console.log(`- Email: ${recipient.email}`);
                if (recipient.keyId) console.log(`- Key ID: ${recipient.keyId}`);
                if (recipient.fingerprint) console.log(`- Fingerprint: ${recipient.fingerprint}`);
                if (recipient.type) console.log(`- Key Type: ${recipient.type}`);
              }
              
              // Timestamp
              if (result.metadata.timestamp) {
                console.log(`\nCreated: ${new Date(result.metadata.timestamp).toLocaleString()}`);
              }
              
              // PGP-specific info
              if (result.metadata.pgp) {
                console.log('\nPGP-Specific Information:');
                console.log('- Message uses OpenPGP encryption');
                
                if (result.metadata.keyId) {
                  console.log(`- Decryption Key ID: ${result.metadata.keyId}`);
                }
              }
              
              // Signature info if available
              if (result.metadata.signature) {
                console.log('\nSignature Information:');
                const sig = result.metadata.signature;
                
                if (sig.valid === true) {
                  console.log('- ✓ Valid signature');
                } else if (sig.valid === null) {
                  console.log('- ? Unverified signature');
                } else {
                  console.log('- ✗ Invalid signature');
                }
                
                if (sig.keyId) console.log(`- Signed by key ID: ${sig.keyId}`);
                if (sig.created) console.log(`- Signed on: ${new Date(sig.created).toLocaleString()}`);
              }
              
              console.log('----------------\n');
            } else {
              // Show just the basic info
              // Handle version 2 (standard RSA/AES encryption)
              if (result.metadata.version === 2) {
                if (result.metadata.sender && result.metadata.sender !== 'self') {
                  console.log(`Sender: ${result.metadata.sender}`);
                }
                
                if (result.metadata.timestamp) {
                  console.log(`Created: ${new Date(result.metadata.timestamp).toLocaleString()}`);
                }
              } 
              // Handle PGP-specific format (version 3)
              else if (result.metadata.pgp) {
                console.log(`PGP encrypted message`);
                
                if (result.metadata.recipient && result.metadata.recipient.name) {
                  console.log(`Recipient: ${result.metadata.recipient.name}`);
                }
                
                if (result.metadata.recipient && result.metadata.recipient.email) {
                  console.log(`Email: ${result.metadata.recipient.email}`);
                }
                
                if (result.metadata.recipient && result.metadata.recipient.keyId) {
                  console.log(`Key ID: ${result.metadata.recipient.keyId}`);
                }
                
                if (result.metadata.timestamp) {
                  console.log(`Created: ${new Date(result.metadata.timestamp).toLocaleString()}`);
                }
              }
            }
          }
          
          console.log('\n✓ Paste decrypted successfully:\n');
          process.stdout.write(result.content);
          
          // Add a newline at the end if the content doesn't end with one
          if (result.content.length > 0 && result.content[result.content.length - 1] !== 10) { // ASCII 10 is newline
            process.stdout.write(Buffer.from('\n'));
          }
        } catch (error) {
          console.error(`\n❌ Decryption error: ${error.message}`);
          
          // Provide more useful suggestions based on the error type
          if (error.message.includes('No personal key found')) {
            console.error('\nTip: You need to generate a key pair first. Run:');
            console.error('  dedpaste keys --gen-key');
          } else if (error.message.includes('passphrase')) {
            console.error('\nTip: Try using --interactive mode to be prompted for the passphrase');
          } else if (error.message.includes('not a PGP')) {
            console.error('\nTip: This paste appears to use standard RSA/AES encryption, not PGP');
          } else if (error.message.includes('GPG keyring')) {
            console.error('\nTip: Make sure GPG is installed and you have the required private key');
            // Check if we have keyId information to help the user
            if (error.keyIds && error.keyIds.length > 0) {
              console.error('\nThis message was encrypted for:');
              error.keyIds.forEach(key => {
                console.error(`- ${key.type} key ID: ${key.id}`);
              });
              console.error('\nYou need one of these keys in your GPG keyring to decrypt this message');
            }
          }
          
          process.exit(1);
        }
      } else {
        // Just output the content
        process.stdout.write(contentBuffer);
        
        // Add a newline at the end if the content doesn't end with one
        if (contentBuffer.length > 0 && contentBuffer[contentBuffer.length - 1] !== 10) { // ASCII 10 is newline
          process.stdout.write(Buffer.from('\n'));
        }
      }
    } catch (error) {
      console.error('Error:', error.message);
      process.exit(1);
    }
  });

// Add options for the default command (no subcommand)
program
  .option('-t, --temp', 'Create a one-time paste that is deleted after being viewed')
  .option('--type <content-type>', 'Specify the content type of the paste (e.g., application/json)')
  .option('-f, --file <path>', 'Upload a file from the specified path instead of stdin')
  .option('-o, --output', 'Print only the URL (without any additional text)')
  .option('-e, --encrypt', 'Encrypt the content before uploading (requires key setup)')
  .option('-c, --copy', 'Copy the URL to clipboard automatically')
  .option('--interactive', 'Use interactive mode with guided prompts for message creation')
  .option('--enhanced', 'Use enhanced interactive mode with advanced key selection features')
  .option('--pgp', 'Use PGP encryption instead of hybrid RSA/AES')
  .addHelpText('after', `
Default Command Examples:
  $ echo "Hello, world!" | dedpaste                    # Create a basic paste
  $ dedpaste --file document.txt                       # Upload a file
  $ echo "Secret data" | dedpaste --encrypt            # Create an encrypted paste
  $ echo "Secret data" | dedpaste --encrypt --for alice --pgp # Create a PGP-encrypted paste for a recipient
  $ dedpaste --file image.jpg --type image/jpeg        # Upload with specific content type
  $ echo "One-time message" | dedpaste --temp          # Create a one-time paste
  $ dedpaste --interactive --encrypt                   # Interactive encrypted message creation
  $ dedpaste --enhanced --encrypt                      # Enhanced interactive mode with GPG support
  
For more advanced options, use the subcommands:
  $ dedpaste keys --help                               # Key management options
  $ dedpaste send --help                               # Advanced sending options (Recommended for PGP)
  $ dedpaste get --help                                # Retrieval options

NOTE: For PGP encryption, always use the 'send' command and specify a recipient with --for
`);

// Add a default command that handles unencrypted pastes
program
  .action(async (options) => {
    // Only show help if no arguments are provided and no stdin (user is at a TTY)
    if (process.argv.length <= 2 && process.stdin.isTTY) {
      program.help();
      return;
    }

    // Process command line options and arguments

    let content;
    let contentType;
    
    try {
      // Check if we should use enhanced interactive mode
      if (options.interactive && options.enhanced) {
        console.log('Using enhanced interactive mode...');
        try {
          const { enhancedInteractiveSend } = await import('./enhancedInteractiveMode.js');
          const result = await enhancedInteractiveSend();
          
          if (!result.success) {
            console.error(`Error: ${result.message}`);
            process.exit(1);
          }
          
          content = result.content;
          options.temp = result.temp;
          options.pgp = result.pgp;
          contentType = 'text/plain';
        } catch (error) {
          console.error(`Error in enhanced interactive mode: ${error.message}`);
          process.exit(1);
        }
      } else {
        // Regular flow - determine if we're reading from a file or stdin
        if (options.file) {
          // Read from the specified file
          if (!fs.existsSync(options.file)) {
            console.error(`Error: File '${options.file}' does not exist`);
            process.exit(1);
          }
        
          content = fs.readFileSync(options.file);
          // Try to detect the content type from the file extension
          contentType = options.type || lookup(options.file) || 'text/plain';
        } else {
          // Read from stdin
          const stdinBuffer = [];
          for await (const chunk of process.stdin) {
            stdinBuffer.push(chunk);
          }
          
          if (stdinBuffer.length === 0) {
            console.error('Error: No input provided. Pipe content to dedpaste or use --file option.');
            process.exit(1);
          }
          
          content = Buffer.concat(stdinBuffer);
          contentType = options.type || 'text/plain';
        }
      }
      
      // Process command line arguments
      
      // Check if encryption is requested (manually check for the flag)
      const shouldEncrypt = process.argv.includes('--encrypt') || process.argv.includes('-e');
      
      // Fix for file option parsing - ensure it's correctly recognized
      if (!options.file && (process.argv.includes('--file') || process.argv.includes('-f'))) {
        // If Commander didn't parse it correctly, get the file path manually
        const fileArgIndex = Math.max(
          process.argv.indexOf('--file'), 
          process.argv.indexOf('-f')
        );
        
        if (fileArgIndex !== -1 && fileArgIndex < process.argv.length - 1) {
          options.file = process.argv[fileArgIndex + 1];
          
          // Since we're handling this manually, recheck file existence
          if (!fs.existsSync(options.file)) {
            console.error(`Error: File '${options.file}' does not exist`);
            process.exit(1);
          }
          
          // Re-read content from file since we updated the option
          content = fs.readFileSync(options.file);
          contentType = options.type || lookup(options.file) || 'text/plain';
        }
      }
      
      // Additional PGP flag check
      const usePgp = options.pgp || process.argv.includes('--pgp');
      if (usePgp && !options.pgp) {
        options.pgp = true;
      }
      
      // Handle encryption if requested
      if (shouldEncrypt) {
        // We need to use the full send command implementation here
        // rather than trying to redirect to it, since we would lose stdin
        
        try {
          // Validate encryption parameters
          
          // If PGP is selected, validate required parameters
          if (usePgp) {
            if (!options.for) {
              console.error(`Error: PGP encryption requires specifying a recipient with --for.`);
              console.error(`Please use: dedpaste send --encrypt --pgp --for <recipient_name>`);
              process.exit(1);
            }
          }
          
          // If PGP key file is provided, read it and use it directly
          if (options.pgpKeyFile) {
            if (!options.for) {
              console.error(`Error: When using --pgp-key-file, you must specify a recipient with --for`);
              process.exit(1);
            }
            const pgpKeyContent = await fsPromises.readFile(options.pgpKeyFile, 'utf8');
            content = await createPgpEncryptedMessage(content, pgpKeyContent, options.for);
          } else {
            // Use the standard encryption flow with PGP option
            content = await encryptContent(content, options.for, usePgp);
          }
          
          // Log PGP mode if used
          if (usePgp || options.pgpKeyFile) {
            console.log('Using PGP encryption');
          }
          
          // Set content type to application/json for encrypted content
          contentType = 'application/json';
        } catch (error) {
          console.error(`Encryption failed: ${error.message}`);
          console.error(`\nFor PGP encryption, use: dedpaste send --encrypt --pgp --for <recipient_name>`);
          process.exit(1);
        }
        
        // Determine the endpoint for encrypted pastes
        const endpoint = options.temp ? '/e/temp' : '/e/upload';
        
        try {
          // Make the API request
          const response = await fetch(`${API_URL}${endpoint}`, {
            method: 'POST',
            headers: {
              'Content-Type': contentType,
              'User-Agent': `dedpaste-cli/${packageJson.version}`
            },
            body: content
          });
          
          if (!response.ok) {
            console.error(`Error: ${response.status} ${response.statusText}`);
            const errorText = await response.text();
            console.error(errorText);
            process.exit(1);
          }
          
          const url = await response.text();
          
          // Copy to clipboard if requested
          if (options.copy) {
            try {
              const cleanUrl = url.trim();
              if (clipboard.default) {
                clipboard.default.writeSync(cleanUrl);
              } else {
                clipboard.writeSync(cleanUrl);
              }
            } catch (error) {
              console.error(`Unable to copy to clipboard: ${error.message}`);
            }
          }
          
          // Output the result with encryption notice
          if (options.output) {
            console.log(url.trim());
          } else {
            console.log(`
✓ Paste created successfully!
${options.temp ? '⚠️  This is a one-time paste that will be deleted after first view\n' : ''}
🔒 This paste is encrypted and can only be decrypted with your private key

${options.copy ? '📋 URL copied to clipboard: ' : '📋 '} ${url.trim()}
`);
          }
          return;
        } catch (error) {
          console.error(`Network error: ${error.message}`);
          process.exit(1);
        }
      }
      
      // Determine the endpoint based on whether it's a temporary paste
      const endpoint = options.temp ? '/temp' : '/upload';
      
      try {
        // Make the API request
        const response = await fetch(`${API_URL}${endpoint}`, {
          method: 'POST',
          headers: {
            'Content-Type': contentType,
            'User-Agent': `dedpaste-cli/${packageJson.version}`
          },
          body: content
        });
        
        if (!response.ok) {
          console.error(`Error: ${response.status} ${response.statusText}`);
          const errorText = await response.text();
          console.error(errorText);
          process.exit(1);
        }
        
        const url = await response.text();
        
        // Copy to clipboard if requested
        if (options.copy) {
          try {
            const cleanUrl = url.trim();
            if (clipboard.default) {
              clipboard.default.writeSync(cleanUrl);
            } else {
              clipboard.writeSync(cleanUrl);
            }
          } catch (error) {
            console.error(`Unable to copy to clipboard: ${error.message}`);
          }
        }
        
        // Output the result
        if (options.output) {
          console.log(url.trim());
        } else {
          console.log(`
✓ Paste created successfully!
${options.temp ? '⚠️  This is a one-time paste that will be deleted after first view\n' : ''}
${options.copy ? '📋 URL copied to clipboard: ' : '📋 '} ${url.trim()}
`);
        }
      } catch (error) {
        console.error(`Network error: ${error.message}`);
        process.exit(1);
      }
    } catch (error) {
      console.error('Error:', error.message);
      process.exit(1);
    }
  });

// Add a command for shell completion setup
program
  .command('completion')
  .description('Generate shell completion script')
  .option('--bash', 'Output Bash completion script')
  .option('--zsh', 'Output Zsh completion script')
  .option('--install', 'Print installation instructions instead of the script')
  .addHelpText('after', `
Examples:
  $ dedpaste completion --bash            # Output Bash completion script
  $ dedpaste completion --zsh             # Output Zsh completion script
  $ dedpaste completion --bash --install  # Show Bash installation instructions
  $ dedpaste completion --zsh --install   # Show Zsh installation instructions
  
Installation:
  # For Bash (add to your ~/.bashrc):
  dedpaste completion --bash > ~/.dedpaste-completion.bash
  echo 'source ~/.dedpaste-completion.bash' >> ~/.bashrc
  
  # For Zsh (add to your ~/.zshrc):
  dedpaste completion --zsh > ~/.dedpaste-completion.zsh
  echo 'source ~/.dedpaste-completion.zsh' >> ~/.zshrc
`)
  .action(async (options) => {
    try {
      // Check if we should output for bash or zsh
      if (!options.bash && !options.zsh) {
        console.error("Error: You must specify either --bash or --zsh");
        process.exit(1);
      }
      
      if (options.bash && options.zsh) {
        console.error("Error: Please specify only one shell type (--bash or --zsh)");
        process.exit(1);
      }
      
      // Installation instructions
      if (options.install) {
        if (options.bash) {
          console.log(`
# Bash Completion Installation Instructions

# Option 1: Source directly in your shell profile
dedpaste completion --bash > ~/.dedpaste-completion.bash
echo 'source ~/.dedpaste-completion.bash' >> ~/.bashrc

# Option 2: Install system-wide (if you have bash-completion installed)
# On macOS with Homebrew:
dedpaste completion --bash | sudo tee /usr/local/etc/bash_completion.d/dedpaste > /dev/null

# On Linux (Ubuntu/Debian):
dedpaste completion --bash | sudo tee /etc/bash_completion.d/dedpaste > /dev/null

# Don't forget to restart your shell or source your profile:
source ~/.bashrc
`);
        } else if (options.zsh) {
          console.log(`
# Zsh Completion Installation Instructions

# Option 1: Source directly in your shell profile
dedpaste completion --zsh > ~/.dedpaste-completion.zsh
echo 'source ~/.dedpaste-completion.zsh' >> ~/.zshrc

# Option 2: Install to your fpath
mkdir -p ~/.zsh/completions
dedpaste completion --zsh > ~/.zsh/completions/_dedpaste
echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc

# Make sure you have compinit loaded in your .zshrc
# If not already there, add:
# autoload -Uz compinit
# compinit

# Don't forget to restart your shell or source your profile:
source ~/.zshrc
`);
        }
        return;
      }
      
      // Generate bash completion script
      if (options.bash) {
        console.log(`#!/usr/bin/env bash

_dedpaste_completions() {
  local cur prev opts commands
  COMPREPLY=()
  cur="\${COMP_WORDS[COMP_CWORD]}"
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
  commands="keys send get completion"
  main_opts="-t --temp --type -f --file -o --output -e --encrypt -c --copy --help --version"
  
  # Check if we're completing a command
  if [[ \${COMP_CWORD} -eq 1 ]]; then
    if [[ \${cur} == -* ]]; then
      COMPREPLY=( $(compgen -W "\${main_opts}" -- "\${cur}") )
      return 0
    else
      COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
      return 0
    fi
  fi
  
  # Complete options for specific commands
  case "\${COMP_WORDS[1]}" in
    keys)
      local key_opts="--interactive --list --add-friend --key-file --export --remove --gen-key --my-key --help"
      if [[ \${cur} == -* ]]; then
        COMPREPLY=( $(compgen -W "\${key_opts}" -- "\${cur}") )
      elif [[ \${prev} == "--key-file" ]]; then
        COMPREPLY=( $(compgen -f -- "\${cur}") )
      fi
      return 0
      ;;
    send)
      local send_opts="-t --temp --type -f --file -o --output -e --encrypt --for --list-friends --key-file --gen-key --interactive --debug -c --copy --help"
      if [[ \${cur} == -* ]]; then
        COMPREPLY=( $(compgen -W "\${send_opts}" -- "\${cur}") )
      elif [[ \${prev} == "--key-file" || \${prev} == "-f" || \${prev} == "--file" ]]; then
        COMPREPLY=( $(compgen -f -- "\${cur}") )
      elif [[ \${prev} == "--type" ]]; then
        local content_types="text/plain application/json text/html text/markdown application/xml application/javascript text/css"
        COMPREPLY=( $(compgen -W "\${content_types}" -- "\${cur}") )
      fi
      return 0
      ;;
    get)
      local get_opts="--key-file --help"
      if [[ \${cur} == -* ]]; then
        COMPREPLY=( $(compgen -W "\${get_opts}" -- "\${cur}") )
      elif [[ \${prev} == "--key-file" ]]; then
        COMPREPLY=( $(compgen -f -- "\${cur}") )
      fi
      return 0
      ;;
    completion)
      local completion_opts="--bash --zsh --install --help"
      if [[ \${cur} == -* ]]; then
        COMPREPLY=( $(compgen -W "\${completion_opts}" -- "\${cur}") )
      fi
      return 0
      ;;
    *)
      if [[ \${cur} == -* ]]; then
        COMPREPLY=( $(compgen -W "\${main_opts}" -- "\${cur}") )
      elif [[ \${prev} == "--key-file" || \${prev} == "-f" || \${prev} == "--file" ]]; then
        COMPREPLY=( $(compgen -f -- "\${cur}") )
      elif [[ \${prev} == "--type" ]]; then
        local content_types="text/plain application/json text/html text/markdown application/xml application/javascript text/css"
        COMPREPLY=( $(compgen -W "\${content_types}" -- "\${cur}") )
      fi
      return 0
      ;;
  esac
}

complete -F _dedpaste_completions dedpaste`);
      }
      
      // Generate zsh completion script
      if (options.zsh) {
        console.log(`#compdef dedpaste

_dedpaste() {
  local -a commands
  local -a main_opts
  
  commands=(
    'keys:Manage encryption keys for secure communication'
    'send:Create and send an encrypted paste to friends'
    'get:Retrieve and decrypt a paste by URL or ID'
    'completion:Generate shell completion script'
  )
  
  main_opts=(
    '(-t --temp)'{-t,--temp}'[Create a one-time paste that is deleted after being viewed]'
    '--type[Specify the content type of the paste (e.g., application/json)]:content-type:(text/plain application/json text/html text/markdown application/xml application/javascript text/css)'
    '(-f --file)'{-f,--file}'[Upload a file from the specified path instead of stdin]:file:_files'
    '(-o --output)'{-o,--output}'[Print only the URL (without any additional text)]'
    '(-e --encrypt)'{-e,--encrypt}'[Encrypt the content before uploading (requires key setup)]'
    '(-c --copy)'{-c,--copy}'[Copy the URL to clipboard automatically]'
    '--help[Show help message]'
    '--version[Show version information]'
  )
  
  _arguments -C \\
    '1: :->command' \\
    '*: :->args' && ret=0
    
  case $state in
    (command)
      _describe -t commands 'dedpaste commands' commands
      _describe -t options 'dedpaste options' main_opts
      ;;
    (args)
      case $line[1] in
        (keys)
          _arguments \\
            '--interactive[Use interactive menu-driven mode for key management]' \\
            '--list[List all your keys and friends keys with fingerprints]' \\
            '--add-friend[Add a friends public key (requires --key-file)]:friend name:' \\
            '--key-file[Path to key file for import/export operations]:key file:_files' \\
            '--export[Export your public key to share with friends]' \\
            '--remove[Remove a friends key from your keyring]:friend name:' \\
            '--gen-key[Generate a new RSA key pair for encryption]' \\
            '--my-key[Output your public key to the console for sharing]' \\
            '--help[Show help message]'
          ;;
        (send)
          _arguments \\
            '(-t --temp)'{-t,--temp}'[Create a one-time paste that is deleted after being viewed]' \\
            '--type[Specify the content type of the paste (e.g., application/json)]:content-type:(text/plain application/json text/html text/markdown application/xml application/javascript text/css)' \\
            '(-f --file)'{-f,--file}'[Upload a file from the specified path instead of stdin]:file:_files' \\
            '(-o --output)'{-o,--output}'[Print only the URL (without any additional text)]' \\
            '(-e --encrypt)'{-e,--encrypt}'[Encrypt the content before uploading (requires key setup)]' \\
            '--for[Encrypt for a specific friend (requires adding their key first)]:friend name:' \\
            '--list-friends[List available friends you can encrypt messages for]' \\
            '--key-file[Path to public key for encryption (alternative to stored keys)]:key file:_files' \\
            '--gen-key[Generate a new key pair for encryption if you dont have one]' \\
            '--interactive[Use interactive mode with guided prompts for message creation]' \\
            '--debug[Debug mode: show encrypted content without uploading]' \\
            '(-c --copy)'{-c,--copy}'[Copy the URL to clipboard automatically]' \\
            '--help[Show help message]'
          ;;
        (get)
          _arguments \\
            '1:url or ID:' \\
            '--key-file[Path to private key for decryption (if not using default key)]:key file:_files' \\
            '--help[Show help message]'
          ;;
        (completion)
          _arguments \\
            '--bash[Output Bash completion script]' \\
            '--zsh[Output Zsh completion script]' \\
            '--install[Print installation instructions instead of the script]' \\
            '--help[Show help message]'
          ;;
      esac
      ;;
  esac
}

_dedpaste "$@"`);
      }
    } catch (error) {
      console.error('Error:', error.message);
      process.exit(1);
    }
  });

program.parse();