d-drive-cli 2.2.2 → 2.2.3

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/README.md CHANGED
@@ -70,7 +70,7 @@ drive config -l
70
70
  #### Upload
71
71
 
72
72
  ```bash
73
- # Upload single file
73
+ # Upload single file (encrypted by default)
74
74
  drive upload ./file.txt
75
75
 
76
76
  # Upload to specific folder
@@ -78,21 +78,22 @@ drive upload ./file.txt /backups/
78
78
 
79
79
  # Upload directory recursively
80
80
  drive upload ./myproject /backups/ -r
81
-
82
- # Upload with encryption
83
- drive upload ./sensitive.txt -e
84
81
  ```
85
82
 
83
+ > **Note:** All CLI uploads are **encrypted by default** for security. Files are encrypted server-side using AES-256-GCM encryption before storage.
84
+
86
85
  #### Download
87
86
 
88
87
  ```bash
89
- # Download file
88
+ # Download file (automatically decrypted)
90
89
  drive download /backups/file.txt
91
90
 
92
91
  # Download to specific location
93
92
  drive download /backups/file.txt ./local-file.txt
94
93
  ```
95
94
 
95
+ > **Note:** Encrypted files are automatically decrypted during download.
96
+
96
97
  #### List
97
98
 
98
99
  ```bash
@@ -108,15 +109,14 @@ drive ls /backups -l
108
109
  ```
109
110
 
110
111
  #### Delete
111
-
112
- ```bash
113
- # Delete file (with confirmation)
112
+ , moves to recycle bin)
114
113
  drive rm /old-file.txt
115
114
 
116
115
  # Force delete without confirmation
117
116
  drive rm /old-file.txt -f
117
+ ```
118
118
 
119
- # Delete directory recursively
119
+ > **Note:** Deleted files are moved to the recycle bin and can be restored via the web interface. The `-r` flag is accepted but directory deletion operates the same way (moves entire directory to recycle bin).elete directory recursively
120
120
  drive rm /old-folder -r
121
121
  ```
122
122
 
@@ -129,26 +129,45 @@ drive cp /backups/file.txt
129
129
  ```
130
130
 
131
131
  ### Task Management
132
-
133
- D-Drive supports SFTP backup tasks that can be managed via CLI.
132
+ Tasks run on a schedule (cron) to automatically backup files from remote SFTP servers.
134
133
 
135
134
  ```bash
136
- # List all tasks
135
+ # List all tasks with status
137
136
  drive tasks ls
138
137
 
139
- # Run a task immediately
138
+ # Run a task immediately (ignores schedule)
140
139
  drive tasks run <task-id>
141
140
 
142
141
  # Stop a running task
143
142
  drive tasks stop <task-id>
144
143
 
145
- # Enable/disable a task
144
+ # Enable a task (allows scheduled runs)
146
145
  drive tasks enable <task-id>
146
+
147
+ # Disable a task (prevents scheduled runs)
147
148
  drive tasks disable <task-id>
148
149
 
149
- # Delete a task
150
+ # Delete a task (with confirmation)
150
151
  drive tasks rm <task-id>
151
- drive tasks rm <task-id> -f # Force delete
152
+
153
+ # Force delete without confirmation
154
+ drive tasks rm <task-id> -f
155
+ ```
156
+
157
+ **Task List Output:**
158
+ ```
159
+ 📋 SFTP Backup Tasks:
160
+ ────────────────────────────────────────────────────────
161
+ ID: clmxyz123...
162
+ Name: Daily Backup
163
+ Status: ✓ Running (5m 23s) | ⏸ Stopped | ✓ Enabled
164
+ Schedule: 0 2 * * * (daily at 2:00 AM)
165
+ SFTP: user@server.example.com:22 → /backups/
166
+ Compression: GZIP | Keep: 5 files
167
+ ────────────────────────────────────────────────────────
168
+ ```
169
+
170
+ > **Note:** Tasks can only be created/edited via the web interface. The CLI allows management and execution only.ve tasks rm <task-id> -f # Force delete
152
171
  ```
153
172
 
154
173
  ### Info & Status
@@ -194,10 +213,10 @@ Interactive mode provides a menu-driven interface for all operations.
194
213
  ### Backup a Project
195
214
 
196
215
  ```bash
197
- # Create a backup of your project
216
+ # Create an encrypted backup of your project
198
217
  drive upload ./my-project /backups/my-project/ -r
199
218
 
200
- # List backups
219
+ # List backups with details
201
220
  drive ls /backups/my-project -l
202
221
  ```
203
222
 
@@ -205,29 +224,207 @@ drive ls /backups/my-project -l
205
224
 
206
225
  ```bash
207
226
  #!/bin/bash
208
- # backup.sh
227
+ # backup.sh - Daily project backup
209
228
 
210
229
  DATE=$(date +%Y-%m-%d)
211
- drive upload ./data "/backups/$DATE/"
212
- echo "Backup completed: $DATE"
230
+ PROJECT_NAME="my-app"
231
+
232
+ echo "Starting backup for $PROJECT_NAME on $DATE..."
233
+
234
+ # Upload with automatic encryption
235
+ drive upload ./ "/backups/$PROJECT_NAME/$DATE/" -r
236
+
237
+ if [ $? -eq 0 ]; then
238
+ echo "✓ Backup completed successfully: $DATE"
239
+
240
+ # List to verify
241
+ drive ls "/backups/$PROJECT_NAME/" -l
242
+ else
243
+ echo "✗ Backup failed!"
244
+ exit 1
245
+ fi
246
+ ```configure the CLI using environment variables instead of `drive config`:
247
+
248
+ ```bash
249
+ # Set in your shell profile (~/.bashrc, ~/.zshrc, etc.)
250
+ export DDRIVE_API_KEY=dd_your_api_key_here
251
+ export DDRIVE_API_URL=https://your-server/api
252
+
253
+ # Or use inline for single commands
254
+ DDRIVE_API_KEY=dd_key123 drive ls /
213
255
  ```
214
256
 
215
- ### Download and Restore
257
+ **Variables:**
258
+ - `DDRIVE_API_KEY` - Your D-Drive API key (starts with `dd_`)
259
+ - `DDRIVE_API_URL` - API base URL (default: `https://localhost/api`)
260
+
261
+ **Priority:**
262
+ 1. Environment variables (highest)
263
+ 2. Config file (`~/.ddrive-cli-config`)
264
+ 3. Default values (lowest)
216
265
 
266
+ **View current configuration:**
217
267
  ```bash
268
+ drive config --list
269
+ # List available backups
270
+ drive ls /backups/ -l
271
+
218
272
  # Download latest backup
219
273
  drive download /backups/2026-01-24/data.tar.gz ./restore/
220
274
 
221
275
  # Extract
222
- tar -xzf ./restore/data.tar.gz
276
+ tar -xzf ./restore/data.tar.gz -C ./restore/
277
+
278
+ echo "Restore complete!"
279
+ ```
280
+
281
+ ### Monitor Task Status
282
+
283
+ ```bash
284
+ #!/bin/bash
285
+ # check-tasks.sh - Monitor backup task status
286
+
287
+ echo "Checking SFTP backup tasks..."
288
+ drive tasks ls
289
+
290
+ # Run a specific task
291
+ TASK_ID="clmxyz123..."
292
+ echo "Running task $TASK_ID..."
293
+ drive tasks run $TASK_ID
294
+ **Causes:**
295
+ - Server is not running
296
+ - Incorrect API URL
297
+ - Network/firewall issues
298
+ - SSL/TLS certificate problems
299
+
300
+ **Solutions:**
301
+ ```bash
302
+ # Check your configuration
303
+ drive config --list
304
+
305
+ # Test with correct URL (include /api path)
306
+ drive config --url https://your-server/api
307
+
308
+ # Verify server is accessible
309
+ curl https://your-server/api/setup/status
310
+
311
+ # For local development with self-signed certs
312
+ export NODE_TLS_REJECT_UNAUTHORIZED=0 # Not recommended for production!
313
+ ```
314
+
315
+ ### "Invalid API key"
316
+ **Causes:**
317
+ - API key is incorrect or expired
318
+ - Missing `dd_` prefix
319
+ - Extra spaces or characters
320
+
321
+ **Solutions:**
322
+ ```bash
323
+ # Generate a new API key:
324
+ # 1. Open D-Drive web interface
325
+ # 2. Go to Settings → API Keys
326
+ # 3. Click "Create API Key"
327
+ # 4. Copy the full key (including dd_ prefix)
328
+
329
+ # Configure with new key
330
+ drive config --key dd_your_new_key_here
331
+
332
+ # Verify it works
333
+ drive info
223
334
  ```
224
335
 
225
- ## Environment Variables
336
+ ### "Permission denied"
337
+ **Causes:**
338
+ - Using wrong account/API key
339
+ - File/folder doesn't belong to you
340
+ - Insufficient permissions
341
+
342
+ **Solutions:**
343
+ ```bash
344
+ # Check which user you're authenticated as
345
+ drive info
346
+
347
+ # Ensure you're using the correct API key
348
+ drive config --list
349
+ ```
350
+
351
+ ### "File not found" when listing nested folders
352
+ **Causes:**
353
+ - Folder path typo
354
+ - Folder doesn't exist
355
+ - Path should start with /
356
+
357
+ **Solutions:**
358
+ ```bash
359
+ # List root to see available folders
360
+ drive ls /
361
+
362
+ # Use absolute paths
363
+ drive ls /backups/subfolder
364
+
365
+ # Use long format to see full paths
366
+ drive ls / -l
367
+ ```
368
+
369
+ ### Upload/Download Progress Not Showing
370
+ **Causes:**
371
+ - File is too small (progress skipped)
372
+ - Terminal doesn't support progress bars
373
+
374
+ **Solutions:**
375
+ - Progress bars automatically appear for larger files
376
+ - Check that your terminal supports ANSI escape codes
377
+ - Try in a different terminal (iTerm2, gnome-terminal, etc.)
226
378
 
227
- You can also configure the CLI using environment variables:
379
+ ### Task Won't Run
380
+ **Causes:**
381
+ - Task is disabled
382
+ - Another task instance is already running
383
+ - SFTP credentials are invalid
384
+ - Network connectivity issues
228
385
 
386
+ **Solutions:**
229
387
  ```bash
230
- export DDRIVE_API_KEY=dd_your_api_key
388
+ # Check task status
389
+ drive tasks ls
390
+
391
+ # Enable if disabled
392
+ drive tasks enable <task-id>
393
+
394
+ # If shows "Running" but stuck, stop and restart
395
+ drive tasks stop <task-id>
396
+ drive tasks run <task-id>
397
+
398
+ # Check server logs for detailed errors
399
+ ```
400
+
401
+ ### npm Installation Issues
402
+ **MacOS/Linux:**
403
+ ```bash
404
+ # Permission errors - use one of these methods:
405
+
406
+ # Method 1: Install globally with correct permissions
407
+ sudo npm install -g d-drive-cli
408
+
409
+ # Method 2: Use npx (no installation needed)
410
+ npx d-drive-cli ls /
411
+
412
+ # Method 3: Configure npm to use different directory
413
+ mkdir ~/.npm-global
414
+ npm config set prefix '~/.npm-global'
415
+ echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
416
+ source ~/.bashrc
417
+ npm install -g d-drive-cli
418
+ ```
419
+
420
+ **Windows:**
421
+ ```powershell
422
+ # Run PowerShell as Administrator
423
+ npm install -g d-drive-cli
424
+
425
+ # Or use npx
426
+ npx d-drive-cli ls /
427
+ ```
231
428
  export DDRIVE_API_URL=https://your-server/api
232
429
  ```
233
430
 
@@ -249,6 +446,14 @@ export DDRIVE_API_URL=https://your-server/api
249
446
 
250
447
  ## Changelog
251
448
 
449
+ ### v2.2.3
450
+ - **Bug fixes:**
451
+ - Fixed nested folder path resolution in `list` command
452
+ - Improved API key validation (now uses `/auth/me` endpoint)
453
+ - Added directory deletion warning message
454
+ - Better error messages for directory download attempts
455
+ - **Documentation:** Enhanced CLI README and API docs
456
+
252
457
  ### v2.2.2
253
458
  - **ACTUALLY** fixed double output issue (previous v2.2.1 had wrong fix)
254
459
  - Added `process.exit(0)` after help display to prevent Commander.js duplicate
@@ -26,8 +26,8 @@ async function configCommand(options) {
26
26
  // Accept keys entered with or without the `dd_` prefix, and strip any accidental "Bearer " prefix
27
27
  const rawKey = options.key.replace(/^Bearer\s+/i, '').trim();
28
28
  const normalizedKey = rawKey.startsWith('dd_') ? rawKey : `dd_${rawKey}`;
29
- // Validate API key by calling a protected endpoint that accepts API keys (authenticate middleware)
30
- const validateUrl = apiUrl.replace(/\/$/, '') + '/api-keys';
29
+ // Validate API key by calling /auth/me endpoint (simpler and more reliable)
30
+ const validateUrl = apiUrl.replace(/\/$/, '') + '/auth/me';
31
31
  const response = await axios_1.default.get(validateUrl, {
32
32
  headers: {
33
33
  Authorization: `Bearer ${normalizedKey}`,
@@ -23,6 +23,10 @@ async function deleteCommand(remotePath, options) {
23
23
  }
24
24
  const file = files[0];
25
25
  spinner.stop();
26
+ // Warn if trying to delete a directory
27
+ if (file.type === 'DIRECTORY') {
28
+ console.log(chalk_1.default.yellow('Warning: This is a directory. All contents will be moved to recycle bin.'));
29
+ }
26
30
  // Confirm deletion
27
31
  if (!options.force) {
28
32
  const answers = await inquirer_1.default.prompt([
@@ -26,6 +26,8 @@ async function downloadCommand(source, destination = './', options) {
26
26
  const file = files[0];
27
27
  if (file.type === 'DIRECTORY') {
28
28
  spinner.fail(chalk_1.default.red('Cannot download directories yet'));
29
+ console.log(chalk_1.default.gray('Tip: Download individual files from the directory instead.'));
30
+ console.log(chalk_1.default.gray('Use: drive ls ' + source + ' -l'));
29
31
  return;
30
32
  }
31
33
  spinner.text = 'Downloading...';
@@ -61,13 +61,22 @@ async function listCommand(remotePath = '/', options) {
61
61
  }
62
62
  }
63
63
  function findFolderByPath(files, targetPath) {
64
- // Simple path matching - looks for folder by name in the path
65
- const pathParts = targetPath.split('/').filter(p => p);
64
+ // Build full path for each file and match against target
65
+ const normalizedTarget = targetPath.startsWith('/') ? targetPath : '/' + targetPath;
66
66
  for (const file of files) {
67
- if (file.type === 'DIRECTORY' && file.name === pathParts[0]) {
67
+ if (file.type === 'DIRECTORY' && file.path === normalizedTarget) {
68
68
  return file;
69
69
  }
70
70
  }
71
+ // Fallback: try matching by name only (for root-level folders)
72
+ const pathParts = targetPath.split('/').filter(p => p);
73
+ if (pathParts.length === 1) {
74
+ for (const file of files) {
75
+ if (file.type === 'DIRECTORY' && file.name === pathParts[0]) {
76
+ return file;
77
+ }
78
+ }
79
+ }
71
80
  return null;
72
81
  }
73
82
  function formatFileSize(bytes) {
package/dist/index.js CHANGED
@@ -20,8 +20,8 @@ const program = new commander_1.Command();
20
20
  // ASCII art banner
21
21
  const banner = `
22
22
  ${chalk_1.default.cyan('╔═══════════════════════════════════════╗')}
23
- ${chalk_1.default.cyan('║')} ${chalk_1.default.bold.white('D-Drive CLI')} ${chalk_1.default.gray('v' + (pkg.version || '2.2.0'))} ${chalk_1.default.cyan('║')}
24
- ${chalk_1.default.cyan('║')} ${chalk_1.default.gray('Discord-based cloud storage')} ${chalk_1.default.cyan('║')}
23
+ ${chalk_1.default.cyan('║')} ${chalk_1.default.bold.white('D-Drive CLI')} ${chalk_1.default.gray('v' + (pkg.version || '2.2.0'))} ${chalk_1.default.cyan(' ║')}
24
+ ${chalk_1.default.cyan('║')} ${chalk_1.default.gray('Discord-based cloud storage')} ${chalk_1.default.cyan(' ║')}
25
25
  ${chalk_1.default.cyan('╚═══════════════════════════════════════╝')}
26
26
  `;
27
27
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "d-drive-cli",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "D-Drive CLI tool for developers - Discord cloud storage",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -21,23 +21,23 @@
21
21
  "author": "jasonzli-DEV",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "commander": "^11.1.0",
25
24
  "axios": "^1.6.5",
26
25
  "chalk": "^4.1.2",
27
- "ora": "^5.4.1",
26
+ "commander": "^11.1.0",
28
27
  "conf": "^10.2.0",
29
- "inquirer": "^8.2.6",
30
28
  "form-data": "^4.0.0",
31
29
  "fs-extra": "^11.2.0",
32
30
  "glob": "^10.3.10",
31
+ "inquirer": "^8.2.6",
32
+ "ora": "^5.4.1",
33
33
  "progress": "^2.0.3"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/node": "^20.10.7",
37
- "@types/inquirer": "^8.2.10",
38
36
  "@types/fs-extra": "^11.0.4",
37
+ "@types/inquirer": "^8.2.10",
38
+ "@types/node": "^20.10.7",
39
39
  "@types/progress": "^2.0.7",
40
- "typescript": "^5.3.3",
41
- "ts-node": "^10.9.2"
40
+ "ts-node": "^10.9.2",
41
+ "typescript": "^5.9.3"
42
42
  }
43
43
  }
@@ -31,8 +31,8 @@ export async function configCommand(options: ConfigOptions) {
31
31
  const rawKey = options.key.replace(/^Bearer\s+/i, '').trim();
32
32
  const normalizedKey = rawKey.startsWith('dd_') ? rawKey : `dd_${rawKey}`;
33
33
 
34
- // Validate API key by calling a protected endpoint that accepts API keys (authenticate middleware)
35
- const validateUrl = apiUrl.replace(/\/$/, '') + '/api-keys';
34
+ // Validate API key by calling /auth/me endpoint (simpler and more reliable)
35
+ const validateUrl = apiUrl.replace(/\/$/, '') + '/auth/me';
36
36
  const response = await axios.get(validateUrl, {
37
37
  headers: {
38
38
  Authorization: `Bearer ${normalizedKey}`,
@@ -28,6 +28,11 @@ export async function deleteCommand(remotePath: string, options: DeleteOptions)
28
28
  const file = files[0];
29
29
  spinner.stop();
30
30
 
31
+ // Warn if trying to delete a directory
32
+ if (file.type === 'DIRECTORY') {
33
+ console.log(chalk.yellow('Warning: This is a directory. All contents will be moved to recycle bin.'));
34
+ }
35
+
31
36
  // Confirm deletion
32
37
  if (!options.force) {
33
38
  const answers = await inquirer.prompt([
@@ -34,6 +34,8 @@ export async function downloadCommand(
34
34
 
35
35
  if (file.type === 'DIRECTORY') {
36
36
  spinner.fail(chalk.red('Cannot download directories yet'));
37
+ console.log(chalk.gray('Tip: Download individual files from the directory instead.'));
38
+ console.log(chalk.gray('Use: drive ls ' + source + ' -l'));
37
39
  return;
38
40
  }
39
41
 
@@ -71,14 +71,25 @@ export async function listCommand(remotePath: string = '/', options: ListOptions
71
71
  }
72
72
 
73
73
  function findFolderByPath(files: any[], targetPath: string): any | null {
74
- // Simple path matching - looks for folder by name in the path
75
- const pathParts = targetPath.split('/').filter(p => p);
74
+ // Build full path for each file and match against target
75
+ const normalizedTarget = targetPath.startsWith('/') ? targetPath : '/' + targetPath;
76
76
 
77
77
  for (const file of files) {
78
- if (file.type === 'DIRECTORY' && file.name === pathParts[0]) {
78
+ if (file.type === 'DIRECTORY' && file.path === normalizedTarget) {
79
79
  return file;
80
80
  }
81
81
  }
82
+
83
+ // Fallback: try matching by name only (for root-level folders)
84
+ const pathParts = targetPath.split('/').filter(p => p);
85
+ if (pathParts.length === 1) {
86
+ for (const file of files) {
87
+ if (file.type === 'DIRECTORY' && file.name === pathParts[0]) {
88
+ return file;
89
+ }
90
+ }
91
+ }
92
+
82
93
  return null;
83
94
  }
84
95
 
package/src/index.ts CHANGED
@@ -18,8 +18,8 @@ const program = new Command();
18
18
  // ASCII art banner
19
19
  const banner = `
20
20
  ${chalk.cyan('╔═══════════════════════════════════════╗')}
21
- ${chalk.cyan('║')} ${chalk.bold.white('D-Drive CLI')} ${chalk.gray('v' + (pkg.version || '2.2.0'))} ${chalk.cyan('║')}
22
- ${chalk.cyan('║')} ${chalk.gray('Discord-based cloud storage')} ${chalk.cyan('║')}
21
+ ${chalk.cyan('║')} ${chalk.bold.white('D-Drive CLI')} ${chalk.gray('v' + (pkg.version || '2.2.0'))} ${chalk.cyan(' ║')}
22
+ ${chalk.cyan('║')} ${chalk.gray('Discord-based cloud storage')} ${chalk.cyan(' ║')}
23
23
  ${chalk.cyan('╚═══════════════════════════════════════╝')}
24
24
  `;
25
25