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.
- package/LICENSE +21 -0
- package/README.md +560 -0
- package/bin/cloudsync.js +48 -0
- package/package.json +104 -0
- package/src/cli/commands/clone.js +103 -0
- package/src/cli/commands/commit.js +153 -0
- package/src/cli/commands/config.js +188 -0
- package/src/cli/commands/diff.js +122 -0
- package/src/cli/commands/doctor.js +142 -0
- package/src/cli/commands/download.js +130 -0
- package/src/cli/commands/history.js +92 -0
- package/src/cli/commands/init.js +103 -0
- package/src/cli/commands/log.js +124 -0
- package/src/cli/commands/port.js +76 -0
- package/src/cli/commands/rollback.js +100 -0
- package/src/cli/commands/share.js +305 -0
- package/src/cli/commands/stage.js +155 -0
- package/src/cli/commands/status.js +193 -0
- package/src/cli/commands/sync.js +255 -0
- package/src/cli/commands/unstage.js +92 -0
- package/src/cli/commands/upload.js +287 -0
- package/src/cli/index.js +134 -0
- package/src/core/crypto/index.js +94 -0
- package/src/core/transport/index.js +264 -0
- package/src/core/vcs/index.js +202 -0
- package/src/utils/helpers.js +234 -0
- package/src/utils/logger.js +66 -0
- package/src/version.mjs +1 -0
|
@@ -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;
|