d-drive-cli 2.0.0 → 2.2.0

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
@@ -1,225 +1,271 @@
1
- # D-Drive CLI
1
+ # D-Drive CLI v2.2.0
2
2
 
3
3
  Command-line tool for D-Drive cloud storage.
4
4
 
5
- **Version: 2.0.0 LTS**
5
+ ```
6
+ ╔═══════════════════════════════════════╗
7
+ ║ D-Drive CLI v2.2.0 ║
8
+ ║ Discord-based cloud storage ║
9
+ ╚═══════════════════════════════════════╝
10
+ ```
6
11
 
7
12
  ## Installation
8
13
 
9
14
  ```bash
15
+ # Install globally
10
16
  npm install -g d-drive-cli
17
+
18
+ # Or use npx
19
+ npx d-drive-cli
11
20
  ```
12
21
 
13
- ## Configuration
22
+ After installation, you can use either command:
23
+ - `d-drive` - Full command name
24
+ - `drive` - Short alias
14
25
 
15
- First, configure your API key:
26
+ ## Quick Start
16
27
 
17
28
  ```bash
18
- d-drive config --key YOUR_API_KEY
19
- ```
29
+ # Interactive configuration (recommended)
30
+ drive config
20
31
 
21
- You can get an API key from the D-Drive web interface at Settings → API Keys.
32
+ # Or set API key directly
33
+ drive config --key YOUR_API_KEY --url https://your-server/api
22
34
 
23
- Optional: Set a custom API URL:
35
+ # Check connection
36
+ drive info
24
37
 
25
- ```bash
26
- d-drive config --url https://your-ddrive-instance.com/api
27
- ```
38
+ # Upload a file
39
+ drive upload ./backup.zip
28
40
 
29
- View current configuration:
41
+ # List files
42
+ drive ls
30
43
 
31
- ```bash
32
- d-drive config --list
44
+ # Download a file
45
+ drive download /backup.zip ./local-backup.zip
33
46
  ```
34
47
 
35
- ## Usage
48
+ ## Commands
36
49
 
37
- ### Upload Files
38
-
39
- Upload a single file:
50
+ ### Configuration
40
51
 
41
52
  ```bash
42
- d-drive upload ./myfile.txt /backups/
43
- ```
53
+ # Interactive setup
54
+ drive config
44
55
 
45
- Note: For very large files the server exposes a streaming upload endpoint (`POST /api/files/upload/stream`) that accepts multipart uploads and streams chunks directly to the storage backend without full buffering. Use the API streaming endpoint (see `docs/API.md`) for multi-GB uploads or when you need more robust handling for long uploads.
56
+ # Set API key
57
+ drive config --key dd_your_api_key
46
58
 
47
- Upload a directory recursively:
59
+ # Set API URL
60
+ drive config --url https://your-server/api
48
61
 
49
- ```bash
50
- d-drive upload ./myproject /backups/projects/ -r
62
+ # View current config
63
+ drive config --list
64
+ # or
65
+ drive config -l
51
66
  ```
52
67
 
53
- ### Download Files
68
+ ### File Operations
54
69
 
55
- Download a file:
70
+ #### Upload
56
71
 
57
72
  ```bash
58
- d-drive download /backups/myfile.txt ./restored.txt
59
- ```
73
+ # Upload single file
74
+ drive upload ./file.txt
60
75
 
61
- ### List Files
76
+ # Upload to specific folder
77
+ drive upload ./file.txt /backups/
62
78
 
63
- List files in root:
79
+ # Upload directory recursively
80
+ drive upload ./myproject /backups/ -r
64
81
 
65
- ```bash
66
- d-drive list
67
- # or
68
- d-drive ls
82
+ # Upload with encryption
83
+ drive upload ./sensitive.txt -e
69
84
  ```
70
85
 
71
- List files in a directory:
86
+ #### Download
72
87
 
73
88
  ```bash
74
- d-drive list /backups
75
- ```
76
-
77
- Long format with details:
89
+ # Download file
90
+ drive download /backups/file.txt
78
91
 
79
- ```bash
80
- d-drive list /backups -l
92
+ # Download to specific location
93
+ drive download /backups/file.txt ./local-file.txt
81
94
  ```
82
95
 
83
- ### Delete Files
84
-
85
- Delete a file:
96
+ #### List
86
97
 
87
98
  ```bash
88
- d-drive delete /backups/old-file.txt
89
- ```
99
+ # List root directory
100
+ drive ls
90
101
 
91
- Force delete without confirmation:
102
+ # List specific directory
103
+ drive ls /backups
92
104
 
93
- ```bash
94
- d-drive delete /backups/old-file.txt -f
105
+ # Long format with details
106
+ drive ls -l
107
+ drive ls /backups -l
95
108
  ```
96
109
 
97
- ## Examples
98
-
99
- ### Automated Backups
100
-
101
- Create a backup script:
110
+ #### Delete
102
111
 
103
112
  ```bash
104
- #!/bin/bash
105
- # backup.sh
106
-
107
- # Backup database
108
- pg_dump mydb > /tmp/backup.sql
109
- d-drive upload /tmp/backup.sql /backups/database/backup-$(date +%Y%m%d).sql
113
+ # Delete file (with confirmation)
114
+ drive rm /old-file.txt
110
115
 
111
- # Backup config files
112
- d-drive upload /etc/myapp /backups/config/ -r
116
+ # Force delete without confirmation
117
+ drive rm /old-file.txt -f
113
118
 
114
- # Cleanup
115
- rm /tmp/backup.sql
119
+ # Delete directory recursively
120
+ drive rm /old-folder -r
116
121
  ```
117
122
 
118
- Add to crontab for daily backups:
123
+ #### Copy
119
124
 
120
125
  ```bash
121
- 0 2 * * * /path/to/backup.sh
126
+ # Create a copy of a file
127
+ drive cp /backups/file.txt
128
+ # Creates: /backups/file (1).txt
122
129
  ```
123
130
 
124
- ### Continuous Integration
131
+ ### Task Management
125
132
 
126
- Upload build artifacts from CI/CD:
133
+ D-Drive supports SFTP backup tasks that can be managed via CLI.
127
134
 
128
- ```yaml
129
- # .github/workflows/deploy.yml
130
- - name: Upload build artifacts
131
- run: |
132
- npm run build
133
- d-drive upload ./dist /releases/${{ github.sha }}/ -r
134
- ```
135
-
136
- ## Options
135
+ ```bash
136
+ # List all tasks
137
+ drive tasks ls
137
138
 
138
- ### Global Options
139
+ # Run a task immediately
140
+ drive tasks run <task-id>
139
141
 
140
- - `--version` - Show version number
141
- - `--help` - Show help
142
+ # Stop a running task
143
+ drive tasks stop <task-id>
142
144
 
143
- ### Upload Options
145
+ # Enable/disable a task
146
+ drive tasks enable <task-id>
147
+ drive tasks disable <task-id>
144
148
 
145
- - `-r, --recursive` - Upload directory recursively
146
- - `--no-progress` - Disable progress bar
149
+ # Delete a task
150
+ drive tasks rm <task-id>
151
+ drive tasks rm <task-id> -f # Force delete
152
+ ```
147
153
 
148
- ### Download Options
154
+ ### Info & Status
149
155
 
150
- - `--no-progress` - Disable progress bar
156
+ ```bash
157
+ # Show connection status and user info
158
+ drive info
159
+ ```
151
160
 
152
- ### List Options
161
+ Output:
162
+ ```
163
+ Connection Status:
164
+ ─────────────────────────────────
165
+ API URL: https://your-server/api
166
+ API Key: ✓ Configured
167
+ Status: ✓ Connected
168
+ User: YourUsername
169
+ ─────────────────────────────────
170
+ ```
153
171
 
154
- - `-l, --long` - Use long listing format
172
+ ### Interactive Mode
155
173
 
156
- ### Delete Options
174
+ ```bash
175
+ # Start interactive mode
176
+ drive interactive
177
+ # or
178
+ drive i
179
+ ```
157
180
 
158
- - `-f, --force` - Force deletion without confirmation
159
- - `-r, --recursive` - Delete directory recursively
181
+ Interactive mode provides a menu-driven interface for all operations.
160
182
 
161
- ---
183
+ ## Getting Your API Key
162
184
 
163
- ## Task Management
185
+ 1. Open D-Drive web interface
186
+ 2. Go to **Settings** (gear icon)
187
+ 3. Scroll to **API Keys** section
188
+ 4. Click **Create API Key**
189
+ 5. Copy the key (starts with `dd_`)
190
+ 6. Use in CLI: `drive config --key dd_your_key`
164
191
 
165
- D-Drive supports automated SFTP backup tasks. Use the CLI to manage them:
192
+ ## Examples
166
193
 
167
- ### List Tasks
194
+ ### Backup a Project
168
195
 
169
196
  ```bash
170
- d-drive tasks list
171
- # or
172
- d-drive tasks ls
173
- ```
197
+ # Create a backup of your project
198
+ drive upload ./my-project /backups/my-project/ -r
174
199
 
175
- ### Run a Task Immediately
176
-
177
- ```bash
178
- d-drive tasks run <taskId>
200
+ # List backups
201
+ drive ls /backups/my-project -l
179
202
  ```
180
203
 
181
- ### Stop a Running Task
204
+ ### Automated Backup Script
182
205
 
183
206
  ```bash
184
- d-drive tasks stop <taskId>
185
- ```
186
-
187
- ### Enable/Disable a Task
207
+ #!/bin/bash
208
+ # backup.sh
188
209
 
189
- ```bash
190
- d-drive tasks enable <taskId>
191
- d-drive tasks disable <taskId>
210
+ DATE=$(date +%Y-%m-%d)
211
+ drive upload ./data "/backups/$DATE/"
212
+ echo "Backup completed: $DATE"
192
213
  ```
193
214
 
194
- ### Delete a Task
215
+ ### Download and Restore
195
216
 
196
217
  ```bash
197
- d-drive tasks delete <taskId>
218
+ # Download latest backup
219
+ drive download /backups/2026-01-24/data.tar.gz ./restore/
198
220
 
199
- # Force delete without confirmation
200
- d-drive tasks delete <taskId> -f
221
+ # Extract
222
+ tar -xzf ./restore/data.tar.gz
201
223
  ```
202
224
 
203
- ### Task Examples
225
+ ## Environment Variables
204
226
 
205
- ```bash
206
- # List all tasks with status
207
- $ d-drive tasks list
208
- ● Daily Server Backup [RUNNING]
209
- ID: abc123
210
- Schedule: 0 2 * * *
211
- SFTP: backupuser@backup.example.com:22/backups
212
- Destination: /backups/server
213
- Last Run: 1/20/2026, 2:00:00 AM (2m 15s)
214
-
215
- # Run a backup task manually
216
- $ d-drive tasks run abc123
227
+ You can also configure the CLI using environment variables:
217
228
 
218
- # Stop a running task
219
- $ d-drive tasks stop abc123
229
+ ```bash
230
+ export DDRIVE_API_KEY=dd_your_api_key
231
+ export DDRIVE_API_URL=https://your-server/api
220
232
  ```
221
233
 
222
- ---
234
+ ## Troubleshooting
235
+
236
+ ### "Cannot connect to server"
237
+ - Check your API URL is correct
238
+ - Ensure the server is running
239
+ - Verify network connectivity
240
+
241
+ ### "Invalid API key"
242
+ - Generate a new API key in Settings
243
+ - Ensure the key starts with `dd_`
244
+ - Check for extra spaces or characters
245
+
246
+ ### "Permission denied"
247
+ - Verify you're logged in with the correct account
248
+ - Check file/folder permissions in D-Drive
249
+
250
+ ## Changelog
251
+
252
+ ### v2.2.0
253
+ - Added `drive` command alias for easier use
254
+ - Interactive mode with menu-driven interface
255
+ - `drive info` command for connection status
256
+ - Interactive configuration wizard
257
+ - Better error messages
258
+ - Colorized output
259
+ - Added `cp` alias for copy command
260
+ - Added `up` and `dl` aliases
261
+
262
+ ### v2.1.0
263
+ - Task management commands
264
+ - Encryption support
265
+ - Progress bars
266
+
267
+ ### v2.0.0
268
+ - Initial release with upload/download/list/delete
223
269
 
224
270
  ## License
225
271
 
@@ -22,7 +22,7 @@ async function configCommand(options) {
22
22
  try {
23
23
  // Normalize and test the API key by calling the /auth/me endpoint
24
24
  const config = (0, config_1.getConfig)();
25
- const apiUrl = options.url || config.apiUrl || 'http://localhost:5000/api';
25
+ const apiUrl = options.url || config.apiUrl || 'https://localhost/api';
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}`;
package/dist/config.js CHANGED
@@ -11,7 +11,7 @@ const conf_1 = __importDefault(require("conf"));
11
11
  const config = new conf_1.default({
12
12
  projectName: 'd-drive',
13
13
  defaults: {
14
- apiUrl: 'http://localhost:5000/api',
14
+ apiUrl: 'https://localhost/api',
15
15
  },
16
16
  });
17
17
  function getConfig() {
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const commander_1 = require("commander");
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
+ const inquirer_1 = __importDefault(require("inquirer"));
9
10
  const config_1 = require("./commands/config");
10
11
  const upload_1 = require("./commands/upload");
11
12
  const download_1 = require("./commands/download");
@@ -13,30 +14,64 @@ const list_1 = require("./commands/list");
13
14
  const delete_1 = require("./commands/delete");
14
15
  const copy_1 = require("./commands/copy");
15
16
  const tasks_1 = require("./commands/tasks");
17
+ const config_2 = require("./config");
16
18
  const pkg = require('../package.json');
17
19
  const program = new commander_1.Command();
20
+ // ASCII art banner
21
+ const banner = `
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('║')}
25
+ ${chalk_1.default.cyan('╚═══════════════════════════════════════╝')}
26
+ `;
18
27
  program
19
28
  .name('d-drive')
20
- .description('D-Drive CLI - Discord-based cloud storage for developers')
21
- .version(pkg.version || '0.0.0');
22
- // Config command
29
+ .description('D-Drive CLI - Discord-based cloud storage')
30
+ .version(pkg.version || '2.2.0')
31
+ .hook('preAction', (thisCommand) => {
32
+ // Show banner only for main commands, not subcommands
33
+ if (thisCommand.name() === 'd-drive' && !process.argv.includes('--help') && !process.argv.includes('-h')) {
34
+ // Don't show banner for quick commands
35
+ }
36
+ });
37
+ // Interactive mode when no command is provided
38
+ program
39
+ .command('interactive', { isDefault: false })
40
+ .alias('i')
41
+ .description('Start interactive mode')
42
+ .action(async () => {
43
+ console.log(banner);
44
+ await interactiveMode();
45
+ });
46
+ // Config command with interactive setup
23
47
  program
24
48
  .command('config')
25
49
  .description('Configure D-Drive CLI')
26
50
  .option('-k, --key <apiKey>', 'Set API key')
27
51
  .option('-u, --url <url>', 'Set API URL')
28
52
  .option('-l, --list', 'List current configuration')
29
- .action(config_1.configCommand);
53
+ .option('-i, --interactive', 'Interactive configuration')
54
+ .action(async (options) => {
55
+ if (options.interactive || (!options.key && !options.url && !options.list)) {
56
+ await interactiveConfig();
57
+ }
58
+ else {
59
+ await (0, config_1.configCommand)(options);
60
+ }
61
+ });
30
62
  // Upload command
31
63
  program
32
64
  .command('upload <source> [destination]')
65
+ .alias('up')
33
66
  .description('Upload a file or directory to D-Drive')
34
67
  .option('-r, --recursive', 'Upload directory recursively')
68
+ .option('-e, --encrypt', 'Encrypt the file')
35
69
  .option('--no-progress', 'Disable progress bar')
36
70
  .action(upload_1.uploadCommand);
37
71
  // Download command
38
72
  program
39
73
  .command('download <source> [destination]')
74
+ .alias('dl')
40
75
  .description('Download a file from D-Drive')
41
76
  .option('--no-progress', 'Disable progress bar')
42
77
  .action(download_1.downloadCommand);
@@ -46,6 +81,7 @@ program
46
81
  .alias('ls')
47
82
  .description('List files in D-Drive')
48
83
  .option('-l, --long', 'Use long listing format')
84
+ .option('-a, --all', 'Include hidden files')
49
85
  .action(list_1.listCommand);
50
86
  // Delete command
51
87
  program
@@ -58,12 +94,49 @@ program
58
94
  // Copy command
59
95
  program
60
96
  .command('copy <path>')
97
+ .alias('cp')
61
98
  .description('Make a copy of a file in-place (auto-numbered)')
62
99
  .action(copy_1.copyCommand);
100
+ // Info command
101
+ program
102
+ .command('info')
103
+ .description('Show D-Drive connection info and status')
104
+ .action(async () => {
105
+ const config = (0, config_2.getConfig)();
106
+ console.log(banner);
107
+ console.log(chalk_1.default.bold('Connection Status:'));
108
+ console.log(chalk_1.default.gray('─────────────────────────────────'));
109
+ console.log(`${chalk_1.default.gray('API URL:')} ${config.apiUrl ? chalk_1.default.cyan(config.apiUrl) : chalk_1.default.red('Not configured')}`);
110
+ console.log(`${chalk_1.default.gray('API Key:')} ${config.apiKey ? chalk_1.default.green('✓ Configured') : chalk_1.default.red('✗ Not configured')}`);
111
+ if (config.apiKey && config.apiUrl) {
112
+ const axios = require('axios');
113
+ try {
114
+ const response = await axios.get(`${config.apiUrl}/auth/me`, {
115
+ headers: { Authorization: `Bearer ${config.apiKey}` },
116
+ timeout: 5000,
117
+ });
118
+ console.log(`${chalk_1.default.gray('Status:')} ${chalk_1.default.green('✓ Connected')}`);
119
+ console.log(`${chalk_1.default.gray('User:')} ${chalk_1.default.white(response.data.username || response.data.id)}`);
120
+ }
121
+ catch (e) {
122
+ if (e.code === 'ECONNREFUSED') {
123
+ console.log(`${chalk_1.default.gray('Status:')} ${chalk_1.default.red('✗ Cannot connect to server')}`);
124
+ }
125
+ else if (e.response?.status === 401) {
126
+ console.log(`${chalk_1.default.gray('Status:')} ${chalk_1.default.red('✗ Invalid API key')}`);
127
+ }
128
+ else {
129
+ console.log(`${chalk_1.default.gray('Status:')} ${chalk_1.default.yellow('? Unable to verify')}`);
130
+ }
131
+ }
132
+ }
133
+ console.log(chalk_1.default.gray('─────────────────────────────────'));
134
+ console.log(chalk_1.default.gray(`Run ${chalk_1.default.white('drive config')} to configure`));
135
+ });
63
136
  // Tasks commands
64
137
  const tasks = program
65
138
  .command('tasks')
66
- .description('Manage backup tasks');
139
+ .description('Manage SFTP backup tasks');
67
140
  tasks
68
141
  .command('list')
69
142
  .alias('ls')
@@ -91,23 +164,133 @@ tasks
91
164
  .command('disable <taskId>')
92
165
  .description('Disable a task')
93
166
  .action((taskId) => (0, tasks_1.taskEnableCommand)(taskId, false));
94
- // Help command
167
+ // Help with examples
95
168
  program.on('--help', () => {
96
169
  console.log('');
97
- console.log(chalk_1.default.bold('Examples:'));
98
- console.log(' $ d-drive config --key YOUR_API_KEY');
99
- console.log(' $ d-drive upload ./file.txt /backups/');
100
- console.log(' $ d-drive upload ./myproject /backups/projects/ -r');
101
- console.log(' $ d-drive download /backups/file.txt ./restored.txt');
102
- console.log(' $ d-drive list /backups');
103
- console.log(' $ d-drive delete /backups/old-file.txt');
170
+ console.log(chalk_1.default.bold('Quick Start:'));
171
+ console.log(chalk_1.default.gray('────────────────────────────────────────'));
172
+ console.log(` ${chalk_1.default.cyan('$')} drive config ${chalk_1.default.gray('# Interactive setup')}`);
173
+ console.log(` ${chalk_1.default.cyan('$')} drive config --key <KEY> ${chalk_1.default.gray('# Set API key')}`);
174
+ console.log('');
175
+ console.log(chalk_1.default.bold('File Operations:'));
176
+ console.log(chalk_1.default.gray('────────────────────────────────────────'));
177
+ console.log(` ${chalk_1.default.cyan('$')} drive upload ./file.txt`);
178
+ console.log(` ${chalk_1.default.cyan('$')} drive upload ./folder -r ${chalk_1.default.gray('# Recursive')}`);
179
+ console.log(` ${chalk_1.default.cyan('$')} drive download /file.txt`);
180
+ console.log(` ${chalk_1.default.cyan('$')} drive ls /backups`);
181
+ console.log(` ${chalk_1.default.cyan('$')} drive rm /old-file.txt`);
182
+ console.log('');
183
+ console.log(chalk_1.default.bold('Task Management:'));
184
+ console.log(chalk_1.default.gray('────────────────────────────────────────'));
185
+ console.log(` ${chalk_1.default.cyan('$')} drive tasks ls ${chalk_1.default.gray('# List tasks')}`);
186
+ console.log(` ${chalk_1.default.cyan('$')} drive tasks run <id> ${chalk_1.default.gray('# Run task')}`);
187
+ console.log(` ${chalk_1.default.cyan('$')} drive tasks stop <id> ${chalk_1.default.gray('# Stop task')}`);
104
188
  console.log('');
105
- console.log(chalk_1.default.bold('Task Commands:'));
106
- console.log(' $ d-drive tasks list List all backup tasks');
107
- console.log(' $ d-drive tasks run <taskId> Run a task immediately');
108
- console.log(' $ d-drive tasks stop <taskId> Stop a running task');
109
- console.log(' $ d-drive tasks enable <taskId> Enable a task');
110
- console.log(' $ d-drive tasks disable <taskId> Disable a task');
111
- console.log(' $ d-drive tasks delete <taskId> Delete a task');
189
+ console.log(chalk_1.default.gray('Documentation: https://github.com/jasonzli-DEV/D-Drive'));
112
190
  });
191
+ // Interactive mode function
192
+ async function interactiveMode() {
193
+ const config = (0, config_2.getConfig)();
194
+ if (!config.apiKey) {
195
+ console.log(chalk_1.default.yellow('No API key configured. Let\'s set one up!\n'));
196
+ await interactiveConfig();
197
+ return;
198
+ }
199
+ const choices = [
200
+ { name: '📂 List files', value: 'list' },
201
+ { name: '⬆️ Upload file', value: 'upload' },
202
+ { name: '⬇️ Download file', value: 'download' },
203
+ { name: '🗑️ Delete file', value: 'delete' },
204
+ { name: '📋 List tasks', value: 'tasks' },
205
+ { name: '⚙️ Configuration', value: 'config' },
206
+ { name: '❌ Exit', value: 'exit' },
207
+ ];
208
+ const { action } = await inquirer_1.default.prompt([
209
+ {
210
+ type: 'list',
211
+ name: 'action',
212
+ message: 'What would you like to do?',
213
+ choices,
214
+ },
215
+ ]);
216
+ switch (action) {
217
+ case 'list':
218
+ const { path } = await inquirer_1.default.prompt([
219
+ { type: 'input', name: 'path', message: 'Path (leave empty for root):', default: '/' },
220
+ ]);
221
+ await (0, list_1.listCommand)(path === '/' ? undefined : path, { long: true });
222
+ break;
223
+ case 'upload':
224
+ const { source, dest } = await inquirer_1.default.prompt([
225
+ { type: 'input', name: 'source', message: 'Local file path:' },
226
+ { type: 'input', name: 'dest', message: 'Remote destination (optional):' },
227
+ ]);
228
+ await (0, upload_1.uploadCommand)(source, dest || undefined, { recursive: false, progress: true });
229
+ break;
230
+ case 'download':
231
+ const { remotePath, localPath } = await inquirer_1.default.prompt([
232
+ { type: 'input', name: 'remotePath', message: 'Remote file path:' },
233
+ { type: 'input', name: 'localPath', message: 'Local destination (optional):' },
234
+ ]);
235
+ await (0, download_1.downloadCommand)(remotePath, localPath || undefined, { progress: true });
236
+ break;
237
+ case 'delete':
238
+ const { deletePath, confirm } = await inquirer_1.default.prompt([
239
+ { type: 'input', name: 'deletePath', message: 'Path to delete:' },
240
+ { type: 'confirm', name: 'confirm', message: 'Are you sure?', default: false },
241
+ ]);
242
+ if (confirm) {
243
+ await (0, delete_1.deleteCommand)(deletePath, { recursive: false, force: true });
244
+ }
245
+ break;
246
+ case 'tasks':
247
+ await (0, tasks_1.tasksListCommand)();
248
+ break;
249
+ case 'config':
250
+ await interactiveConfig();
251
+ break;
252
+ case 'exit':
253
+ console.log(chalk_1.default.gray('Goodbye! 👋'));
254
+ process.exit(0);
255
+ }
256
+ }
257
+ // Interactive configuration
258
+ async function interactiveConfig() {
259
+ const config = (0, config_2.getConfig)();
260
+ console.log(chalk_1.default.bold('\n📋 D-Drive Configuration\n'));
261
+ const answers = await inquirer_1.default.prompt([
262
+ {
263
+ type: 'input',
264
+ name: 'apiUrl',
265
+ message: 'API URL:',
266
+ default: config.apiUrl || 'https://your-server/api',
267
+ validate: (input) => {
268
+ if (!input.startsWith('http://') && !input.startsWith('https://')) {
269
+ return 'URL must start with http:// or https://';
270
+ }
271
+ return true;
272
+ },
273
+ },
274
+ {
275
+ type: 'input',
276
+ name: 'apiKey',
277
+ message: 'API Key (from Settings → API Keys):',
278
+ validate: (input) => {
279
+ if (!input || input.trim().length === 0) {
280
+ return 'API key is required';
281
+ }
282
+ return true;
283
+ },
284
+ },
285
+ ]);
286
+ await (0, config_1.configCommand)({
287
+ key: answers.apiKey,
288
+ url: answers.apiUrl,
289
+ });
290
+ }
291
+ // Handle no arguments - show help (but not for --version or --help)
292
+ if (process.argv.length === 2 && !process.argv.includes('-v') && !process.argv.includes('--version') && !process.argv.includes('-h') && !process.argv.includes('--help')) {
293
+ console.log(banner);
294
+ program.outputHelp();
295
+ }
113
296
  program.parse();
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "d-drive-cli",
3
- "version": "2.0.0",
4
- "description": "D-Drive CLI tool for developers (LTS)",
3
+ "version": "2.2.0",
4
+ "description": "D-Drive CLI tool for developers - Discord cloud storage",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "d-drive": "./dist/index.js"
7
+ "d-drive": "./dist/index.js",
8
+ "drive": "./dist/index.js"
8
9
  },
9
10
  "scripts": {
10
11
  "build": "tsc",
@@ -25,7 +25,7 @@ export async function configCommand(options: ConfigOptions) {
25
25
  try {
26
26
  // Normalize and test the API key by calling the /auth/me endpoint
27
27
  const config = getConfig();
28
- const apiUrl = options.url || config.apiUrl || 'http://localhost:5000/api';
28
+ const apiUrl = options.url || config.apiUrl || 'https://localhost/api';
29
29
 
30
30
  // Accept keys entered with or without the `dd_` prefix, and strip any accidental "Bearer " prefix
31
31
  const rawKey = options.key.replace(/^Bearer\s+/i, '').trim();
package/src/config.ts CHANGED
@@ -8,7 +8,7 @@ interface Config {
8
8
  const config = new Conf<Config>({
9
9
  projectName: 'd-drive',
10
10
  defaults: {
11
- apiUrl: 'http://localhost:5000/api',
11
+ apiUrl: 'https://localhost/api',
12
12
  },
13
13
  });
14
14
 
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
+ import inquirer from 'inquirer';
5
6
  import { configCommand } from './commands/config';
6
7
  import { uploadCommand } from './commands/upload';
7
8
  import { downloadCommand } from './commands/download';
@@ -9,35 +10,70 @@ import { listCommand } from './commands/list';
9
10
  import { deleteCommand } from './commands/delete';
10
11
  import { copyCommand } from './commands/copy';
11
12
  import { tasksListCommand, taskRunCommand, taskStopCommand, taskDeleteCommand, taskEnableCommand } from './commands/tasks';
13
+ import { getConfig } from './config';
12
14
  const pkg = require('../package.json');
13
15
 
14
16
  const program = new Command();
15
17
 
18
+ // ASCII art banner
19
+ const banner = `
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('║')}
23
+ ${chalk.cyan('╚═══════════════════════════════════════╝')}
24
+ `;
25
+
16
26
  program
17
27
  .name('d-drive')
18
- .description('D-Drive CLI - Discord-based cloud storage for developers')
19
- .version(pkg.version || '0.0.0');
28
+ .description('D-Drive CLI - Discord-based cloud storage')
29
+ .version(pkg.version || '2.2.0')
30
+ .hook('preAction', (thisCommand) => {
31
+ // Show banner only for main commands, not subcommands
32
+ if (thisCommand.name() === 'd-drive' && !process.argv.includes('--help') && !process.argv.includes('-h')) {
33
+ // Don't show banner for quick commands
34
+ }
35
+ });
36
+
37
+ // Interactive mode when no command is provided
38
+ program
39
+ .command('interactive', { isDefault: false })
40
+ .alias('i')
41
+ .description('Start interactive mode')
42
+ .action(async () => {
43
+ console.log(banner);
44
+ await interactiveMode();
45
+ });
20
46
 
21
- // Config command
47
+ // Config command with interactive setup
22
48
  program
23
49
  .command('config')
24
50
  .description('Configure D-Drive CLI')
25
51
  .option('-k, --key <apiKey>', 'Set API key')
26
52
  .option('-u, --url <url>', 'Set API URL')
27
53
  .option('-l, --list', 'List current configuration')
28
- .action(configCommand);
54
+ .option('-i, --interactive', 'Interactive configuration')
55
+ .action(async (options) => {
56
+ if (options.interactive || (!options.key && !options.url && !options.list)) {
57
+ await interactiveConfig();
58
+ } else {
59
+ await configCommand(options);
60
+ }
61
+ });
29
62
 
30
63
  // Upload command
31
64
  program
32
65
  .command('upload <source> [destination]')
66
+ .alias('up')
33
67
  .description('Upload a file or directory to D-Drive')
34
68
  .option('-r, --recursive', 'Upload directory recursively')
69
+ .option('-e, --encrypt', 'Encrypt the file')
35
70
  .option('--no-progress', 'Disable progress bar')
36
71
  .action(uploadCommand);
37
72
 
38
73
  // Download command
39
74
  program
40
75
  .command('download <source> [destination]')
76
+ .alias('dl')
41
77
  .description('Download a file from D-Drive')
42
78
  .option('--no-progress', 'Disable progress bar')
43
79
  .action(downloadCommand);
@@ -48,6 +84,7 @@ program
48
84
  .alias('ls')
49
85
  .description('List files in D-Drive')
50
86
  .option('-l, --long', 'Use long listing format')
87
+ .option('-a, --all', 'Include hidden files')
51
88
  .action(listCommand);
52
89
 
53
90
  // Delete command
@@ -62,13 +99,49 @@ program
62
99
  // Copy command
63
100
  program
64
101
  .command('copy <path>')
102
+ .alias('cp')
65
103
  .description('Make a copy of a file in-place (auto-numbered)')
66
104
  .action(copyCommand);
67
105
 
106
+ // Info command
107
+ program
108
+ .command('info')
109
+ .description('Show D-Drive connection info and status')
110
+ .action(async () => {
111
+ const config = getConfig();
112
+ console.log(banner);
113
+ console.log(chalk.bold('Connection Status:'));
114
+ console.log(chalk.gray('─────────────────────────────────'));
115
+ console.log(`${chalk.gray('API URL:')} ${config.apiUrl ? chalk.cyan(config.apiUrl) : chalk.red('Not configured')}`);
116
+ console.log(`${chalk.gray('API Key:')} ${config.apiKey ? chalk.green('✓ Configured') : chalk.red('✗ Not configured')}`);
117
+
118
+ if (config.apiKey && config.apiUrl) {
119
+ const axios = require('axios');
120
+ try {
121
+ const response = await axios.get(`${config.apiUrl}/auth/me`, {
122
+ headers: { Authorization: `Bearer ${config.apiKey}` },
123
+ timeout: 5000,
124
+ });
125
+ console.log(`${chalk.gray('Status:')} ${chalk.green('✓ Connected')}`);
126
+ console.log(`${chalk.gray('User:')} ${chalk.white(response.data.username || response.data.id)}`);
127
+ } catch (e: any) {
128
+ if (e.code === 'ECONNREFUSED') {
129
+ console.log(`${chalk.gray('Status:')} ${chalk.red('✗ Cannot connect to server')}`);
130
+ } else if (e.response?.status === 401) {
131
+ console.log(`${chalk.gray('Status:')} ${chalk.red('✗ Invalid API key')}`);
132
+ } else {
133
+ console.log(`${chalk.gray('Status:')} ${chalk.yellow('? Unable to verify')}`);
134
+ }
135
+ }
136
+ }
137
+ console.log(chalk.gray('─────────────────────────────────'));
138
+ console.log(chalk.gray(`Run ${chalk.white('drive config')} to configure`));
139
+ });
140
+
68
141
  // Tasks commands
69
142
  const tasks = program
70
143
  .command('tasks')
71
- .description('Manage backup tasks');
144
+ .description('Manage SFTP backup tasks');
72
145
 
73
146
  tasks
74
147
  .command('list')
@@ -103,24 +176,144 @@ tasks
103
176
  .description('Disable a task')
104
177
  .action((taskId: string) => taskEnableCommand(taskId, false));
105
178
 
106
- // Help command
179
+ // Help with examples
107
180
  program.on('--help', () => {
108
181
  console.log('');
109
- console.log(chalk.bold('Examples:'));
110
- console.log(' $ d-drive config --key YOUR_API_KEY');
111
- console.log(' $ d-drive upload ./file.txt /backups/');
112
- console.log(' $ d-drive upload ./myproject /backups/projects/ -r');
113
- console.log(' $ d-drive download /backups/file.txt ./restored.txt');
114
- console.log(' $ d-drive list /backups');
115
- console.log(' $ d-drive delete /backups/old-file.txt');
182
+ console.log(chalk.bold('Quick Start:'));
183
+ console.log(chalk.gray('────────────────────────────────────────'));
184
+ console.log(` ${chalk.cyan('$')} drive config ${chalk.gray('# Interactive setup')}`);
185
+ console.log(` ${chalk.cyan('$')} drive config --key <KEY> ${chalk.gray('# Set API key')}`);
186
+ console.log('');
187
+ console.log(chalk.bold('File Operations:'));
188
+ console.log(chalk.gray('────────────────────────────────────────'));
189
+ console.log(` ${chalk.cyan('$')} drive upload ./file.txt`);
190
+ console.log(` ${chalk.cyan('$')} drive upload ./folder -r ${chalk.gray('# Recursive')}`);
191
+ console.log(` ${chalk.cyan('$')} drive download /file.txt`);
192
+ console.log(` ${chalk.cyan('$')} drive ls /backups`);
193
+ console.log(` ${chalk.cyan('$')} drive rm /old-file.txt`);
194
+ console.log('');
195
+ console.log(chalk.bold('Task Management:'));
196
+ console.log(chalk.gray('────────────────────────────────────────'));
197
+ console.log(` ${chalk.cyan('$')} drive tasks ls ${chalk.gray('# List tasks')}`);
198
+ console.log(` ${chalk.cyan('$')} drive tasks run <id> ${chalk.gray('# Run task')}`);
199
+ console.log(` ${chalk.cyan('$')} drive tasks stop <id> ${chalk.gray('# Stop task')}`);
116
200
  console.log('');
117
- console.log(chalk.bold('Task Commands:'));
118
- console.log(' $ d-drive tasks list List all backup tasks');
119
- console.log(' $ d-drive tasks run <taskId> Run a task immediately');
120
- console.log(' $ d-drive tasks stop <taskId> Stop a running task');
121
- console.log(' $ d-drive tasks enable <taskId> Enable a task');
122
- console.log(' $ d-drive tasks disable <taskId> Disable a task');
123
- console.log(' $ d-drive tasks delete <taskId> Delete a task');
201
+ console.log(chalk.gray('Documentation: https://github.com/jasonzli-DEV/D-Drive'));
124
202
  });
125
203
 
204
+ // Interactive mode function
205
+ async function interactiveMode() {
206
+ const config = getConfig();
207
+
208
+ if (!config.apiKey) {
209
+ console.log(chalk.yellow('No API key configured. Let\'s set one up!\n'));
210
+ await interactiveConfig();
211
+ return;
212
+ }
213
+
214
+ const choices = [
215
+ { name: '📂 List files', value: 'list' },
216
+ { name: '⬆️ Upload file', value: 'upload' },
217
+ { name: '⬇️ Download file', value: 'download' },
218
+ { name: '🗑️ Delete file', value: 'delete' },
219
+ { name: '📋 List tasks', value: 'tasks' },
220
+ { name: '⚙️ Configuration', value: 'config' },
221
+ { name: '❌ Exit', value: 'exit' },
222
+ ];
223
+
224
+ const { action } = await inquirer.prompt([
225
+ {
226
+ type: 'list',
227
+ name: 'action',
228
+ message: 'What would you like to do?',
229
+ choices,
230
+ },
231
+ ]);
232
+
233
+ switch (action) {
234
+ case 'list':
235
+ const { path } = await inquirer.prompt([
236
+ { type: 'input', name: 'path', message: 'Path (leave empty for root):', default: '/' },
237
+ ]);
238
+ await listCommand(path === '/' ? undefined : path, { long: true });
239
+ break;
240
+ case 'upload':
241
+ const { source, dest } = await inquirer.prompt([
242
+ { type: 'input', name: 'source', message: 'Local file path:' },
243
+ { type: 'input', name: 'dest', message: 'Remote destination (optional):' },
244
+ ]);
245
+ await uploadCommand(source, dest || undefined, { recursive: false, progress: true });
246
+ break;
247
+ case 'download':
248
+ const { remotePath, localPath } = await inquirer.prompt([
249
+ { type: 'input', name: 'remotePath', message: 'Remote file path:' },
250
+ { type: 'input', name: 'localPath', message: 'Local destination (optional):' },
251
+ ]);
252
+ await downloadCommand(remotePath, localPath || undefined, { progress: true });
253
+ break;
254
+ case 'delete':
255
+ const { deletePath, confirm } = await inquirer.prompt([
256
+ { type: 'input', name: 'deletePath', message: 'Path to delete:' },
257
+ { type: 'confirm', name: 'confirm', message: 'Are you sure?', default: false },
258
+ ]);
259
+ if (confirm) {
260
+ await deleteCommand(deletePath, { recursive: false, force: true });
261
+ }
262
+ break;
263
+ case 'tasks':
264
+ await tasksListCommand();
265
+ break;
266
+ case 'config':
267
+ await interactiveConfig();
268
+ break;
269
+ case 'exit':
270
+ console.log(chalk.gray('Goodbye! 👋'));
271
+ process.exit(0);
272
+ }
273
+ }
274
+
275
+ // Interactive configuration
276
+ async function interactiveConfig() {
277
+ const config = getConfig();
278
+
279
+ console.log(chalk.bold('\n📋 D-Drive Configuration\n'));
280
+
281
+ const answers = await inquirer.prompt([
282
+ {
283
+ type: 'input',
284
+ name: 'apiUrl',
285
+ message: 'API URL:',
286
+ default: config.apiUrl || 'https://your-server/api',
287
+ validate: (input: string) => {
288
+ if (!input.startsWith('http://') && !input.startsWith('https://')) {
289
+ return 'URL must start with http:// or https://';
290
+ }
291
+ return true;
292
+ },
293
+ },
294
+ {
295
+ type: 'input',
296
+ name: 'apiKey',
297
+ message: 'API Key (from Settings → API Keys):',
298
+ validate: (input: string) => {
299
+ if (!input || input.trim().length === 0) {
300
+ return 'API key is required';
301
+ }
302
+ return true;
303
+ },
304
+ },
305
+ ]);
306
+
307
+ await configCommand({
308
+ key: answers.apiKey,
309
+ url: answers.apiUrl,
310
+ });
311
+ }
312
+
313
+ // Handle no arguments - show help (but not for --version or --help)
314
+ if (process.argv.length === 2 && !process.argv.includes('-v') && !process.argv.includes('--version') && !process.argv.includes('-h') && !process.argv.includes('--help')) {
315
+ console.log(banner);
316
+ program.outputHelp();
317
+ }
318
+
126
319
  program.parse();