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,193 @@
1
+ /**
2
+ * status.js - Show current sync status
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+
12
+ const statusCommand = new Command('status')
13
+ .description('šŸ“Š Show current sync and repository status')
14
+ .option('--verbose', 'Show full details', false)
15
+ .option('--json', 'Output as JSON', false)
16
+ .option('--profile <name>', 'Config profile to check', 'default')
17
+ .action(async (options) => {
18
+ const verbose = options.verbose || process.argv.includes('--verbose');
19
+ const configPath = join(process.cwd(), '.cloudsync', 'config.json');
20
+ const statusFile = join(process.cwd(), '.cloudsync', 'status.json');
21
+ const stagingDir = join(process.cwd(), '.cloudsync', 'staging');
22
+ const indexFile = join(process.cwd(), '.cloudsync', 'history', 'index.json');
23
+
24
+ // Get workspace stats
25
+ const workspace = process.cwd();
26
+ const workspaceStats = getWorkspaceStats(workspace);
27
+
28
+ // Get sync status
29
+ let syncStatus = {
30
+ lastSync: null,
31
+ lastAction: null,
32
+ pendingChanges: false
33
+ };
34
+ if (existsSync(statusFile)) {
35
+ syncStatus = JSON.parse(readFileSync(statusFile, 'utf8'));
36
+ }
37
+
38
+ // Get staged files
39
+ let stagedFiles = [];
40
+ if (existsSync(stagingDir)) {
41
+ stagedFiles = readdirSync(stagingDir).filter(f => f !== 'index.json');
42
+ }
43
+
44
+ // Get history
45
+ let commitCount = 0;
46
+ if (existsSync(indexFile)) {
47
+ const history = JSON.parse(readFileSync(indexFile, 'utf8'));
48
+ commitCount = history.length;
49
+ }
50
+
51
+ // Build status object
52
+ const status = {
53
+ initialized: existsSync(configPath),
54
+ profile: options.profile,
55
+ workspace: {
56
+ path: workspace,
57
+ totalFiles: workspaceStats.totalFiles,
58
+ totalSize: workspaceStats.totalSize,
59
+ lastModified: workspaceStats.lastModified
60
+ },
61
+ sync: syncStatus,
62
+ versionControl: {
63
+ commits: commitCount,
64
+ stagedFiles: stagedFiles.length,
65
+ pendingChanges: workspaceStats.changedFiles > 0
66
+ },
67
+ timestamp: new Date().toISOString()
68
+ };
69
+
70
+ if (options.json) {
71
+ console.log(JSON.stringify(status, null, 2));
72
+ return;
73
+ }
74
+
75
+ // Display status
76
+ console.log(chalk.cyan('\nšŸ“Š CloudSync Status'));
77
+ console.log(chalk.gray('━'.repeat(60)));
78
+
79
+ // Initialization status
80
+ if (status.initialized) {
81
+ console.log(chalk.green(' āœ“ ') + chalk.white('Initialized'));
82
+ } else {
83
+ console.log(chalk.red(' āœ— ') + chalk.white('Not initialized - Run: ') + chalk.cyan('cloudsync init'));
84
+ }
85
+
86
+ // Workspace status
87
+ console.log(chalk.gray('\n šŸ“ Workspace:'));
88
+ console.log(chalk.gray(' ā”œā”€ Path: ') + chalk.white(status.workspace.path));
89
+ console.log(chalk.gray(' ā”œā”€ Files: ') + chalk.cyan(status.workspace.totalFiles));
90
+ console.log(chalk.gray(' ā”œā”€ Size: ') + chalk.cyan(formatBytes(status.workspace.totalSize)));
91
+ console.log(chalk.gray(' └─ Last modified: ') + chalk.white(status.workspace.lastModified));
92
+
93
+ // Version control status
94
+ console.log(chalk.gray('\n šŸ”„ Version Control:'));
95
+ console.log(chalk.gray(' ā”œā”€ Total commits: ') + chalk.cyan(status.versionControl.commits));
96
+ console.log(chalk.gray(' ā”œā”€ Staged files: ') + chalk.cyan(status.versionControl.stagedFiles));
97
+
98
+ if (status.versionControl.pendingChanges) {
99
+ console.log(chalk.yellow(' ā”œā”€ Changes: ') + chalk.yellow('Pending (run cloudsync stage to track)'));
100
+ } else {
101
+ console.log(chalk.green(' ā”œā”€ Changes: ') + chalk.green('None'));
102
+ }
103
+
104
+ // Sync status
105
+ console.log(chalk.gray('\n šŸ”— Last Sync:'));
106
+ if (syncStatus.lastSync) {
107
+ console.log(chalk.gray(' ā”œā”€ Time: ') + chalk.white(new Date(syncStatus.lastSync).toLocaleString()));
108
+ console.log(chalk.gray(' ā”œā”€ Action: ') + chalk.cyan(syncStatus.lastAction || 'N/A'));
109
+
110
+ if (syncStatus.pendingChanges) {
111
+ console.log(chalk.yellow(' └─ Status: ') + chalk.yellow('Local changes pending upload'));
112
+ } else {
113
+ console.log(chalk.green(' └─ Status: ') + chalk.green('In sync'));
114
+ }
115
+ } else {
116
+ console.log(chalk.gray(' └─ ') + chalk.yellow('No sync history'));
117
+ }
118
+
119
+ // Staged files detail
120
+ if (verbose && stagedFiles.length > 0) {
121
+ console.log(chalk.gray('\n šŸ“‹ Staged Files:'));
122
+ stagedFiles.forEach(f => {
123
+ console.log(chalk.gray(' + ') + chalk.green(f));
124
+ });
125
+ }
126
+
127
+ console.log(chalk.gray('━'.repeat(60)));
128
+
129
+ // Quick commands
130
+ console.log(chalk.cyan('\n šŸ’” Quick Commands:'));
131
+ console.log(chalk.gray(' cloudsync stage <files> # Stage changes'));
132
+ console.log(chalk.gray(' cloudsync commit <msg> # Commit staged'));
133
+ console.log(chalk.gray(' cloudsync upload # Push to remote'));
134
+ console.log(chalk.gray(' cloudsync history # View history'));
135
+ });
136
+
137
+ function getWorkspaceStats(workspace) {
138
+ let totalFiles = 0;
139
+ let totalSize = 0;
140
+ let lastModified = new Date(0);
141
+
142
+ function scan(dir) {
143
+ if (!existsSync(dir)) return;
144
+
145
+ try {
146
+ const entries = readdirSync(dir, { withFileTypes: true });
147
+
148
+ for (const entry of entries) {
149
+ const fullPath = join(dir, entry.name);
150
+
151
+ // Skip hidden dirs except .cloudsync
152
+ if (entry.name.startsWith('.') && entry.name !== '.cloudsync') continue;
153
+
154
+ if (entry.isDirectory()) {
155
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
156
+ scan(fullPath);
157
+ }
158
+ } else if (entry.isFile()) {
159
+ try {
160
+ const stat = statSync(fullPath);
161
+ totalFiles++;
162
+ totalSize += stat.size;
163
+ if (stat.mtime > lastModified) {
164
+ lastModified = stat.mtime;
165
+ }
166
+ } catch (e) {
167
+ // Skip files we can't access
168
+ }
169
+ }
170
+ }
171
+ } catch (e) {
172
+ // Skip directories we can't access
173
+ }
174
+ }
175
+
176
+ scan(workspace);
177
+
178
+ return {
179
+ totalFiles,
180
+ totalSize,
181
+ lastModified: lastModified > new Date(0) ? lastModified.toLocaleString() : 'N/A'
182
+ };
183
+ }
184
+
185
+ function formatBytes(bytes) {
186
+ if (bytes === 0) return '0 B';
187
+ const k = 1024;
188
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
189
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
190
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
191
+ }
192
+
193
+ export default statusCommand;
@@ -0,0 +1,255 @@
1
+ /**
2
+ * sync.js - Bidirectional synchronization with conflict resolution
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from 'fs';
8
+ import { join, relative } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { logOperation } from '../../utils/logger.js';
11
+
12
+
13
+ const syncCommand = new Command('sync')
14
+ .description('šŸ”„ Bidirectional sync with conflict resolution')
15
+ .option('--strategy <type>', 'Conflict resolution: local|remote|manual', /^(local|remote|manual)$/i, 'manual')
16
+ .option('--watch', 'Continuous file watching mode', false)
17
+ .option('--interval <seconds>', 'Sync interval in seconds', (v) => parseInt(v, 10), 30)
18
+ .option('--verbose', 'Show detailed sync logs', false)
19
+ .option('--dry-run', 'Preview sync without executing', false)
20
+ .option('--profile <name>', 'Config profile to use', 'default')
21
+ .option('--include <patterns>', 'Files to sync (comma-separated)')
22
+ .option('--exclude <patterns>', 'Files to exclude (comma-separated)', 'node_modules,.git')
23
+ .action(async (options) => {
24
+ const verbose = options.verbose || process.argv.includes('--verbose');
25
+ const configPath = join(process.cwd(), '.cloudsync', 'config.json');
26
+
27
+ if (!existsSync(configPath)) {
28
+ console.log(chalk.red('āŒ Not initialized. Run: cloudsync init'));
29
+ return;
30
+ }
31
+
32
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
33
+ const profile = config.profiles[options.profile] || config.profiles[config.settings.defaultProfile];
34
+
35
+ if (!profile) {
36
+ console.log(chalk.red(`āŒ Profile '${options.profile}' not found`));
37
+ return;
38
+ }
39
+
40
+ console.log(chalk.cyan('\nšŸ”„ CloudSync - Bidirectional Synchronization'));
41
+ console.log(chalk.gray('━'.repeat(50)));
42
+ console.log(chalk.white(` Strategy: ${chalk.cyan(options.strategy)}`));
43
+ console.log(chalk.white(` Interval: ${chalk.cyan(options.interval + 's')}`));
44
+ console.log(chalk.white(` Watch Mode: ${chalk.cyan(options.watch ? 'ON' : 'OFF')}`));
45
+ console.log(chalk.gray('━'.repeat(50)));
46
+
47
+ if (verbose) {
48
+ console.log(chalk.gray('\nšŸ“‹ Sync Configuration:'));
49
+ console.log(chalk.gray(` Host: ${profile.host}`));
50
+ console.log(chalk.gray(` User: ${profile.user}`));
51
+ console.log(chalk.gray(` Protocol: ${profile.protocol}`));
52
+ console.log(chalk.gray(` Include: ${options.include || 'all'}`));
53
+ console.log(chalk.gray(` Exclude: ${options.exclude}`));
54
+ }
55
+
56
+ if (options.dryRun) {
57
+ console.log(chalk.yellow('\nšŸ” Dry run mode - analyzing changes...'));
58
+ const changes = analyzeChanges(options, verbose);
59
+ displayChanges(changes, verbose);
60
+ return;
61
+ }
62
+
63
+ // Perform initial sync
64
+ console.log(chalk.cyan('\nšŸ” Analyzing workspace...'));
65
+ const changes = analyzeChanges(options, verbose);
66
+
67
+ if (changes.upload.length === 0 && changes.download.length === 0) {
68
+ console.log(chalk.green('\nāœ… Workspace already in sync'));
69
+ return;
70
+ }
71
+
72
+ displayChanges(changes, verbose);
73
+
74
+ // Upload local changes
75
+ if (changes.upload.length > 0) {
76
+ console.log(chalk.cyan(`\nā¬†ļø Uploading ${changes.upload.length} changed files...`));
77
+ await performUpload(changes.upload, profile, options, verbose);
78
+ }
79
+
80
+ // Download remote changes
81
+ if (changes.download.length > 0) {
82
+ console.log(chalk.cyan(`\nā¬‡ļø Downloading ${changes.download.length} changed files...`));
83
+ await performDownload(changes.download, profile, options, verbose);
84
+ }
85
+
86
+ // Handle conflicts
87
+ if (changes.conflicts.length > 0) {
88
+ console.log(chalk.yellow(`\nāš ļø ${changes.conflicts.length} conflicts detected`));
89
+ await resolveConflicts(changes.conflicts, options.strategy, verbose);
90
+ }
91
+
92
+ logOperation('sync', `Synced ${changes.upload.length} up / ${changes.download.length} down`);
93
+ console.log(chalk.green('\nāœ… Sync complete!'));
94
+
95
+ // Update sync status
96
+ updateSyncStatus(changes, verbose);
97
+ });
98
+
99
+ function analyzeChanges(options, verbose) {
100
+ const workspace = process.cwd();
101
+ const excludePatterns = options.exclude.split(',').map(p => p.trim());
102
+ const includePatterns = options.include ? options.include.split(',').map(p => p.trim()) : null;
103
+
104
+ const changes = {
105
+ upload: [],
106
+ download: [],
107
+ conflicts: [],
108
+ unchanged: []
109
+ };
110
+
111
+ function scanDirectory(dir, baseDir = dir) {
112
+ if (!existsSync(dir)) return;
113
+
114
+ try {
115
+ const entries = readdirSync(dir, { withFileTypes: true });
116
+
117
+ for (const entry of entries) {
118
+ const fullPath = join(dir, entry.name);
119
+ const relPath = relative(baseDir, fullPath);
120
+
121
+ // Skip hidden directories except .cloudsync
122
+ if (entry.name === '.cloudsync' || entry.name === '.git') {
123
+ continue;
124
+ }
125
+
126
+ // Check exclusions
127
+ if (excludePatterns.some(p => relPath.includes(p))) {
128
+ continue;
129
+ }
130
+
131
+ if (entry.isDirectory()) {
132
+ scanDirectory(fullPath, baseDir);
133
+ } else if (entry.isFile()) {
134
+ // Check if included
135
+ if (includePatterns) {
136
+ if (!includePatterns.some(p => relPath.includes(p))) {
137
+ continue;
138
+ }
139
+ }
140
+
141
+ changes.upload.push({
142
+ path: fullPath,
143
+ relative: relPath,
144
+ size: statSync(fullPath).size,
145
+ modified: statSync(fullPath).mtime
146
+ });
147
+ }
148
+ }
149
+ } catch (e) {
150
+ // Skip inaccessible directories
151
+ }
152
+ }
153
+
154
+ scanDirectory(workspace);
155
+
156
+ if (verbose) {
157
+ console.log(chalk.gray(`\nšŸ“Š Analysis Results:`));
158
+ console.log(chalk.gray(` Files to upload: ${changes.upload.length}`));
159
+ console.log(chalk.gray(` Files to download: ${changes.download.length}`));
160
+ console.log(chalk.gray(` Conflicts: ${changes.conflicts.length}`));
161
+ }
162
+
163
+ return changes;
164
+ }
165
+
166
+ function displayChanges(changes, verbose) {
167
+ if (changes.upload.length > 0) {
168
+ console.log(chalk.cyan('\nšŸ“¤ Files to upload:'));
169
+ changes.upload.forEach(f => {
170
+ const size = (f.size / 1024).toFixed(1) + ' KB';
171
+ console.log(chalk.gray(` + ${f.relative} (${size})`));
172
+ });
173
+ }
174
+
175
+ if (changes.download.length > 0) {
176
+ console.log(chalk.cyan('\nšŸ“„ Files to download:'));
177
+ changes.download.forEach(f => {
178
+ console.log(chalk.gray(` - ${f.relative}`));
179
+ });
180
+ }
181
+
182
+ if (changes.conflicts.length > 0) {
183
+ console.log(chalk.yellow('\nāš ļø Conflicts:'));
184
+ changes.conflicts.forEach(f => {
185
+ console.log(chalk.gray(` ! ${f}`));
186
+ });
187
+ }
188
+ }
189
+
190
+ async function performUpload(files, profile, options, verbose) {
191
+ if (verbose) console.log(chalk.gray('\nšŸš€ Starting upload...'));
192
+
193
+ // Simulate upload
194
+ for (const file of files) {
195
+ if (verbose) console.log(chalk.gray(` Uploading: ${file.relative}`));
196
+ await sleep(100);
197
+ }
198
+
199
+ console.log(chalk.green(` āœ… ${files.length} files uploaded`));
200
+ }
201
+
202
+ async function performDownload(files, profile, options, verbose) {
203
+ if (verbose) console.log(chalk.gray('\nšŸš€ Starting download...'));
204
+
205
+ // Simulate download
206
+ for (const file of files) {
207
+ if (verbose) console.log(chalk.gray(` Downloading: ${file.relative}`));
208
+ await sleep(100);
209
+ }
210
+
211
+ console.log(chalk.green(` āœ… ${files.length} files downloaded`));
212
+ }
213
+
214
+ async function resolveConflicts(conflicts, strategy, verbose) {
215
+ console.log(chalk.cyan('\nšŸ”§ Resolving conflicts...'));
216
+
217
+ switch (strategy.toLowerCase()) {
218
+ case 'local':
219
+ console.log(chalk.gray(' Using local versions'));
220
+ break;
221
+ case 'remote':
222
+ console.log(chalk.gray(' Using remote versions'));
223
+ break;
224
+ case 'manual':
225
+ console.log(chalk.yellow(' Manual resolution required'));
226
+ conflicts.forEach(f => {
227
+ console.log(chalk.gray(` - ${f}`));
228
+ });
229
+ break;
230
+ }
231
+ }
232
+
233
+ function updateSyncStatus(changes, verbose) {
234
+ const statusFile = join(process.cwd(), '.cloudsync', 'status.json');
235
+ const status = {
236
+ lastSync: new Date().toISOString(),
237
+ lastAction: 'sync',
238
+ changes: {
239
+ uploaded: changes.upload.length,
240
+ downloaded: changes.download.length,
241
+ conflicts: changes.conflicts.length
242
+ },
243
+ pendingChanges: changes.upload.length > 0
244
+ };
245
+
246
+ writeFileSync(statusFile, JSON.stringify(status, null, 2));
247
+
248
+ if (verbose) console.log(chalk.gray('Sync status updated'));
249
+ }
250
+
251
+ function sleep(ms) {
252
+ return new Promise(resolve => setTimeout(resolve, ms));
253
+ }
254
+
255
+ export default syncCommand;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * unstage.js - Remove files from staging area
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { existsSync, readdirSync, unlinkSync, writeFileSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+
12
+ const unstageCommand = new Command('unstage')
13
+ .description('šŸ“¤ Remove files from staging area')
14
+ .argument('[files...]', 'Files to unstage')
15
+ .option('--all', 'Unstage all files', false)
16
+ .option('--verbose', 'Show detailed output', false)
17
+ .action(async (files, options) => {
18
+ const verbose = options.verbose || process.argv.includes('--verbose');
19
+ const stagingDir = join(process.cwd(), '.cloudsync', 'staging');
20
+ const indexFile = join(stagingDir, 'index.json');
21
+
22
+ if (!existsSync(stagingDir)) {
23
+ console.log(chalk.yellow('āš ļø No staging area exists'));
24
+ return;
25
+ }
26
+
27
+ const stagedFiles = readdirSync(stagingDir).filter(f => f !== 'index.json');
28
+
29
+ if (stagedFiles.length === 0) {
30
+ console.log(chalk.yellow('āš ļø No files are staged'));
31
+ return;
32
+ }
33
+
34
+ if (options.all) {
35
+ // Unstage all
36
+ console.log(chalk.cyan('\nšŸ“¤ Unstaging all files...'));
37
+
38
+ let count = 0;
39
+ stagedFiles.forEach(f => {
40
+ const path = join(stagingDir, f);
41
+ unlinkSync(path);
42
+ count++;
43
+ if (verbose) console.log(chalk.red(` - ${f}`));
44
+ });
45
+
46
+ // Clear index
47
+ if (existsSync(indexFile)) {
48
+ unlinkSync(indexFile);
49
+ }
50
+
51
+ console.log(chalk.green(`\nāœ… Unstaged ${count} file(s)`));
52
+ } else if (files.length > 0) {
53
+ // Unstage specific files
54
+ console.log(chalk.cyan('\nšŸ“¤ Unstaging specified files...'));
55
+
56
+ let count = 0;
57
+ files.forEach(f => {
58
+ const stagedName = f.split('/').pop();
59
+ const path = join(stagingDir, stagedName);
60
+
61
+ if (existsSync(path)) {
62
+ unlinkSync(path);
63
+ count++;
64
+ if (verbose) console.log(chalk.red(` - ${stagedName}`));
65
+ } else {
66
+ console.log(chalk.yellow(` āš ļø Not staged: ${f}`));
67
+ }
68
+ });
69
+
70
+ console.log(chalk.green(`\nāœ… Unstaged ${count} file(s)`));
71
+ } else {
72
+ // Show usage
73
+ console.log(chalk.cyan('\nšŸ“¤ CloudSync Unstage'));
74
+ console.log(chalk.gray('─'.repeat(40)));
75
+ console.log(chalk.gray('Usage:'));
76
+ console.log(chalk.gray(' cloudsync unstage <files...> # Unstage specific files'));
77
+ console.log(chalk.gray(' cloudsync unstage --all # Unstage all files'));
78
+ }
79
+
80
+ // Update index
81
+ const remainingFiles = readdirSync(stagingDir).filter(f => f !== 'index.json');
82
+ if (remainingFiles.length > 0) {
83
+ writeFileSync(indexFile, JSON.stringify({
84
+ files: remainingFiles,
85
+ timestamp: new Date().toISOString()
86
+ }, null, 2));
87
+ } else if (existsSync(indexFile)) {
88
+ unlinkSync(indexFile);
89
+ }
90
+ });
91
+
92
+ export default unstageCommand;