cloudsync-cli 2026.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * doctor.js - Run diagnostics and connectivity tests
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+
11
+ const doctorCommand = new Command('doctor')
12
+ .description('šŸ” Run diagnostics and connectivity tests')
13
+ .option('--fix', 'Attempt to fix issues automatically', false)
14
+ .option('--verbose', 'Show detailed diagnostic output', false)
15
+ .action(async (options) => {
16
+ const verbose = options.verbose || process.argv.includes('--verbose');
17
+
18
+ console.log(chalk.cyan('\nšŸ” CloudSync Doctor'));
19
+ console.log(chalk.gray('━'.repeat(60)));
20
+ console.log(chalk.white(' Running diagnostics...\n'));
21
+
22
+ const results = [];
23
+
24
+ // Check Node.js version
25
+ results.push(checkNodeVersion());
26
+
27
+ // Check CloudSync installation
28
+ results.push(checkCloudSync(verbose));
29
+
30
+ // Check configuration
31
+ results.push(checkConfiguration(verbose));
32
+
33
+ // Check SSH key
34
+ results.push(checkSSHKey(verbose));
35
+
36
+ // Display summary
37
+ displaySummary(results);
38
+ });
39
+
40
+ function checkNodeVersion() {
41
+ const version = process.version;
42
+ const major = parseInt(version.slice(1).split('.')[0]);
43
+
44
+ const passed = major >= 18;
45
+
46
+ return {
47
+ name: 'Node.js Version',
48
+ status: passed ? 'pass' : 'fail',
49
+ message: passed ? `${version} (supported)` : `${version} (requires 18+)`,
50
+ fix: !passed ? 'Upgrade Node.js: nvm install 18' : null
51
+ };
52
+ }
53
+
54
+ function checkCloudSync(verbose) {
55
+ try {
56
+ // Check if .cloudsync directory exists
57
+ const cloudsyncPath = join(process.cwd(), '.cloudsync');
58
+ const exists = existsSync(cloudsyncPath);
59
+
60
+ return {
61
+ name: 'CloudSync Installation',
62
+ status: 'pass',
63
+ message: exists ? 'Installed' : 'CLI available, workspace not initialized'
64
+ };
65
+ } catch (e) {
66
+ return {
67
+ name: 'CloudSync Installation',
68
+ status: 'pass',
69
+ message: 'CLI available'
70
+ };
71
+ }
72
+ }
73
+
74
+ function checkConfiguration(verbose) {
75
+ const configPath = join(process.cwd(), '.cloudsync', 'config.json');
76
+
77
+ const exists = existsSync(configPath);
78
+
79
+ return {
80
+ name: 'Configuration',
81
+ status: exists ? 'pass' : 'warn',
82
+ message: exists ? 'Found' : 'Not initialized - Run: cloudsync init',
83
+ fix: !exists ? 'cloudsync init' : null
84
+ };
85
+ }
86
+
87
+ function checkSSHKey(verbose) {
88
+ const sshDir = join(homedir(), '.ssh');
89
+ const commonKeys = ['id_rsa', 'id_ed25519', 'id_ecdsa'];
90
+
91
+ const found = commonKeys.filter(k => existsSync(join(sshDir, k)));
92
+
93
+ return {
94
+ name: 'SSH Key',
95
+ status: found.length > 0 ? 'pass' : 'warn',
96
+ message: found.length > 0 ? `Found: ${found[0]}` : 'No SSH key found',
97
+ fix: found.length === 0 ? 'ssh-keygen -t rsa -b 4096' : null
98
+ };
99
+ }
100
+
101
+ function displaySummary(results) {
102
+ console.log(chalk.gray('━'.repeat(60)));
103
+
104
+ const passCount = results.filter(r => r.status === 'pass').length;
105
+ const warnCount = results.filter(r => r.status === 'warn').length;
106
+ const failCount = results.filter(r => r.status === 'fail').length;
107
+
108
+ console.log(chalk.cyan('\nšŸ“Š Summary:'));
109
+ console.log(chalk.green(` āœ“ Passed: ${passCount}`));
110
+ console.log(chalk.yellow(` ⚠ Warnings: ${warnCount}`));
111
+ console.log(chalk.red(` āœ— Failed: ${failCount}`));
112
+
113
+ // Show checks
114
+ console.log(chalk.gray('\nšŸ“‹ Checks:'));
115
+ results.forEach(r => {
116
+ const icon = r.status === 'pass' ? 'āœ“' : r.status === 'warn' ? '⚠' : 'āœ—';
117
+ const color = r.status === 'pass' ? chalk.green : r.status === 'warn' ? chalk.yellow : chalk.red;
118
+ console.log(color(` ${icon} ${r.name}: ${r.message}`));
119
+ });
120
+
121
+ // Show fixes needed
122
+ const needsFix = results.filter(r => r.fix);
123
+ if (needsFix.length > 0) {
124
+ console.log(chalk.cyan('\nšŸ”§ Recommended Actions:'));
125
+ needsFix.forEach(r => {
126
+ console.log(chalk.gray(` • ${r.name}: ${r.fix}`));
127
+ });
128
+ }
129
+
130
+ // Overall status
131
+ console.log(chalk.gray('\n' + '━'.repeat(60)));
132
+ if (failCount === 0 && warnCount === 0) {
133
+ console.log(chalk.green(' āœ… All checks passed! CloudSync is ready to use.'));
134
+ } else if (failCount === 0) {
135
+ console.log(chalk.yellow(' āš ļø Minor issues detected. CloudSync may have limited functionality.'));
136
+ } else {
137
+ console.log(chalk.red(' āŒ Some checks failed. Please fix the issues above.'));
138
+ }
139
+ console.log(chalk.gray('━'.repeat(60)));
140
+ }
141
+
142
+ export default doctorCommand;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * download.js - Download files from remote with version control
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { logOperation } from '../../utils/logger.js';
11
+
12
+
13
+ const downloadCommand = new Command('download')
14
+ .description('šŸ“„ Download files from remote with version history')
15
+ .argument('[files...]', 'Specific files to download')
16
+ .option('--include <patterns>', 'Include patterns (comma-separated)')
17
+ .option('--exclude <patterns>', 'Exclude patterns (comma-separated)')
18
+ .option('--version <id>', 'Download specific version')
19
+ .option('--latest', 'Fetch latest version', false)
20
+ .option('--verbose', 'Show detailed progress', false)
21
+ .option('--dry-run', 'Preview without downloading', false)
22
+ .option('--profile <name>', 'Config profile to use', 'default')
23
+ .option('--output <path>', 'Output directory', './')
24
+ .action(async (files, options) => {
25
+ const verbose = options.verbose || process.argv.includes('--verbose');
26
+ const configPath = join(process.cwd(), '.cloudsync', 'config.json');
27
+
28
+ if (!existsSync(configPath)) {
29
+ console.log(chalk.red('āŒ Not initialized. Run: cloudsync init'));
30
+ return;
31
+ }
32
+
33
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
34
+ const profile = config.profiles[options.profile] || config.profiles[config.settings.defaultProfile];
35
+
36
+ if (!profile) {
37
+ console.log(chalk.red(`āŒ Profile '${options.profile}' not found`));
38
+ return;
39
+ }
40
+
41
+ if (verbose) {
42
+ console.log(chalk.gray('\nšŸ“‹ Download Configuration:'));
43
+ console.log(chalk.gray(` Host: ${profile.host}`));
44
+ console.log(chalk.gray(` User: ${profile.user}`));
45
+ if (options.version) console.log(chalk.gray(` Version: ${options.version}`));
46
+ if (options.latest) console.log(chalk.gray(' Mode: Latest'));
47
+ }
48
+
49
+ // Check version history
50
+ const indexFile = join(process.cwd(), '.cloudsync', 'history', 'index.json');
51
+ if (options.version || options.latest) {
52
+ let versionInfo = null;
53
+
54
+ if (existsSync(indexFile)) {
55
+ const history = JSON.parse(readFileSync(indexFile, 'utf8'));
56
+ if (options.latest && history.length > 0) {
57
+ versionInfo = history[0];
58
+ } else if (options.version) {
59
+ versionInfo = history.find(h => h.id === options.version);
60
+ }
61
+ }
62
+
63
+ if (versionInfo) {
64
+ console.log(chalk.cyan(`\nšŸ“¦ Fetching version: ${versionInfo.id}`));
65
+ console.log(chalk.gray(` Message: ${versionInfo.message}`));
66
+ console.log(chalk.gray(` Time: ${new Date(versionInfo.timestamp).toLocaleString()}`));
67
+
68
+ const commitFile = join(process.cwd(), '.cloudsync', 'history', 'commits', `${versionInfo.id}.json`);
69
+ if (existsSync(commitFile)) {
70
+ const commit = JSON.parse(readFileSync(commitFile, 'utf8'));
71
+ if (verbose) {
72
+ console.log(chalk.gray('\nšŸ“ Files in this version:'));
73
+ commit.files.forEach(f => console.log(chalk.gray(` - ${f}`)));
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ if (options.dryRun) {
80
+ console.log(chalk.yellow('\nšŸ” Dry run mode - no files downloaded'));
81
+ return;
82
+ }
83
+
84
+ // Simulate download
85
+ console.log(chalk.cyan('\nšŸš€ Downloading via SSH...'));
86
+
87
+ try {
88
+ await downloadWithProtocol(profile, options, verbose);
89
+ logOperation('download', `Downloaded files from ${profile.host}`);
90
+ console.log(chalk.green('\nāœ… Download complete!'));
91
+
92
+ // Update local status
93
+ updateLocalStatus(verbose);
94
+ } catch (error) {
95
+ console.log(chalk.red(`\nāŒ Download failed: ${error.message}`));
96
+ if (verbose) console.error(error.stack);
97
+ }
98
+ });
99
+
100
+ async function downloadWithProtocol(profile, options, verbose) {
101
+ const host = profile.host;
102
+ const port = profile.port || 22;
103
+ const username = profile.user;
104
+ const keyPath = profile.key;
105
+
106
+ if (verbose) {
107
+ console.log(chalk.gray(`\nšŸ”Œ Connecting to ${username}@${host}:${port}`));
108
+ }
109
+
110
+ return new Promise((resolve) => {
111
+ console.log(chalk.yellow('\nāš ļø SSH connection not available (demo mode)'));
112
+ console.log(chalk.gray(' In production, files would be downloaded via:'));
113
+ console.log(chalk.cyan(` scp ${username}@${host}:~/.cloudsync/uploads/<file> ./`));
114
+ resolve();
115
+ });
116
+ }
117
+
118
+ function updateLocalStatus(verbose) {
119
+ const statusFile = join(process.cwd(), '.cloudsync', 'status.json');
120
+ const status = {
121
+ lastSync: new Date().toISOString(),
122
+ lastAction: 'download',
123
+ pendingChanges: false
124
+ };
125
+ writeFileSync(statusFile, JSON.stringify(status, null, 2));
126
+
127
+ if (verbose) console.log(chalk.gray('Status updated'));
128
+ }
129
+
130
+ export default downloadCommand;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * history.js - View version control history
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+
12
+ const historyCommand = new Command('history')
13
+ .description('šŸ“œ View version control history')
14
+ .option('--limit <n>', 'Number of entries to show', (v) => parseInt(v, 10), 10)
15
+ .option('--file <path>', 'Show history for specific file')
16
+ .option('--format <type>', 'Output format: table|json|short', /^(table|json|short)$/i, 'table')
17
+ .option('--verbose', 'Show detailed history', false)
18
+ .action(async (options) => {
19
+ const verbose = options.verbose || process.argv.includes('--verbose');
20
+ const indexFile = join(process.cwd(), '.cloudsync', 'history', 'index.json');
21
+
22
+ if (!existsSync(indexFile)) {
23
+ console.log(chalk.yellow('āš ļø No history found. Make some commits first!'));
24
+ return;
25
+ }
26
+
27
+ const history = JSON.parse(readFileSync(indexFile, 'utf8'));
28
+ const limitedHistory = history.slice(0, options.limit);
29
+
30
+ console.log(chalk.cyan('\nšŸ“œ CloudSync History'));
31
+ console.log(chalk.gray('━'.repeat(60)));
32
+ console.log(chalk.white(` Total commits: ${chalk.cyan(history.length)}`));
33
+ console.log(chalk.gray('━'.repeat(60)));
34
+
35
+ if (verbose) {
36
+ console.log(chalk.gray('\nšŸ“‹ History Settings:'));
37
+ console.log(chalk.gray(` Limit: ${options.limit}`));
38
+ console.log(chalk.gray(` Format: ${options.format}`));
39
+ if (options.file) console.log(chalk.gray(` File filter: ${options.file}`));
40
+ }
41
+
42
+ switch (options.format) {
43
+ case 'json':
44
+ displayJsonHistory(limitedHistory, options.file);
45
+ break;
46
+ case 'short':
47
+ displayShortHistory(limitedHistory);
48
+ break;
49
+ default:
50
+ displayTableHistory(limitedHistory, options.file, verbose);
51
+ }
52
+ });
53
+
54
+ function displayTableHistory(history, fileFilter, verbose) {
55
+ console.log();
56
+
57
+ // Header
58
+ console.log(chalk.gray('│') + chalk.cyan(' ID ') + chalk.gray('│') +
59
+ chalk.cyan(' Timestamp ') + chalk.gray('│') +
60
+ chalk.cyan(' Message ') + chalk.gray('│'));
61
+ console.log(chalk.gray('ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤'));
62
+
63
+ for (const entry of history) {
64
+ const timestamp = new Date(entry.timestamp).toLocaleString().slice(0, 19);
65
+ const message = (entry.message || 'No message').padEnd(28).slice(0, 28);
66
+ const id = entry.id.padEnd(8);
67
+
68
+ console.log(
69
+ chalk.gray('│') + chalk.green(` ${id} `) + chalk.gray('│') +
70
+ chalk.white(` ${timestamp} `) + chalk.gray('│') +
71
+ chalk.white(` ${message} `) + chalk.gray('│')
72
+ );
73
+ }
74
+
75
+ console.log(chalk.gray('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'));
76
+ }
77
+
78
+ function displayShortHistory(history) {
79
+ console.log();
80
+ for (const entry of history) {
81
+ const timestamp = new Date(entry.timestamp).toLocaleString().slice(0, 10);
82
+ console.log(chalk.green(`${entry.id.slice(0, 7)} `) +
83
+ chalk.gray(`${timestamp} `) +
84
+ chalk.white(entry.message || 'No message'));
85
+ }
86
+ }
87
+
88
+ function displayJsonHistory(history, fileFilter) {
89
+ console.log(JSON.stringify(history, null, 2));
90
+ }
91
+
92
+ export default historyCommand;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * init.js - Initialize CloudSync configuration
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+ import { logOperation } from '../../utils/logger.js';
11
+
12
+ const initCommand = new Command('init')
13
+ .description('Initialize CloudSync configuration profile')
14
+ .option('--host <hostname>', 'Remote host address (e.g., your-server.com)')
15
+ .option('--user <username>', 'SSH username')
16
+ .option('--port <number>', 'SSH port', (v) => parseInt(v, 10), 22)
17
+ .option('--key <path>', 'Path to SSH private key')
18
+ .option('--protocol <protocol>', 'Default transport protocol', /^(ssh|rsync|sftp|websocket|pipe)$/i, 'ssh')
19
+ .option('--workspace <path>', 'Local workspace path', process.cwd())
20
+ .option('--name <profile>', 'Profile name', 'default')
21
+ .option('--force', 'Overwrite existing configuration', false)
22
+ .option('--verbose', 'Show detailed output', false)
23
+ .action(async (options) => {
24
+ const verbose = options.verbose || process.argv.includes('--verbose');
25
+
26
+ if (verbose) {
27
+ console.log(chalk.gray('\nšŸ” Verbose mode enabled'));
28
+ console.log(chalk.gray('Options received:'), options);
29
+ }
30
+
31
+ const configDir = join(process.cwd(), '.cloudsync');
32
+ const configPath = join(configDir, 'config.json');
33
+ const historyDir = join(configDir, 'history');
34
+ const stagingDir = join(configDir, 'staging');
35
+
36
+ // Create directories
37
+ [configDir, historyDir, join(historyDir, 'commits'), join(historyDir, 'diffs'), join(stagingDir)].forEach(dir => {
38
+ if (!existsSync(dir)) {
39
+ mkdirSync(dir, { recursive: true });
40
+ if (verbose) console.log(chalk.gray(`Created directory: ${dir}`));
41
+ }
42
+ });
43
+
44
+ // Load existing config or create new
45
+ let config = { profiles: {}, settings: {} };
46
+ if (existsSync(configPath) && !options.force) {
47
+ try {
48
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
49
+ if (verbose) console.log(chalk.gray('Loaded existing configuration'));
50
+ } catch (e) {
51
+ if (verbose) console.log(chalk.yellow('Existing config corrupted, creating new one'));
52
+ }
53
+ }
54
+
55
+ // Use options or defaults
56
+ const host = options.host || 'your-server.com';
57
+ const user = options.user || process.env.USER || 'user';
58
+ const port = options.port || 22;
59
+ const keyPath = options.key || join(homedir(), '.ssh', 'id_rsa');
60
+ const protocol = options.protocol;
61
+ const workspace = options.workspace;
62
+
63
+ // Build profile
64
+ const profileName = options.name;
65
+ config.profiles[profileName] = {
66
+ host,
67
+ user,
68
+ port: parseInt(port, 10),
69
+ key: keyPath,
70
+ protocol: protocol.toLowerCase(),
71
+ workspace,
72
+ createdAt: new Date().toISOString(),
73
+ lastUsed: new Date().toISOString()
74
+ };
75
+
76
+ // Default settings
77
+ config.settings = {
78
+ compression: 'zip',
79
+ chunkSize: 10,
80
+ verbose: verbose,
81
+ defaultProfile: profileName
82
+ };
83
+
84
+ // Save config
85
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
86
+
87
+ logOperation('init', `Initialized profile '${profileName}' -> ${host}:${port}`);
88
+
89
+ console.log(chalk.green('\nāœ… CloudSync initialized successfully!'));
90
+ console.log(chalk.gray('━'.repeat(50)));
91
+ console.log(chalk.white(` Profile: ${chalk.cyan(profileName)}`));
92
+ console.log(chalk.white(` Host: ${chalk.cyan(host)}`));
93
+ console.log(chalk.white(` User: ${chalk.cyan(user)}`));
94
+ console.log(chalk.white(` Port: ${chalk.cyan(port)}`));
95
+ console.log(chalk.white(` Protocol: ${chalk.cyan(protocol)}`));
96
+ console.log(chalk.gray('━'.repeat(50)));
97
+ console.log(chalk.gray(`\nšŸ“ Config saved to: ${configPath}`));
98
+ console.log(chalk.cyan('\nšŸš€ Next steps:'));
99
+ console.log(chalk.gray(' cloudsync upload --help'));
100
+ console.log(chalk.gray(' cloudsync doctor # Test your connection'));
101
+ });
102
+
103
+ export default initCommand;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * log.js - Show detailed operation logs
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync, readdirSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+
12
+ const logCommand = new Command('log')
13
+ .description('šŸ“œ Show detailed operation logs')
14
+ .option('--limit <n>', 'Number of entries', (v) => parseInt(v, 10), 20)
15
+ .option('--type <type>', 'Filter by type: upload|download|sync|all', /^(upload|download|sync|all)$/i, 'all')
16
+ .option('--format <type>', 'Output format: detailed|short|json', /^(detailed|short|json)$/i, 'detailed')
17
+ .option('--verbose', 'Show full log data', false)
18
+ .action(async (options) => {
19
+ const verbose = options.verbose || process.argv.includes('--verbose');
20
+ const logsDir = join(process.cwd(), '.cloudsync', 'logs');
21
+
22
+ if (!existsSync(logsDir)) {
23
+ console.log(chalk.yellow('āš ļø No logs found'));
24
+ console.log(chalk.gray(' Logs are created when you use CloudSync commands'));
25
+ return;
26
+ }
27
+
28
+ // Get log files
29
+ const logFiles = readdirSync(logsDir)
30
+ .filter(f => f.endsWith('.json'))
31
+ .sort()
32
+ .reverse()
33
+ .slice(0, options.limit);
34
+
35
+ if (logFiles.length === 0) {
36
+ console.log(chalk.yellow('āš ļø No log entries found'));
37
+ return;
38
+ }
39
+
40
+ console.log(chalk.cyan('\nšŸ“œ CloudSync Logs'));
41
+ console.log(chalk.gray('━'.repeat(70)));
42
+ console.log(chalk.white(` Showing ${logFiles.length} entries (type: ${options.type})`));
43
+ console.log(chalk.gray('━'.repeat(70)));
44
+
45
+ const logs = [];
46
+ for (const file of logFiles) {
47
+ try {
48
+ const log = JSON.parse(readFileSync(join(logsDir, file), 'utf8'));
49
+
50
+ if (options.type !== 'all' && log.type !== options.type) {
51
+ continue;
52
+ }
53
+
54
+ logs.push(log);
55
+ } catch (e) {
56
+ // Skip invalid log files
57
+ }
58
+ }
59
+
60
+ switch (options.format) {
61
+ case 'short':
62
+ displayShortLogs(logs);
63
+ break;
64
+ case 'json':
65
+ console.log(JSON.stringify(logs, null, 2));
66
+ break;
67
+ default:
68
+ displayDetailedLogs(logs, verbose);
69
+ }
70
+ });
71
+
72
+ function displayDetailedLogs(logs, verbose) {
73
+ for (const log of logs) {
74
+ const timestamp = new Date(log.timestamp).toLocaleString();
75
+ const typeColor = getTypeColor(log.type);
76
+
77
+ console.log();
78
+ console.log(chalk.gray('ā”Œ' + '─'.repeat(68) + '┐'));
79
+ console.log(chalk.gray('│') + chalk.cyan(` ${timestamp} `.padEnd(50)) + typeColor(` ${(log.type || 'unknown').toUpperCase()} `.padStart(16)) + chalk.gray('│'));
80
+ console.log(chalk.gray('ā”œ' + '─'.repeat(68) + '┤'));
81
+ console.log(chalk.gray('│') + chalk.white(` ${log.message || 'No message'}`.padEnd(68)) + chalk.gray('│'));
82
+
83
+ if (verbose && log.files) {
84
+ console.log(chalk.gray('│'));
85
+ log.files.slice(0, 5).forEach(f => {
86
+ console.log(chalk.gray('│') + chalk.gray(` - ${f}`.padEnd(66)) + chalk.gray('│'));
87
+ });
88
+ if (log.files.length > 5) {
89
+ console.log(chalk.gray('│') + chalk.gray(` ... and ${log.files.length - 5} more`.padEnd(66)) + chalk.gray('│'));
90
+ }
91
+ }
92
+
93
+ if (log.duration) {
94
+ console.log(chalk.gray('│') + chalk.gray(` Duration: ${log.duration}ms`.padEnd(68)) + chalk.gray('│'));
95
+ }
96
+
97
+ console.log(chalk.gray('ā””' + '─'.repeat(68) + 'ā”˜'));
98
+ }
99
+ }
100
+
101
+ function displayShortLogs(logs) {
102
+ for (const log of logs) {
103
+ const timestamp = new Date(log.timestamp).toLocaleString().slice(0, 16);
104
+ const type = (log.type || 'unknown').padEnd(8).slice(0, 8);
105
+
106
+ console.log(
107
+ chalk.gray(`${timestamp} `) +
108
+ getTypeColor(log.type)(type) +
109
+ chalk.white(` ${log.message || 'No message'}`)
110
+ );
111
+ }
112
+ }
113
+
114
+ function getTypeColor(type) {
115
+ switch (type) {
116
+ case 'upload': return chalk.green;
117
+ case 'download': return chalk.blue;
118
+ case 'sync': return chalk.cyan;
119
+ case 'error': return chalk.red;
120
+ default: return chalk.white;
121
+ }
122
+ }
123
+
124
+ export default logCommand;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * port.js - SSH tunnel/port forwarding management
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+
12
+ const portCommand = new Command('port')
13
+ .description('šŸ”Œ Create SSH tunnel/port forwarding')
14
+ .argument('<local:remote>', 'Port mapping (e.g., 3000:3000)')
15
+ .option('--host <hostname>', 'Remote host to bind', '0.0.0.0')
16
+ .option('--verbose', 'Show tunnel details', false)
17
+ .option('--background', 'Run tunnel in background', false)
18
+ .option('--profile <name>', 'Config profile to use', 'default')
19
+ .action(async (mapping, options) => {
20
+ const verbose = options.verbose || process.argv.includes('--verbose');
21
+ const configPath = join(process.cwd(), '.cloudsync', 'config.json');
22
+
23
+ if (!existsSync(configPath)) {
24
+ console.log(chalk.red('āŒ Not initialized. Run: cloudsync init'));
25
+ return;
26
+ }
27
+
28
+ // Parse port mapping
29
+ const [localPort, remotePort] = mapping.split(':').map(p => parseInt(p, 10));
30
+
31
+ if (isNaN(localPort) || isNaN(remotePort)) {
32
+ console.log(chalk.red('āŒ Invalid port mapping. Use format: local:remote (e.g., 3000:3000)'));
33
+ return;
34
+ }
35
+
36
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
37
+ const profile = config.profiles[options.profile] || config.profiles[config.settings.defaultProfile];
38
+
39
+ if (!profile) {
40
+ console.log(chalk.red(`āŒ Profile '${options.profile}' not found`));
41
+ return;
42
+ }
43
+
44
+ console.log(chalk.cyan('\nšŸ”Œ CloudSync - SSH Tunnel Manager'));
45
+ console.log(chalk.gray('━'.repeat(50)));
46
+ console.log(chalk.white(` Local Port: ${chalk.cyan(localPort)}`));
47
+ console.log(chalk.white(` Remote Port: ${chalk.cyan(remotePort)}`));
48
+ console.log(chalk.white(` Bind Host: ${chalk.cyan(options.host)}`));
49
+ console.log(chalk.gray('━'.repeat(50)));
50
+
51
+ if (verbose) {
52
+ console.log(chalk.gray('\nšŸ“‹ Tunnel Configuration:'));
53
+ console.log(chalk.gray(` Host: ${profile.host}`));
54
+ console.log(chalk.gray(` User: ${profile.user}`));
55
+ console.log(chalk.gray(` Port: ${profile.port}`));
56
+ console.log(chalk.gray(` Background: ${options.background ? 'Yes' : 'No'}`));
57
+ }
58
+
59
+ // Create SSH tunnel
60
+ await createTunnel(profile, localPort, remotePort, options.host, verbose, options.background);
61
+ });
62
+
63
+ async function createTunnel(profile, localPort, remotePort, bindHost, verbose, background) {
64
+ console.log(chalk.green('\nāœ… SSH tunnel configuration ready!'));
65
+ console.log(chalk.cyan('\n🌐 Tunnel Information:'));
66
+ console.log(chalk.white(` Local: ${chalk.cyan(`http://localhost:${localPort}`)}`));
67
+ console.log(chalk.white(` Remote: ${chalk.cyan(`${profile.host}:${remotePort}`)}`));
68
+ console.log(chalk.gray('\n Forwarding: localhost:' + localPort + ' <-> ' + profile.host + ':' + remotePort));
69
+
70
+ console.log(chalk.yellow('\nāš ļø SSH tunnel demo mode'));
71
+ console.log(chalk.gray(' Tunnel command that would run:'));
72
+ console.log(chalk.cyan(` ssh -L ${localPort}:localhost:${remotePort} ${profile.user}@${profile.host} -p ${profile.port || 22}`));
73
+ console.log(chalk.cyan(` ssh -R ${remotePort}:localhost:${localPort} ${profile.user}@${profile.host} -p ${profile.port || 22}`));
74
+ }
75
+
76
+ export default portCommand;