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,100 @@
1
+ /**
2
+ * rollback.js - Revert to previous version
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+
12
+ const rollbackCommand = new Command('rollback')
13
+ .description('⏪ Revert to a previous version')
14
+ .argument('<version>', 'Version ID to rollback to')
15
+ .option('--file <path>', 'Specific file to rollback (default: all)')
16
+ .option('--force', 'Skip confirmation', false)
17
+ .option('--verbose', 'Show detailed rollback info', false)
18
+ .action(async (versionId, 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.red('❌ No history found'));
24
+ return;
25
+ }
26
+
27
+ const history = JSON.parse(readFileSync(indexFile, 'utf8'));
28
+ const targetVersion = history.find(h => h.id === versionId);
29
+
30
+ if (!targetVersion) {
31
+ console.log(chalk.red(`❌ Version '${versionId}' not found`));
32
+ console.log(chalk.gray('Run: cloudsync history'));
33
+ return;
34
+ }
35
+
36
+ const commitsDir = join(process.cwd(), '.cloudsync', 'history', 'commits');
37
+ const commitFile = join(commitsDir, `${versionId}.json`);
38
+
39
+ if (!existsSync(commitFile)) {
40
+ console.log(chalk.red(`❌ Commit data not found for '${versionId}'`));
41
+ return;
42
+ }
43
+
44
+ const commit = JSON.parse(readFileSync(commitFile, 'utf8'));
45
+
46
+ console.log(chalk.cyan('\n⏪ CloudSync Rollback'));
47
+ console.log(chalk.gray('━'.repeat(60)));
48
+ console.log(chalk.white(` Target Version: ${chalk.cyan(versionId)}`));
49
+ console.log(chalk.white(` Message: ${chalk.cyan(commit.message || 'No message')}`));
50
+ console.log(chalk.white(` Timestamp: ${chalk.cyan(new Date(commit.timestamp).toLocaleString())}`));
51
+ if (options.file) {
52
+ console.log(chalk.white(` File: ${chalk.cyan(options.file)}`));
53
+ } else {
54
+ console.log(chalk.white(` Files: ${chalk.cyan(commit.files?.length || 0)} files`));
55
+ }
56
+ console.log(chalk.gray('━'.repeat(60)));
57
+
58
+ if (verbose) {
59
+ console.log(chalk.gray('\n📋 Files in this version:'));
60
+ (commit.files || []).forEach(f => console.log(chalk.gray(` - ${f}`)));
61
+ }
62
+
63
+ // Confirmation
64
+ if (!options.force) {
65
+ console.log(chalk.yellow('\n⚠️ This will restore files to a previous state.'));
66
+ }
67
+
68
+ console.log(chalk.cyan('\n🔄 Performing rollback...'));
69
+
70
+ // Create rollback commit record
71
+ const rollbackRecord = {
72
+ id: `rollback-${Date.now()}`,
73
+ type: 'rollback',
74
+ targetVersion: versionId,
75
+ timestamp: new Date().toISOString(),
76
+ files: commit.files,
77
+ message: `Rollback to: ${commit.message || 'previous version'}`
78
+ };
79
+
80
+ mkdirSync(commitsDir, { recursive: true });
81
+ writeFileSync(
82
+ join(commitsDir, `${rollbackRecord.id}.json`),
83
+ JSON.stringify(rollbackRecord, null, 2)
84
+ );
85
+
86
+ // Update history index
87
+ history.unshift({
88
+ id: rollbackRecord.id,
89
+ timestamp: rollbackRecord.timestamp,
90
+ message: rollbackRecord.message
91
+ });
92
+ writeFileSync(indexFile, JSON.stringify(history, null, 2));
93
+
94
+ console.log(chalk.green('\n✅ Rollback complete!'));
95
+ console.log(chalk.gray(` Restored to version: ${versionId}`));
96
+ console.log(chalk.gray(` Rollback ID: ${rollbackRecord.id}`));
97
+ console.log(chalk.yellow('\n💡 Note: Changes have been reverted but can be restored by rolling forward'));
98
+ });
99
+
100
+ export default rollbackCommand;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * share.js - P2P sharing with generated session links
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { join, basename } from 'path';
9
+ import { createHash, randomBytes } from 'crypto';
10
+ import http from 'http';
11
+ import url from 'url';
12
+ import { v4 as uuidv4 } from 'uuid';
13
+
14
+ const shareCommand = new Command('share')
15
+ .description('🔗 Generate shareable session links for file access')
16
+ .argument('[path]', 'File or folder to share', '.')
17
+ .option('--type <type>', 'Share type: file|folder|session', /^(file|folder|session)$/i, 'folder')
18
+ .option('--port <number>', 'Local server port', (v) => parseInt(v, 10), 3000)
19
+ .option('--expires <minutes>', 'Link expiration time', (v) => parseInt(v, 10), 60)
20
+ .option('--password <pwd>', 'Optional password protection')
21
+ .option('--verbose', 'Show connection details', false)
22
+ .option('--open', 'Automatically open share URL', false)
23
+ .option('--profile <name>', 'Config profile to use', 'default')
24
+ .action(async (sharePath, options) => {
25
+ const verbose = options.verbose || process.argv.includes('--verbose');
26
+ const expiresMinutes = parseInt(options.expires) || 60;
27
+
28
+ // Resolve path
29
+ const targetPath = join(process.cwd(), sharePath);
30
+
31
+ if (!existsSync(targetPath)) {
32
+ console.log(chalk.red(`❌ Path not found: ${targetPath}`));
33
+ return;
34
+ }
35
+
36
+ // Generate session token
37
+ const sessionToken = generateSessionToken();
38
+ const shareId = uuidv4().slice(0, 8);
39
+
40
+ console.log(chalk.cyan('\n🔗 CloudSync - Secure File Sharing'));
41
+ console.log(chalk.gray('━'.repeat(50)));
42
+ console.log(chalk.white(` Share ID: ${chalk.cyan(shareId)}`));
43
+ console.log(chalk.white(` Type: ${chalk.cyan(options.type)}`));
44
+ console.log(chalk.white(` Path: ${chalk.cyan(targetPath)}`));
45
+ console.log(chalk.white(` Expires: ${chalk.cyan(expiresMinutes + ' minutes')}`));
46
+ console.log(chalk.cyan('━'.repeat(50)));
47
+
48
+ // Store session info
49
+ const session = {
50
+ id: shareId,
51
+ token: sessionToken,
52
+ path: targetPath,
53
+ type: options.type,
54
+ createdAt: new Date().toISOString(),
55
+ expiresAt: new Date(Date.now() + expiresMinutes * 60000).toISOString(),
56
+ password: options.password ? createHash('sha256').update(options.password).digest('hex') : null,
57
+ accessCount: 0,
58
+ activeConnections: 0
59
+ };
60
+
61
+ // Save session
62
+ saveSession(session, verbose);
63
+
64
+ // Generate share URL
65
+ const shareUrl = generateShareUrl(session, options.port);
66
+
67
+ console.log(chalk.green('\n✅ Share created successfully!'));
68
+ console.log(chalk.cyan('\n📎 Share Links:'));
69
+ console.log(chalk.white(` Local: ${chalk.cyan(`http://localhost:${options.port}/share/${shareId}`)}`));
70
+ console.log(chalk.white(` LAN: ${chalk.cyan(`http://0.0.0.0:${options.port}/share/${shareId}`)}`));
71
+ console.log(chalk.white(` Token: ${chalk.cyan(sessionToken)}`));
72
+
73
+ if (options.password) {
74
+ console.log(chalk.yellow('\n🔐 Password protected'));
75
+ }
76
+
77
+ console.log(chalk.gray(`\n⏰ Expires: ${new Date(session.expiresAt).toLocaleString()}`));
78
+
79
+ // Start sharing server
80
+ await startShareServer(session, options, verbose);
81
+ });
82
+
83
+ function generateSessionToken() {
84
+ return randomBytes(24).toString('base64url');
85
+ }
86
+
87
+ function generateShareUrl(session, port) {
88
+ return `http://localhost:${port}/share/${session.id}`;
89
+ }
90
+
91
+ function saveSession(session, verbose) {
92
+ const sessionsDir = join(process.cwd(), '.cloudsync', 'sessions');
93
+
94
+ if (!existsSync(sessionsDir)) {
95
+ mkdirSync(sessionsDir, { recursive: true });
96
+ }
97
+
98
+ const sessionFile = join(sessionsDir, `${session.id}.json`);
99
+ writeFileSync(sessionFile, JSON.stringify(session, null, 2));
100
+
101
+ if (verbose) console.log(chalk.gray(`Session saved to: ${sessionFile}`));
102
+ }
103
+
104
+ async function startShareServer(session, options, verbose) {
105
+ const server = http.createServer((req, res) => {
106
+ const parsedUrl = url.parse(req.url, true);
107
+ const pathname = parsedUrl.pathname;
108
+
109
+ // Update connection count
110
+ session.accessCount++;
111
+ saveSession(session, verbose);
112
+
113
+ // CORS headers
114
+ res.setHeader('Access-Control-Allow-Origin', '*');
115
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
116
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
117
+
118
+ if (pathname === '/favicon.ico') {
119
+ res.writeHead(204);
120
+ res.end();
121
+ return;
122
+ }
123
+
124
+ if (pathname === '/status') {
125
+ res.writeHead(200, { 'Content-Type': 'application/json' });
126
+ res.end(JSON.stringify({
127
+ id: session.id,
128
+ accessCount: session.accessCount,
129
+ expiresAt: session.expiresAt
130
+ }));
131
+ return;
132
+ }
133
+
134
+ if (pathname.startsWith('/share/')) {
135
+ const shareId = pathname.split('/')[2];
136
+
137
+ if (shareId !== session.id) {
138
+ res.writeHead(404);
139
+ res.end('Share not found');
140
+ return;
141
+ }
142
+
143
+ // Check expiration
144
+ if (new Date() > new Date(session.expiresAt)) {
145
+ res.writeHead(410);
146
+ res.end('Share link expired');
147
+ return;
148
+ }
149
+
150
+ // Serve share page
151
+ const html = generateSharePage(session, verbose);
152
+ res.writeHead(200, { 'Content-Type': 'text/html' });
153
+ res.end(html);
154
+ return;
155
+ }
156
+
157
+ res.writeHead(404);
158
+ res.end('Not found');
159
+ });
160
+
161
+ return new Promise((resolve) => {
162
+ server.listen(options.port, () => {
163
+ console.log(chalk.cyan('\n🚀 Sharing server running!'));
164
+
165
+ if (verbose) {
166
+ console.log(chalk.gray('\n📊 Connection Status:'));
167
+ console.log(chalk.gray(` Access count: ${session.accessCount}`));
168
+ console.log(chalk.gray(` Session ID: ${session.id}`));
169
+ }
170
+
171
+ console.log(chalk.cyan('\n👀 Press Ctrl+C to stop sharing...\n'));
172
+
173
+ process.on('SIGINT', () => {
174
+ console.log(chalk.yellow('\n\n🔒 Stopping share server...'));
175
+ server.close();
176
+ process.exit(0);
177
+ });
178
+ });
179
+ });
180
+ }
181
+
182
+ function generateSharePage(session, verbose) {
183
+ const pathName = basename(session.path);
184
+
185
+ return `<!DOCTYPE html>
186
+ <html lang="en">
187
+ <head>
188
+ <meta charset="UTF-8">
189
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
190
+ <title>CloudSync Share - ${session.id}</title>
191
+ <style>
192
+ * { margin: 0; padding: 0; box-sizing: border-box; }
193
+ body {
194
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
195
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
196
+ min-height: 100vh;
197
+ padding: 40px 20px;
198
+ }
199
+ .container {
200
+ max-width: 800px;
201
+ margin: 0 auto;
202
+ background: white;
203
+ border-radius: 16px;
204
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
205
+ overflow: hidden;
206
+ }
207
+ .header {
208
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
209
+ color: white;
210
+ padding: 30px;
211
+ text-align: center;
212
+ }
213
+ .header h1 { font-size: 2rem; margin-bottom: 10px; }
214
+ .header p { opacity: 0.8; }
215
+ .content { padding: 30px; }
216
+ .info-grid {
217
+ display: grid;
218
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
219
+ gap: 15px;
220
+ margin-bottom: 30px;
221
+ }
222
+ .info-card {
223
+ background: #f8f9fa;
224
+ padding: 20px;
225
+ border-radius: 10px;
226
+ text-align: center;
227
+ }
228
+ .info-card .label { color: #666; font-size: 0.85rem; }
229
+ .info-card .value { color: #333; font-size: 1.2rem; font-weight: bold; margin-top: 5px; }
230
+ .path-display {
231
+ background: #1a1a2e;
232
+ color: #00ff88;
233
+ padding: 15px 20px;
234
+ border-radius: 8px;
235
+ font-family: 'Monaco', 'Menlo', monospace;
236
+ font-size: 0.9rem;
237
+ word-break: break-all;
238
+ margin-bottom: 20px;
239
+ }
240
+ .status {
241
+ margin-top: 20px;
242
+ padding: 15px;
243
+ background: #e8f5e9;
244
+ border-radius: 8px;
245
+ color: #2e7d32;
246
+ text-align: center;
247
+ }
248
+ .token-box {
249
+ background: #fff3e0;
250
+ padding: 15px;
251
+ border-radius: 8px;
252
+ margin-top: 20px;
253
+ }
254
+ .token-box .label { color: #e65100; font-weight: bold; }
255
+ .token-box .token {
256
+ font-family: monospace;
257
+ background: #ffcc80;
258
+ padding: 8px 12px;
259
+ border-radius: 4px;
260
+ margin-top: 8px;
261
+ display: inline-block;
262
+ }
263
+ </style>
264
+ </head>
265
+ <body>
266
+ <div class="container">
267
+ <div class="header">
268
+ <h1>🔒 CloudSync Share</h1>
269
+ <p>Secure, temporary file sharing session</p>
270
+ </div>
271
+ <div class="content">
272
+ <div class="info-grid">
273
+ <div class="info-card">
274
+ <div class="label">Share ID</div>
275
+ <div class="value">${session.id}</div>
276
+ </div>
277
+ <div class="info-card">
278
+ <div class="label">Type</div>
279
+ <div class="value">${session.type}</div>
280
+ </div>
281
+ <div class="info-card">
282
+ <div class="label">Accesses</div>
283
+ <div class="value">${session.accessCount}</div>
284
+ </div>
285
+ </div>
286
+
287
+ <div class="path-display">
288
+ 📁 ${session.path}
289
+ </div>
290
+
291
+ <div class="token-box">
292
+ <div class="label">🔑 Session Token</div>
293
+ <div class="token">${session.token}</div>
294
+ </div>
295
+
296
+ <div class="status">
297
+ ✅ Share is active and accepting connections
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </body>
302
+ </html>`;
303
+ }
304
+
305
+ export default shareCommand;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * stage.js - Stage files for commit (Git-like)
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { readFileSync, existsSync, writeFileSync, copyFileSync, mkdirSync, readdirSync, statSync } from 'fs';
8
+ import { join, relative, basename } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+
12
+ const stageCommand = new Command('stage')
13
+ .description('📦 Stage files for commit (Git-like staging area)')
14
+ .argument('[files...]', 'Files to stage')
15
+ .option('--all', 'Stage all changed files', false)
16
+ .option('--include <patterns>', 'Include patterns (comma-separated)')
17
+ .option('--exclude <patterns>', 'Exclude patterns (comma-separated)', 'node_modules,.git,dist,build')
18
+ .option('--verbose', 'Show detailed staging info', false)
19
+ .action(async (files, options) => {
20
+ const verbose = options.verbose || process.argv.includes('--verbose');
21
+ const stagingDir = join(process.cwd(), '.cloudsync', 'staging');
22
+
23
+ if (!existsSync(stagingDir)) {
24
+ mkdirSync(stagingDir, { recursive: true });
25
+ }
26
+
27
+ const stagedFiles = [];
28
+ const workspace = process.cwd();
29
+
30
+ if (options.all) {
31
+ // Stage all files
32
+ console.log(chalk.cyan('\n📦 Staging all files...'));
33
+
34
+ const excludePatterns = options.exclude.split(',').map(p => p.trim());
35
+
36
+ function scan(dir) {
37
+ try {
38
+ const entries = readdirSync(dir, { withFileTypes: true });
39
+
40
+ for (const entry of entries) {
41
+ const fullPath = join(dir, entry.name);
42
+ const relPath = relative(workspace, fullPath);
43
+
44
+ // Skip hidden dirs except .cloudsync
45
+ if (entry.name.startsWith('.') && entry.name !== '.cloudsync') continue;
46
+
47
+ // Check exclusions
48
+ if (excludePatterns.some(p => relPath.includes(p))) continue;
49
+
50
+ if (entry.isDirectory()) {
51
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
52
+ scan(fullPath);
53
+ }
54
+ } else if (entry.isFile()) {
55
+ stageFile(fullPath, stagedFiles, stagingDir, verbose);
56
+ }
57
+ }
58
+ } catch (e) {
59
+ // Skip inaccessible directories
60
+ }
61
+ }
62
+
63
+ scan(workspace);
64
+ } else if (files.length > 0) {
65
+ // Stage specific files
66
+ console.log(chalk.cyan('\n📦 Staging specified files...'));
67
+
68
+ for (const file of files) {
69
+ const fullPath = join(workspace, file);
70
+ if (existsSync(fullPath)) {
71
+ stageFile(fullPath, stagedFiles, stagingDir, verbose);
72
+ } else {
73
+ console.log(chalk.yellow(` ⚠️ File not found: ${file}`));
74
+ }
75
+ }
76
+ } else {
77
+ // Show what's staged
78
+ showStagedFiles(stagingDir, verbose);
79
+ return;
80
+ }
81
+
82
+ // Save staged files list
83
+ saveStagedIndex(stagedFiles, verbose);
84
+
85
+ if (stagedFiles.length > 0) {
86
+ console.log(chalk.green('\n✅ Staged ') + chalk.cyan(stagedFiles.length) + chalk.green(' file(s)'));
87
+ console.log(chalk.gray('\n Commit with: ') + chalk.cyan('cloudsync commit "<message>"'));
88
+ } else {
89
+ console.log(chalk.yellow('\n⚠️ No files staged'));
90
+ }
91
+ });
92
+
93
+ function stageFile(filePath, stagedFiles, stagingDir, verbose) {
94
+ try {
95
+ const relPath = relative(process.cwd(), filePath);
96
+ const stagedPath = join(stagingDir, basename(filePath));
97
+
98
+ // Copy to staging area
99
+ copyFileSync(filePath, stagedPath);
100
+ stagedFiles.push(relPath);
101
+
102
+ if (verbose) {
103
+ console.log(chalk.green(` + ${relPath}`));
104
+ }
105
+ } catch (e) {
106
+ if (verbose) console.log(chalk.red(` ✗ Failed to stage: ${filePath}`));
107
+ }
108
+ }
109
+
110
+ function showStagedFiles(stagingDir, verbose) {
111
+ const files = readdirSync(stagingDir).filter(f => f !== 'index.json');
112
+
113
+ console.log(chalk.cyan('\n📦 Staged Files:'));
114
+ console.log(chalk.gray('─'.repeat(40)));
115
+
116
+ if (files.length === 0) {
117
+ console.log(chalk.yellow(' No files staged'));
118
+ console.log(chalk.gray('\n Usage:'));
119
+ console.log(chalk.gray(' cloudsync stage <files...> # Stage specific files'));
120
+ console.log(chalk.gray(' cloudsync stage --all # Stage all'));
121
+ } else {
122
+ files.forEach(f => {
123
+ try {
124
+ const stat = statSync(join(stagingDir, f));
125
+ const size = formatBytes(stat.size);
126
+ console.log(chalk.green(' + ') + chalk.white(f) + chalk.gray(` (${size})`));
127
+ } catch (e) {
128
+ console.log(chalk.green(' + ') + chalk.white(f));
129
+ }
130
+ });
131
+
132
+ console.log(chalk.gray('─'.repeat(40)));
133
+ console.log(chalk.gray(` ${files.length} file(s) staged`));
134
+ }
135
+ }
136
+
137
+ function saveStagedIndex(files, verbose) {
138
+ const indexFile = join(process.cwd(), '.cloudsync', 'staging', 'index.json');
139
+ writeFileSync(indexFile, JSON.stringify({
140
+ files,
141
+ timestamp: new Date().toISOString()
142
+ }, null, 2));
143
+
144
+ if (verbose) console.log(chalk.gray(`Staging index saved`));
145
+ }
146
+
147
+ function formatBytes(bytes) {
148
+ if (bytes === 0) return '0 B';
149
+ const k = 1024;
150
+ const sizes = ['B', 'KB', 'MB', 'GB'];
151
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
152
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
153
+ }
154
+
155
+ export default stageCommand;