d-drive-cli 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -130
- package/dist/commands/config.js +1 -1
- package/dist/config.js +1 -1
- package/dist/index.js +203 -20
- package/package.json +2 -2
- package/src/commands/config.ts +1 -1
- package/src/config.ts +1 -1
- package/src/index.ts +213 -20
package/README.md
CHANGED
|
@@ -1,225 +1,273 @@
|
|
|
1
|
-
# D-Drive CLI
|
|
1
|
+
# D-Drive CLI v2.2.1
|
|
2
2
|
|
|
3
3
|
Command-line tool for D-Drive cloud storage.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```
|
|
6
|
+
╔═══════════════════════════════════════╗
|
|
7
|
+
║ D-Drive CLI v2.2.1 ║
|
|
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
|
-
|
|
22
|
+
After installation, use the `d-drive` command:
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
## Quick Start
|
|
16
25
|
|
|
17
26
|
```bash
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
# Interactive configuration (recommended)
|
|
28
|
+
d-drive config
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
# Or set API key directly
|
|
31
|
+
d-drive config --key YOUR_API_KEY --url https://your-server/api
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
# Check connection
|
|
34
|
+
d-drive info
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
d-drive
|
|
27
|
-
```
|
|
36
|
+
# Upload a file
|
|
37
|
+
d-drive upload ./backup.zip
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
# List files
|
|
40
|
+
d-drive ls
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
d-drive
|
|
42
|
+
# Download a file
|
|
43
|
+
d-drive download /backup.zip ./local-backup.zip
|
|
33
44
|
```
|
|
34
45
|
|
|
35
|
-
##
|
|
36
|
-
|
|
37
|
-
### Upload Files
|
|
46
|
+
## Commands
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
### Configuration
|
|
40
49
|
|
|
41
50
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
# Interactive setup
|
|
52
|
+
d-drive config
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
# Set API key
|
|
55
|
+
d-drive config --key dd_your_api_key
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
# Set API URL
|
|
58
|
+
d-drive config --url https://your-server/api
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
d-drive
|
|
60
|
+
# View current config
|
|
61
|
+
d-drive config --list
|
|
62
|
+
# or
|
|
63
|
+
d-drive config -l
|
|
51
64
|
```
|
|
52
65
|
|
|
53
|
-
###
|
|
66
|
+
### File Operations
|
|
54
67
|
|
|
55
|
-
|
|
68
|
+
#### Upload
|
|
56
69
|
|
|
57
70
|
```bash
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
# Upload single file
|
|
72
|
+
d-drive upload ./file.txt
|
|
60
73
|
|
|
61
|
-
|
|
74
|
+
# Upload to specific folder
|
|
75
|
+
d-drive upload ./file.txt /backups/
|
|
62
76
|
|
|
63
|
-
|
|
77
|
+
# Upload directory recursively
|
|
78
|
+
d-drive upload ./myproject /backups/ -r
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
d-drive
|
|
67
|
-
# or
|
|
68
|
-
d-drive ls
|
|
80
|
+
# Upload with encryption
|
|
81
|
+
d-drive upload ./sensitive.txt -e
|
|
69
82
|
```
|
|
70
83
|
|
|
71
|
-
|
|
84
|
+
#### Download
|
|
72
85
|
|
|
73
86
|
```bash
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
# Download file
|
|
88
|
+
d-drive download /backups/file.txt
|
|
76
89
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
d-drive list /backups -l
|
|
90
|
+
# Download to specific location
|
|
91
|
+
d-drive download /backups/file.txt ./local-file.txt
|
|
81
92
|
```
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Delete a file:
|
|
94
|
+
#### List
|
|
86
95
|
|
|
87
96
|
```bash
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
# List root directory
|
|
98
|
+
d-drive ls
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
# List specific directory
|
|
101
|
+
d-drive ls /backups
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
d-drive
|
|
103
|
+
# Long format with details
|
|
104
|
+
d-drive ls -l
|
|
105
|
+
d-drive ls /backups -l
|
|
95
106
|
```
|
|
96
107
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
### Automated Backups
|
|
100
|
-
|
|
101
|
-
Create a backup script:
|
|
108
|
+
#### Delete
|
|
102
109
|
|
|
103
110
|
```bash
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
# Delete file (with confirmation)
|
|
112
|
+
d-drive rm /old-file.txt
|
|
106
113
|
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
d-drive upload /tmp/backup.sql /backups/database/backup-$(date +%Y%m%d).sql
|
|
110
|
-
|
|
111
|
-
# Backup config files
|
|
112
|
-
d-drive upload /etc/myapp /backups/config/ -r
|
|
114
|
+
# Force delete without confirmation
|
|
115
|
+
d-drive rm /old-file.txt -f
|
|
113
116
|
|
|
114
|
-
#
|
|
115
|
-
rm /
|
|
117
|
+
# Delete directory recursively
|
|
118
|
+
d-drive rm /old-folder -r
|
|
116
119
|
```
|
|
117
120
|
|
|
118
|
-
|
|
121
|
+
#### Copy
|
|
119
122
|
|
|
120
123
|
```bash
|
|
121
|
-
|
|
124
|
+
# Create a copy of a file
|
|
125
|
+
d-drive cp /backups/file.txt
|
|
126
|
+
# Creates: /backups/file (1).txt
|
|
122
127
|
```
|
|
123
128
|
|
|
124
|
-
###
|
|
129
|
+
### Task Management
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
```
|
|
131
|
+
D-Drive supports SFTP backup tasks that can be managed via CLI.
|
|
135
132
|
|
|
136
|
-
|
|
133
|
+
```bash
|
|
134
|
+
# List all tasks
|
|
135
|
+
d-drive tasks ls
|
|
137
136
|
|
|
138
|
-
|
|
137
|
+
# Run a task immediately
|
|
138
|
+
d-drive tasks run <task-id>
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
-
|
|
140
|
+
# Stop a running task
|
|
141
|
+
d-drive tasks stop <task-id>
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
# Enable/disable a task
|
|
144
|
+
d-drive tasks enable <task-id>
|
|
145
|
+
d-drive tasks disable <task-id>
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
+
# Delete a task
|
|
148
|
+
d-drive tasks rm <task-id>
|
|
149
|
+
d-drive tasks rm <task-id> -f # Force delete
|
|
150
|
+
```
|
|
147
151
|
|
|
148
|
-
###
|
|
152
|
+
### Info & Status
|
|
149
153
|
|
|
150
|
-
|
|
154
|
+
```bash
|
|
155
|
+
# Show connection status and user info
|
|
156
|
+
d-drive info
|
|
157
|
+
```
|
|
151
158
|
|
|
152
|
-
|
|
159
|
+
Output:
|
|
160
|
+
```
|
|
161
|
+
Connection Status:
|
|
162
|
+
─────────────────────────────────
|
|
163
|
+
API URL: https://your-server/api
|
|
164
|
+
API Key: ✓ Configured
|
|
165
|
+
Status: ✓ Connected
|
|
166
|
+
User: YourUsername
|
|
167
|
+
─────────────────────────────────
|
|
168
|
+
```
|
|
153
169
|
|
|
154
|
-
|
|
170
|
+
### Interactive Mode
|
|
155
171
|
|
|
156
|
-
|
|
172
|
+
```bash
|
|
173
|
+
# Start interactive mode
|
|
174
|
+
d-drive interactive
|
|
175
|
+
# or
|
|
176
|
+
d-drive i
|
|
177
|
+
```
|
|
157
178
|
|
|
158
|
-
|
|
159
|
-
- `-r, --recursive` - Delete directory recursively
|
|
179
|
+
Interactive mode provides a menu-driven interface for all operations.
|
|
160
180
|
|
|
161
|
-
|
|
181
|
+
## Getting Your API Key
|
|
162
182
|
|
|
163
|
-
|
|
183
|
+
1. Open D-Drive web interface
|
|
184
|
+
2. Go to **Settings** (gear icon)
|
|
185
|
+
3. Scroll to **API Keys** section
|
|
186
|
+
4. Click **Create API Key**
|
|
187
|
+
5. Copy the key (starts with `dd_`)
|
|
188
|
+
6. Use in CLI: `d-drive config --key dd_your_key`
|
|
164
189
|
|
|
165
|
-
|
|
190
|
+
## Examples
|
|
166
191
|
|
|
167
|
-
###
|
|
192
|
+
### Backup a Project
|
|
168
193
|
|
|
169
194
|
```bash
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
d-drive tasks ls
|
|
173
|
-
```
|
|
195
|
+
# Create a backup of your project
|
|
196
|
+
d-drive upload ./my-project /backups/my-project/ -r
|
|
174
197
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
d-drive tasks run <taskId>
|
|
198
|
+
# List backups
|
|
199
|
+
d-drive ls /backups/my-project -l
|
|
179
200
|
```
|
|
180
201
|
|
|
181
|
-
###
|
|
202
|
+
### Automated Backup Script
|
|
182
203
|
|
|
183
204
|
```bash
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
### Enable/Disable a Task
|
|
205
|
+
#!/bin/bash
|
|
206
|
+
# backup.sh
|
|
188
207
|
|
|
189
|
-
|
|
190
|
-
d-drive
|
|
191
|
-
|
|
208
|
+
DATE=$(date +%Y-%m-%d)
|
|
209
|
+
d-drive upload ./data "/backups/$DATE/"
|
|
210
|
+
echo "Backup completed: $DATE"
|
|
192
211
|
```
|
|
193
212
|
|
|
194
|
-
###
|
|
213
|
+
### Download and Restore
|
|
195
214
|
|
|
196
215
|
```bash
|
|
197
|
-
|
|
216
|
+
# Download latest backup
|
|
217
|
+
d-drive download /backups/2026-01-24/data.tar.gz ./restore/
|
|
198
218
|
|
|
199
|
-
#
|
|
200
|
-
|
|
219
|
+
# Extract
|
|
220
|
+
tar -xzf ./restore/data.tar.gz
|
|
201
221
|
```
|
|
202
222
|
|
|
203
|
-
|
|
223
|
+
## Environment Variables
|
|
204
224
|
|
|
205
|
-
|
|
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
|
|
225
|
+
You can also configure the CLI using environment variables:
|
|
217
226
|
|
|
218
|
-
|
|
219
|
-
|
|
227
|
+
```bash
|
|
228
|
+
export DDRIVE_API_KEY=dd_your_api_key
|
|
229
|
+
export DDRIVE_API_URL=https://your-server/api
|
|
220
230
|
```
|
|
221
231
|
|
|
222
|
-
|
|
232
|
+
## Troubleshooting
|
|
233
|
+
|
|
234
|
+
### "Cannot connect to server"
|
|
235
|
+
- Check your API URL is correct
|
|
236
|
+
- Ensure the server is running
|
|
237
|
+
- Verify network connectivity
|
|
238
|
+
|
|
239
|
+
### "Invalid API key"
|
|
240
|
+
- Generate a new API key in Settings
|
|
241
|
+
- Ensure the key starts with `dd_`
|
|
242
|
+
- Check for extra spaces or characters
|
|
243
|
+
|
|
244
|
+
### "Permission denied"
|
|
245
|
+
- Verify you're logged in with the correct account
|
|
246
|
+
- Check file/folder permissions in D-Drive
|
|
247
|
+
|
|
248
|
+
## Changelog
|
|
249
|
+
|
|
250
|
+
### v2.2.1
|
|
251
|
+
- Fixed double output issue by removing `drive` alias
|
|
252
|
+
- Use `d-drive` as the single command name
|
|
253
|
+
- Improved consistency across documentation
|
|
254
|
+
|
|
255
|
+
### v2.2.0
|
|
256
|
+
- Interactive mode with menu-driven interface
|
|
257
|
+
- `info` command for connection status
|
|
258
|
+
- Interactive configuration wizard
|
|
259
|
+
- Better error messages
|
|
260
|
+
- Colorized output
|
|
261
|
+
- Added `cp` alias for copy command
|
|
262
|
+
- Added `up` and `dl` aliases
|
|
263
|
+
|
|
264
|
+
### v2.1.0
|
|
265
|
+
- Task management commands
|
|
266
|
+
- Encryption support
|
|
267
|
+
- Progress bars
|
|
268
|
+
|
|
269
|
+
### v2.0.0
|
|
270
|
+
- Initial release with upload/download/list/delete
|
|
223
271
|
|
|
224
272
|
## License
|
|
225
273
|
|
package/dist/commands/config.js
CHANGED
|
@@ -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 || '
|
|
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
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
|
|
21
|
-
.version(pkg.version || '
|
|
22
|
-
|
|
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
|
-
.
|
|
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
|
|
167
|
+
// Help with examples
|
|
95
168
|
program.on('--help', () => {
|
|
96
169
|
console.log('');
|
|
97
|
-
console.log(chalk_1.default.bold('
|
|
98
|
-
console.log('
|
|
99
|
-
console.log(
|
|
100
|
-
console.log(
|
|
101
|
-
console.log('
|
|
102
|
-
console.log('
|
|
103
|
-
console.log(
|
|
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.
|
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "d-drive-cli",
|
|
3
|
-
"version": "2.1
|
|
4
|
-
"description": "D-Drive CLI tool for developers
|
|
3
|
+
"version": "2.2.1",
|
|
4
|
+
"description": "D-Drive CLI tool for developers - Discord cloud storage",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"d-drive": "./dist/index.js"
|
package/src/commands/config.ts
CHANGED
|
@@ -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 || '
|
|
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
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
|
|
19
|
-
.version(pkg.version || '
|
|
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
|
-
.
|
|
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
|
|
179
|
+
// Help with examples
|
|
107
180
|
program.on('--help', () => {
|
|
108
181
|
console.log('');
|
|
109
|
-
console.log(chalk.bold('
|
|
110
|
-
console.log('
|
|
111
|
-
console.log(
|
|
112
|
-
console.log(
|
|
113
|
-
console.log('
|
|
114
|
-
console.log('
|
|
115
|
-
console.log(
|
|
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.
|
|
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();
|