d-drive-cli 1.0.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 ADDED
@@ -0,0 +1,159 @@
1
+ # D-Drive CLI
2
+
3
+ Command-line tool for D-Drive cloud storage.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g d-drive-cli
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ First, configure your API key:
14
+
15
+ ```bash
16
+ d-drive config --key YOUR_API_KEY
17
+ ```
18
+
19
+ You can get an API key from the D-Drive web interface at Settings → API Keys.
20
+
21
+ Optional: Set a custom API URL:
22
+
23
+ ```bash
24
+ d-drive config --url https://your-ddrive-instance.com/api
25
+ ```
26
+
27
+ View current configuration:
28
+
29
+ ```bash
30
+ d-drive config --list
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Upload Files
36
+
37
+ Upload a single file:
38
+
39
+ ```bash
40
+ d-drive upload ./myfile.txt /backups/
41
+ ```
42
+
43
+ Upload a directory recursively:
44
+
45
+ ```bash
46
+ d-drive upload ./myproject /backups/projects/ -r
47
+ ```
48
+
49
+ ### Download Files
50
+
51
+ Download a file:
52
+
53
+ ```bash
54
+ d-drive download /backups/myfile.txt ./restored.txt
55
+ ```
56
+
57
+ ### List Files
58
+
59
+ List files in root:
60
+
61
+ ```bash
62
+ d-drive list
63
+ # or
64
+ d-drive ls
65
+ ```
66
+
67
+ List files in a directory:
68
+
69
+ ```bash
70
+ d-drive list /backups
71
+ ```
72
+
73
+ Long format with details:
74
+
75
+ ```bash
76
+ d-drive list /backups -l
77
+ ```
78
+
79
+ ### Delete Files
80
+
81
+ Delete a file:
82
+
83
+ ```bash
84
+ d-drive delete /backups/old-file.txt
85
+ ```
86
+
87
+ Force delete without confirmation:
88
+
89
+ ```bash
90
+ d-drive delete /backups/old-file.txt -f
91
+ ```
92
+
93
+ ## Examples
94
+
95
+ ### Automated Backups
96
+
97
+ Create a backup script:
98
+
99
+ ```bash
100
+ #!/bin/bash
101
+ # backup.sh
102
+
103
+ # Backup database
104
+ pg_dump mydb > /tmp/backup.sql
105
+ d-drive upload /tmp/backup.sql /backups/database/backup-$(date +%Y%m%d).sql
106
+
107
+ # Backup config files
108
+ d-drive upload /etc/myapp /backups/config/ -r
109
+
110
+ # Cleanup
111
+ rm /tmp/backup.sql
112
+ ```
113
+
114
+ Add to crontab for daily backups:
115
+
116
+ ```bash
117
+ 0 2 * * * /path/to/backup.sh
118
+ ```
119
+
120
+ ### Continuous Integration
121
+
122
+ Upload build artifacts from CI/CD:
123
+
124
+ ```yaml
125
+ # .github/workflows/deploy.yml
126
+ - name: Upload build artifacts
127
+ run: |
128
+ npm run build
129
+ d-drive upload ./dist /releases/${{ github.sha }}/ -r
130
+ ```
131
+
132
+ ## Options
133
+
134
+ ### Global Options
135
+
136
+ - `--version` - Show version number
137
+ - `--help` - Show help
138
+
139
+ ### Upload Options
140
+
141
+ - `-r, --recursive` - Upload directory recursively
142
+ - `--no-progress` - Disable progress bar
143
+
144
+ ### Download Options
145
+
146
+ - `--no-progress` - Disable progress bar
147
+
148
+ ### List Options
149
+
150
+ - `-l, --long` - Use long listing format
151
+
152
+ ### Delete Options
153
+
154
+ - `-f, --force` - Force deletion without confirmation
155
+ - `-r, --recursive` - Delete directory recursively
156
+
157
+ ## License
158
+
159
+ MIT
package/dist/api.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { AxiosInstance } from 'axios';
2
+ export declare function createApiClient(): AxiosInstance;
package/dist/api.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createApiClient = createApiClient;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const config_1 = require("./config");
9
+ function createApiClient() {
10
+ const config = (0, config_1.getConfig)();
11
+ if (!config.apiKey) {
12
+ throw new Error('API key not configured. Run: d-drive config --key YOUR_API_KEY');
13
+ }
14
+ const client = axios_1.default.create({
15
+ baseURL: config.apiUrl,
16
+ headers: {
17
+ Authorization: `Bearer ${config.apiKey}`,
18
+ },
19
+ maxContentLength: Infinity,
20
+ maxBodyLength: Infinity,
21
+ });
22
+ return client;
23
+ }
@@ -0,0 +1,7 @@
1
+ interface ConfigOptions {
2
+ key?: string;
3
+ url?: string;
4
+ list?: boolean;
5
+ }
6
+ export declare function configCommand(options: ConfigOptions): void;
7
+ export {};
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.configCommand = configCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../config");
9
+ function configCommand(options) {
10
+ if (options.list) {
11
+ const config = (0, config_1.getAllConfig)();
12
+ console.log(chalk_1.default.bold('Current Configuration:'));
13
+ console.log(chalk_1.default.gray('────────────────────────────────'));
14
+ console.log(`API Key: ${config.apiKey ? chalk_1.default.green('✓ Set') : chalk_1.default.red('✗ Not set')}`);
15
+ console.log(`API URL: ${chalk_1.default.cyan(config.apiUrl || 'Not set')}`);
16
+ return;
17
+ }
18
+ if (options.key) {
19
+ (0, config_1.setConfig)('apiKey', options.key);
20
+ console.log(chalk_1.default.green('✓ API key configured successfully'));
21
+ }
22
+ if (options.url) {
23
+ (0, config_1.setConfig)('apiUrl', options.url);
24
+ console.log(chalk_1.default.green('✓ API URL configured successfully'));
25
+ }
26
+ if (!options.key && !options.url) {
27
+ console.log(chalk_1.default.yellow('No configuration options provided.'));
28
+ console.log('Use: d-drive config --key YOUR_API_KEY');
29
+ console.log(' d-drive config --url https://api.example.com');
30
+ console.log(' d-drive config --list');
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ interface DeleteOptions {
2
+ recursive?: boolean;
3
+ force?: boolean;
4
+ }
5
+ export declare function deleteCommand(remotePath: string, options: DeleteOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deleteCommand = deleteCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const api_1 = require("../api");
11
+ async function deleteCommand(remotePath, options) {
12
+ const spinner = (0, ora_1.default)('Finding file...').start();
13
+ try {
14
+ const api = (0, api_1.createApiClient)();
15
+ // Get file info
16
+ const filesResponse = await api.get('/files', {
17
+ params: { path: remotePath },
18
+ });
19
+ const files = filesResponse.data;
20
+ if (files.length === 0) {
21
+ spinner.fail(chalk_1.default.red(`File not found: ${remotePath}`));
22
+ return;
23
+ }
24
+ const file = files[0];
25
+ spinner.stop();
26
+ // Confirm deletion
27
+ if (!options.force) {
28
+ const answers = await inquirer_1.default.prompt([
29
+ {
30
+ type: 'confirm',
31
+ name: 'confirm',
32
+ message: `Are you sure you want to delete ${chalk_1.default.cyan(file.name)}?`,
33
+ default: false,
34
+ },
35
+ ]);
36
+ if (!answers.confirm) {
37
+ console.log(chalk_1.default.yellow('Deletion cancelled'));
38
+ return;
39
+ }
40
+ }
41
+ const deleteSpinner = (0, ora_1.default)('Deleting...').start();
42
+ await api.delete(`/files/${file.id}`);
43
+ deleteSpinner.succeed(chalk_1.default.green('File deleted successfully'));
44
+ }
45
+ catch (error) {
46
+ spinner.fail(chalk_1.default.red('Delete failed'));
47
+ console.error(chalk_1.default.red(error.response?.data?.error || error.message));
48
+ process.exit(1);
49
+ }
50
+ }
@@ -0,0 +1,5 @@
1
+ interface DownloadOptions {
2
+ progress?: boolean;
3
+ }
4
+ export declare function downloadCommand(source: string, destination: string | undefined, options: DownloadOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.downloadCommand = downloadCommand;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const progress_1 = __importDefault(require("progress"));
12
+ const api_1 = require("../api");
13
+ async function downloadCommand(source, destination = './', options) {
14
+ const spinner = (0, ora_1.default)('Finding file...').start();
15
+ try {
16
+ const api = (0, api_1.createApiClient)();
17
+ // First, get file info
18
+ const filesResponse = await api.get('/files', {
19
+ params: { path: source },
20
+ });
21
+ const files = filesResponse.data;
22
+ if (files.length === 0) {
23
+ spinner.fail(chalk_1.default.red(`File not found: ${source}`));
24
+ return;
25
+ }
26
+ const file = files[0];
27
+ if (file.type === 'DIRECTORY') {
28
+ spinner.fail(chalk_1.default.red('Cannot download directories yet'));
29
+ return;
30
+ }
31
+ spinner.text = 'Downloading...';
32
+ const destPath = path_1.default.resolve(destination);
33
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(destPath));
34
+ const response = await api.get(`/files/${file.id}/download`, {
35
+ responseType: 'stream',
36
+ });
37
+ const fileSize = parseInt(response.headers['content-length'] || '0');
38
+ const writer = fs_extra_1.default.createWriteStream(destPath);
39
+ let progressBar = null;
40
+ if (options.progress !== false && fileSize > 0) {
41
+ spinner.stop();
42
+ progressBar = new progress_1.default('[:bar] :percent :etas', {
43
+ complete: '█',
44
+ incomplete: '░',
45
+ width: 40,
46
+ total: fileSize,
47
+ });
48
+ }
49
+ let downloadedBytes = 0;
50
+ response.data.on('data', (chunk) => {
51
+ downloadedBytes += chunk.length;
52
+ if (progressBar) {
53
+ progressBar.update(downloadedBytes / fileSize);
54
+ }
55
+ });
56
+ response.data.pipe(writer);
57
+ await new Promise((resolve, reject) => {
58
+ writer.on('finish', () => resolve());
59
+ writer.on('error', reject);
60
+ });
61
+ if (progressBar) {
62
+ console.log(chalk_1.default.green('\n✓ File downloaded successfully'));
63
+ }
64
+ else {
65
+ spinner.succeed(chalk_1.default.green('File downloaded successfully'));
66
+ }
67
+ console.log(chalk_1.default.gray(`Saved to: ${destPath}`));
68
+ }
69
+ catch (error) {
70
+ spinner.fail(chalk_1.default.red('Download failed'));
71
+ console.error(chalk_1.default.red(error.response?.data?.error || error.message));
72
+ process.exit(1);
73
+ }
74
+ }
@@ -0,0 +1,5 @@
1
+ interface ListOptions {
2
+ long?: boolean;
3
+ }
4
+ export declare function listCommand(remotePath: string | undefined, options: ListOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listCommand = listCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const api_1 = require("../api");
10
+ async function listCommand(remotePath = '/', options) {
11
+ const spinner = (0, ora_1.default)('Fetching files...').start();
12
+ try {
13
+ const api = (0, api_1.createApiClient)();
14
+ const response = await api.get('/files', {
15
+ params: { path: remotePath },
16
+ });
17
+ const files = response.data;
18
+ spinner.stop();
19
+ if (files.length === 0) {
20
+ console.log(chalk_1.default.yellow('No files found'));
21
+ return;
22
+ }
23
+ console.log(chalk_1.default.bold(`\nFiles in ${remotePath}:`));
24
+ console.log(chalk_1.default.gray('────────────────────────────────────────────────────'));
25
+ if (options.long) {
26
+ // Long format
27
+ for (const file of files) {
28
+ const icon = file.type === 'DIRECTORY' ? '📁' : '📄';
29
+ const size = file.type === 'FILE' ? formatFileSize(Number(file.size)) : '-';
30
+ const date = new Date(file.updatedAt).toLocaleString();
31
+ console.log(`${icon} ${chalk_1.default.cyan(file.name.padEnd(30))} ${size.padEnd(10)} ${chalk_1.default.gray(date)}`);
32
+ }
33
+ }
34
+ else {
35
+ // Simple format
36
+ for (const file of files) {
37
+ const icon = file.type === 'DIRECTORY' ? '📁' : '📄';
38
+ console.log(`${icon} ${chalk_1.default.cyan(file.name)}`);
39
+ }
40
+ }
41
+ console.log(chalk_1.default.gray('────────────────────────────────────────────────────'));
42
+ console.log(chalk_1.default.gray(`Total: ${files.length} items`));
43
+ }
44
+ catch (error) {
45
+ spinner.fail(chalk_1.default.red('Failed to list files'));
46
+ console.error(chalk_1.default.red(error.response?.data?.error || error.message));
47
+ process.exit(1);
48
+ }
49
+ }
50
+ function formatFileSize(bytes) {
51
+ if (bytes === 0)
52
+ return '0 Bytes';
53
+ const k = 1024;
54
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
55
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
56
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
57
+ }
@@ -0,0 +1,6 @@
1
+ interface UploadOptions {
2
+ recursive?: boolean;
3
+ progress?: boolean;
4
+ }
5
+ export declare function uploadCommand(source: string, destination: string | undefined, options: UploadOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uploadCommand = uploadCommand;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const form_data_1 = __importDefault(require("form-data"));
12
+ const progress_1 = __importDefault(require("progress"));
13
+ const api_1 = require("../api");
14
+ const glob_1 = require("glob");
15
+ async function uploadCommand(source, destination = '/', options) {
16
+ const spinner = (0, ora_1.default)('Preparing upload...').start();
17
+ try {
18
+ const api = (0, api_1.createApiClient)();
19
+ const sourcePath = path_1.default.resolve(source);
20
+ // Check if source exists
21
+ if (!await fs_extra_1.default.pathExists(sourcePath)) {
22
+ spinner.fail(chalk_1.default.red(`Source not found: ${source}`));
23
+ return;
24
+ }
25
+ const stats = await fs_extra_1.default.stat(sourcePath);
26
+ if (stats.isDirectory()) {
27
+ if (!options.recursive) {
28
+ spinner.fail(chalk_1.default.red('Use -r flag to upload directories'));
29
+ return;
30
+ }
31
+ spinner.text = 'Finding files...';
32
+ const files = await (0, glob_1.glob)('**/*', {
33
+ cwd: sourcePath,
34
+ nodir: true,
35
+ });
36
+ spinner.succeed(chalk_1.default.green(`Found ${files.length} files to upload`));
37
+ for (const file of files) {
38
+ await uploadSingleFile(api, path_1.default.join(sourcePath, file), path_1.default.join(destination, file), options.progress !== false);
39
+ }
40
+ console.log(chalk_1.default.green(`\n✓ Uploaded ${files.length} files successfully`));
41
+ }
42
+ else {
43
+ spinner.stop();
44
+ const fileName = path_1.default.basename(sourcePath);
45
+ const destPath = destination.endsWith('/')
46
+ ? path_1.default.join(destination, fileName)
47
+ : destination;
48
+ await uploadSingleFile(api, sourcePath, destPath, options.progress !== false);
49
+ console.log(chalk_1.default.green('✓ File uploaded successfully'));
50
+ }
51
+ }
52
+ catch (error) {
53
+ spinner.fail(chalk_1.default.red('Upload failed'));
54
+ console.error(chalk_1.default.red(error.response?.data?.error || error.message));
55
+ process.exit(1);
56
+ }
57
+ }
58
+ async function uploadSingleFile(api, filePath, destination, showProgress) {
59
+ const fileName = path_1.default.basename(filePath);
60
+ const fileSize = (await fs_extra_1.default.stat(filePath)).size;
61
+ console.log(chalk_1.default.cyan(`\nUploading: ${fileName}`));
62
+ console.log(chalk_1.default.gray(`Size: ${formatFileSize(fileSize)}`));
63
+ const formData = new form_data_1.default();
64
+ formData.append('file', fs_extra_1.default.createReadStream(filePath));
65
+ formData.append('path', destination);
66
+ let progressBar = null;
67
+ if (showProgress) {
68
+ progressBar = new progress_1.default('[:bar] :percent :etas', {
69
+ complete: '█',
70
+ incomplete: '░',
71
+ width: 40,
72
+ total: fileSize,
73
+ });
74
+ }
75
+ await api.post('/files/upload', formData, {
76
+ headers: formData.getHeaders(),
77
+ onUploadProgress: (progressEvent) => {
78
+ if (progressBar && progressEvent.loaded) {
79
+ progressBar.update(progressEvent.loaded / fileSize);
80
+ }
81
+ },
82
+ });
83
+ }
84
+ function formatFileSize(bytes) {
85
+ if (bytes === 0)
86
+ return '0 Bytes';
87
+ const k = 1024;
88
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
89
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
90
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
91
+ }
@@ -0,0 +1,9 @@
1
+ interface Config {
2
+ apiKey?: string;
3
+ apiUrl?: string;
4
+ }
5
+ export declare function getConfig(): Config;
6
+ export declare function setConfig(key: keyof Config, value: string): void;
7
+ export declare function deleteConfig(key: keyof Config): void;
8
+ export declare function getAllConfig(): Config;
9
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getConfig = getConfig;
7
+ exports.setConfig = setConfig;
8
+ exports.deleteConfig = deleteConfig;
9
+ exports.getAllConfig = getAllConfig;
10
+ const conf_1 = __importDefault(require("conf"));
11
+ const config = new conf_1.default({
12
+ projectName: 'd-drive',
13
+ defaults: {
14
+ apiUrl: 'http://localhost:5000/api',
15
+ },
16
+ });
17
+ function getConfig() {
18
+ return {
19
+ apiKey: config.get('apiKey'),
20
+ apiUrl: config.get('apiUrl'),
21
+ };
22
+ }
23
+ function setConfig(key, value) {
24
+ config.set(key, value);
25
+ }
26
+ function deleteConfig(key) {
27
+ config.delete(key);
28
+ }
29
+ function getAllConfig() {
30
+ return config.store;
31
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const config_1 = require("./commands/config");
10
+ const upload_1 = require("./commands/upload");
11
+ const download_1 = require("./commands/download");
12
+ const list_1 = require("./commands/list");
13
+ const delete_1 = require("./commands/delete");
14
+ const program = new commander_1.Command();
15
+ program
16
+ .name('d-drive')
17
+ .description('D-Drive CLI - Discord-based cloud storage for developers')
18
+ .version('1.0.0');
19
+ // Config command
20
+ program
21
+ .command('config')
22
+ .description('Configure D-Drive CLI')
23
+ .option('-k, --key <apiKey>', 'Set API key')
24
+ .option('-u, --url <url>', 'Set API URL')
25
+ .option('-l, --list', 'List current configuration')
26
+ .action(config_1.configCommand);
27
+ // Upload command
28
+ program
29
+ .command('upload <source> [destination]')
30
+ .description('Upload a file or directory to D-Drive')
31
+ .option('-r, --recursive', 'Upload directory recursively')
32
+ .option('--no-progress', 'Disable progress bar')
33
+ .action(upload_1.uploadCommand);
34
+ // Download command
35
+ program
36
+ .command('download <source> [destination]')
37
+ .description('Download a file from D-Drive')
38
+ .option('--no-progress', 'Disable progress bar')
39
+ .action(download_1.downloadCommand);
40
+ // List command
41
+ program
42
+ .command('list [path]')
43
+ .alias('ls')
44
+ .description('List files in D-Drive')
45
+ .option('-l, --long', 'Use long listing format')
46
+ .action(list_1.listCommand);
47
+ // Delete command
48
+ program
49
+ .command('delete <path>')
50
+ .alias('rm')
51
+ .description('Delete a file or directory from D-Drive')
52
+ .option('-r, --recursive', 'Delete directory recursively')
53
+ .option('-f, --force', 'Force deletion without confirmation')
54
+ .action(delete_1.deleteCommand);
55
+ // Help command
56
+ program.on('--help', () => {
57
+ console.log('');
58
+ console.log(chalk_1.default.bold('Examples:'));
59
+ console.log(' $ d-drive config --key YOUR_API_KEY');
60
+ console.log(' $ d-drive upload ./file.txt /backups/');
61
+ console.log(' $ d-drive upload ./myproject /backups/projects/ -r');
62
+ console.log(' $ d-drive download /backups/file.txt ./restored.txt');
63
+ console.log(' $ d-drive list /backups');
64
+ console.log(' $ d-drive delete /backups/old-file.txt');
65
+ });
66
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "d-drive-cli",
3
+ "version": "1.0.0",
4
+ "description": "D-Drive CLI tool for developers",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "d-drive": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "d-drive",
16
+ "discord",
17
+ "backup",
18
+ "cli"
19
+ ],
20
+ "author": "jasonzli-DEV",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "commander": "^11.1.0",
24
+ "axios": "^1.6.5",
25
+ "chalk": "^4.1.2",
26
+ "ora": "^5.4.1",
27
+ "conf": "^10.2.0",
28
+ "inquirer": "^8.2.6",
29
+ "form-data": "^4.0.0",
30
+ "fs-extra": "^11.2.0",
31
+ "glob": "^10.3.10",
32
+ "progress": "^2.0.3"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.10.7",
36
+ "@types/inquirer": "^8.2.10",
37
+ "@types/fs-extra": "^11.0.4",
38
+ "@types/progress": "^2.0.7",
39
+ "typescript": "^5.3.3",
40
+ "ts-node": "^10.9.2"
41
+ }
42
+ }
package/src/api.ts ADDED
@@ -0,0 +1,21 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+ import { getConfig } from './config';
3
+
4
+ export function createApiClient(): AxiosInstance {
5
+ const config = getConfig();
6
+
7
+ if (!config.apiKey) {
8
+ throw new Error('API key not configured. Run: d-drive config --key YOUR_API_KEY');
9
+ }
10
+
11
+ const client = axios.create({
12
+ baseURL: config.apiUrl,
13
+ headers: {
14
+ Authorization: `Bearer ${config.apiKey}`,
15
+ },
16
+ maxContentLength: Infinity,
17
+ maxBodyLength: Infinity,
18
+ });
19
+
20
+ return client;
21
+ }
@@ -0,0 +1,36 @@
1
+ import chalk from 'chalk';
2
+ import { getConfig, setConfig, getAllConfig } from '../config';
3
+
4
+ interface ConfigOptions {
5
+ key?: string;
6
+ url?: string;
7
+ list?: boolean;
8
+ }
9
+
10
+ export function configCommand(options: ConfigOptions) {
11
+ if (options.list) {
12
+ const config = getAllConfig();
13
+ console.log(chalk.bold('Current Configuration:'));
14
+ console.log(chalk.gray('────────────────────────────────'));
15
+ console.log(`API Key: ${config.apiKey ? chalk.green('✓ Set') : chalk.red('✗ Not set')}`);
16
+ console.log(`API URL: ${chalk.cyan(config.apiUrl || 'Not set')}`);
17
+ return;
18
+ }
19
+
20
+ if (options.key) {
21
+ setConfig('apiKey', options.key);
22
+ console.log(chalk.green('✓ API key configured successfully'));
23
+ }
24
+
25
+ if (options.url) {
26
+ setConfig('apiUrl', options.url);
27
+ console.log(chalk.green('✓ API URL configured successfully'));
28
+ }
29
+
30
+ if (!options.key && !options.url) {
31
+ console.log(chalk.yellow('No configuration options provided.'));
32
+ console.log('Use: d-drive config --key YOUR_API_KEY');
33
+ console.log(' d-drive config --url https://api.example.com');
34
+ console.log(' d-drive config --list');
35
+ }
36
+ }
@@ -0,0 +1,58 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import inquirer from 'inquirer';
4
+ import { createApiClient } from '../api';
5
+
6
+ interface DeleteOptions {
7
+ recursive?: boolean;
8
+ force?: boolean;
9
+ }
10
+
11
+ export async function deleteCommand(remotePath: string, options: DeleteOptions) {
12
+ const spinner = ora('Finding file...').start();
13
+
14
+ try {
15
+ const api = createApiClient();
16
+
17
+ // Get file info
18
+ const filesResponse = await api.get('/files', {
19
+ params: { path: remotePath },
20
+ });
21
+
22
+ const files = filesResponse.data;
23
+ if (files.length === 0) {
24
+ spinner.fail(chalk.red(`File not found: ${remotePath}`));
25
+ return;
26
+ }
27
+
28
+ const file = files[0];
29
+ spinner.stop();
30
+
31
+ // Confirm deletion
32
+ if (!options.force) {
33
+ const answers = await inquirer.prompt([
34
+ {
35
+ type: 'confirm',
36
+ name: 'confirm',
37
+ message: `Are you sure you want to delete ${chalk.cyan(file.name)}?`,
38
+ default: false,
39
+ },
40
+ ]);
41
+
42
+ if (!answers.confirm) {
43
+ console.log(chalk.yellow('Deletion cancelled'));
44
+ return;
45
+ }
46
+ }
47
+
48
+ const deleteSpinner = ora('Deleting...').start();
49
+
50
+ await api.delete(`/files/${file.id}`);
51
+
52
+ deleteSpinner.succeed(chalk.green('File deleted successfully'));
53
+ } catch (error: any) {
54
+ spinner.fail(chalk.red('Delete failed'));
55
+ console.error(chalk.red(error.response?.data?.error || error.message));
56
+ process.exit(1);
57
+ }
58
+ }
@@ -0,0 +1,92 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import ProgressBar from 'progress';
6
+ import { createApiClient } from '../api';
7
+
8
+ interface DownloadOptions {
9
+ progress?: boolean;
10
+ }
11
+
12
+ export async function downloadCommand(
13
+ source: string,
14
+ destination: string = './',
15
+ options: DownloadOptions
16
+ ) {
17
+ const spinner = ora('Finding file...').start();
18
+
19
+ try {
20
+ const api = createApiClient();
21
+
22
+ // First, get file info
23
+ const filesResponse = await api.get('/files', {
24
+ params: { path: source },
25
+ });
26
+
27
+ const files = filesResponse.data;
28
+ if (files.length === 0) {
29
+ spinner.fail(chalk.red(`File not found: ${source}`));
30
+ return;
31
+ }
32
+
33
+ const file = files[0];
34
+
35
+ if (file.type === 'DIRECTORY') {
36
+ spinner.fail(chalk.red('Cannot download directories yet'));
37
+ return;
38
+ }
39
+
40
+ spinner.text = 'Downloading...';
41
+
42
+ const destPath = path.resolve(destination);
43
+ await fs.ensureDir(path.dirname(destPath));
44
+
45
+ const response = await api.get(`/files/${file.id}/download`, {
46
+ responseType: 'stream',
47
+ });
48
+
49
+ const fileSize = parseInt(response.headers['content-length'] || '0');
50
+ const writer = fs.createWriteStream(destPath);
51
+
52
+ let progressBar: ProgressBar | null = null;
53
+
54
+ if (options.progress !== false && fileSize > 0) {
55
+ spinner.stop();
56
+ progressBar = new ProgressBar('[:bar] :percent :etas', {
57
+ complete: '█',
58
+ incomplete: '░',
59
+ width: 40,
60
+ total: fileSize,
61
+ });
62
+ }
63
+
64
+ let downloadedBytes = 0;
65
+
66
+ response.data.on('data', (chunk: Buffer) => {
67
+ downloadedBytes += chunk.length;
68
+ if (progressBar) {
69
+ progressBar.update(downloadedBytes / fileSize);
70
+ }
71
+ });
72
+
73
+ response.data.pipe(writer);
74
+
75
+ await new Promise<void>((resolve, reject) => {
76
+ writer.on('finish', () => resolve());
77
+ writer.on('error', reject);
78
+ });
79
+
80
+ if (progressBar) {
81
+ console.log(chalk.green('\n✓ File downloaded successfully'));
82
+ } else {
83
+ spinner.succeed(chalk.green('File downloaded successfully'));
84
+ }
85
+
86
+ console.log(chalk.gray(`Saved to: ${destPath}`));
87
+ } catch (error: any) {
88
+ spinner.fail(chalk.red('Download failed'));
89
+ console.error(chalk.red(error.response?.data?.error || error.message));
90
+ process.exit(1);
91
+ }
92
+ }
@@ -0,0 +1,64 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createApiClient } from '../api';
4
+
5
+ interface ListOptions {
6
+ long?: boolean;
7
+ }
8
+
9
+ export async function listCommand(remotePath: string = '/', options: ListOptions) {
10
+ const spinner = ora('Fetching files...').start();
11
+
12
+ try {
13
+ const api = createApiClient();
14
+
15
+ const response = await api.get('/files', {
16
+ params: { path: remotePath },
17
+ });
18
+
19
+ const files = response.data;
20
+ spinner.stop();
21
+
22
+ if (files.length === 0) {
23
+ console.log(chalk.yellow('No files found'));
24
+ return;
25
+ }
26
+
27
+ console.log(chalk.bold(`\nFiles in ${remotePath}:`));
28
+ console.log(chalk.gray('────────────────────────────────────────────────────'));
29
+
30
+ if (options.long) {
31
+ // Long format
32
+ for (const file of files) {
33
+ const icon = file.type === 'DIRECTORY' ? '📁' : '📄';
34
+ const size = file.type === 'FILE' ? formatFileSize(Number(file.size)) : '-';
35
+ const date = new Date(file.updatedAt).toLocaleString();
36
+
37
+ console.log(
38
+ `${icon} ${chalk.cyan(file.name.padEnd(30))} ${size.padEnd(10)} ${chalk.gray(date)}`
39
+ );
40
+ }
41
+ } else {
42
+ // Simple format
43
+ for (const file of files) {
44
+ const icon = file.type === 'DIRECTORY' ? '📁' : '📄';
45
+ console.log(`${icon} ${chalk.cyan(file.name)}`);
46
+ }
47
+ }
48
+
49
+ console.log(chalk.gray('────────────────────────────────────────────────────'));
50
+ console.log(chalk.gray(`Total: ${files.length} items`));
51
+ } catch (error: any) {
52
+ spinner.fail(chalk.red('Failed to list files'));
53
+ console.error(chalk.red(error.response?.data?.error || error.message));
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ function formatFileSize(bytes: number): string {
59
+ if (bytes === 0) return '0 Bytes';
60
+ const k = 1024;
61
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
62
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
63
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
64
+ }
@@ -0,0 +1,118 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import FormData from 'form-data';
6
+ import ProgressBar from 'progress';
7
+ import { createApiClient } from '../api';
8
+ import { glob } from 'glob';
9
+
10
+ interface UploadOptions {
11
+ recursive?: boolean;
12
+ progress?: boolean;
13
+ }
14
+
15
+ export async function uploadCommand(
16
+ source: string,
17
+ destination: string = '/',
18
+ options: UploadOptions
19
+ ) {
20
+ const spinner = ora('Preparing upload...').start();
21
+
22
+ try {
23
+ const api = createApiClient();
24
+ const sourcePath = path.resolve(source);
25
+
26
+ // Check if source exists
27
+ if (!await fs.pathExists(sourcePath)) {
28
+ spinner.fail(chalk.red(`Source not found: ${source}`));
29
+ return;
30
+ }
31
+
32
+ const stats = await fs.stat(sourcePath);
33
+
34
+ if (stats.isDirectory()) {
35
+ if (!options.recursive) {
36
+ spinner.fail(chalk.red('Use -r flag to upload directories'));
37
+ return;
38
+ }
39
+
40
+ spinner.text = 'Finding files...';
41
+ const files = await glob('**/*', {
42
+ cwd: sourcePath,
43
+ nodir: true,
44
+ });
45
+
46
+ spinner.succeed(chalk.green(`Found ${files.length} files to upload`));
47
+
48
+ for (const file of files) {
49
+ await uploadSingleFile(
50
+ api,
51
+ path.join(sourcePath, file),
52
+ path.join(destination, file),
53
+ options.progress !== false
54
+ );
55
+ }
56
+
57
+ console.log(chalk.green(`\n✓ Uploaded ${files.length} files successfully`));
58
+ } else {
59
+ spinner.stop();
60
+ const fileName = path.basename(sourcePath);
61
+ const destPath = destination.endsWith('/')
62
+ ? path.join(destination, fileName)
63
+ : destination;
64
+
65
+ await uploadSingleFile(api, sourcePath, destPath, options.progress !== false);
66
+ console.log(chalk.green('✓ File uploaded successfully'));
67
+ }
68
+ } catch (error: any) {
69
+ spinner.fail(chalk.red('Upload failed'));
70
+ console.error(chalk.red(error.response?.data?.error || error.message));
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ async function uploadSingleFile(
76
+ api: any,
77
+ filePath: string,
78
+ destination: string,
79
+ showProgress: boolean
80
+ ): Promise<void> {
81
+ const fileName = path.basename(filePath);
82
+ const fileSize = (await fs.stat(filePath)).size;
83
+
84
+ console.log(chalk.cyan(`\nUploading: ${fileName}`));
85
+ console.log(chalk.gray(`Size: ${formatFileSize(fileSize)}`));
86
+
87
+ const formData = new FormData();
88
+ formData.append('file', fs.createReadStream(filePath));
89
+ formData.append('path', destination);
90
+
91
+ let progressBar: ProgressBar | null = null;
92
+
93
+ if (showProgress) {
94
+ progressBar = new ProgressBar('[:bar] :percent :etas', {
95
+ complete: '█',
96
+ incomplete: '░',
97
+ width: 40,
98
+ total: fileSize,
99
+ });
100
+ }
101
+
102
+ await api.post('/files/upload', formData, {
103
+ headers: formData.getHeaders(),
104
+ onUploadProgress: (progressEvent: any) => {
105
+ if (progressBar && progressEvent.loaded) {
106
+ progressBar.update(progressEvent.loaded / fileSize);
107
+ }
108
+ },
109
+ });
110
+ }
111
+
112
+ function formatFileSize(bytes: number): string {
113
+ if (bytes === 0) return '0 Bytes';
114
+ const k = 1024;
115
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
116
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
117
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
118
+ }
package/src/config.ts ADDED
@@ -0,0 +1,32 @@
1
+ import Conf from 'conf';
2
+
3
+ interface Config {
4
+ apiKey?: string;
5
+ apiUrl?: string;
6
+ }
7
+
8
+ const config = new Conf<Config>({
9
+ projectName: 'd-drive',
10
+ defaults: {
11
+ apiUrl: 'http://localhost:5000/api',
12
+ },
13
+ });
14
+
15
+ export function getConfig(): Config {
16
+ return {
17
+ apiKey: config.get('apiKey'),
18
+ apiUrl: config.get('apiUrl'),
19
+ };
20
+ }
21
+
22
+ export function setConfig(key: keyof Config, value: string): void {
23
+ config.set(key, value);
24
+ }
25
+
26
+ export function deleteConfig(key: keyof Config): void {
27
+ config.delete(key);
28
+ }
29
+
30
+ export function getAllConfig(): Config {
31
+ return config.store;
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { configCommand } from './commands/config';
6
+ import { uploadCommand } from './commands/upload';
7
+ import { downloadCommand } from './commands/download';
8
+ import { listCommand } from './commands/list';
9
+ import { deleteCommand } from './commands/delete';
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('d-drive')
15
+ .description('D-Drive CLI - Discord-based cloud storage for developers')
16
+ .version('1.0.0');
17
+
18
+ // Config command
19
+ program
20
+ .command('config')
21
+ .description('Configure D-Drive CLI')
22
+ .option('-k, --key <apiKey>', 'Set API key')
23
+ .option('-u, --url <url>', 'Set API URL')
24
+ .option('-l, --list', 'List current configuration')
25
+ .action(configCommand);
26
+
27
+ // Upload command
28
+ program
29
+ .command('upload <source> [destination]')
30
+ .description('Upload a file or directory to D-Drive')
31
+ .option('-r, --recursive', 'Upload directory recursively')
32
+ .option('--no-progress', 'Disable progress bar')
33
+ .action(uploadCommand);
34
+
35
+ // Download command
36
+ program
37
+ .command('download <source> [destination]')
38
+ .description('Download a file from D-Drive')
39
+ .option('--no-progress', 'Disable progress bar')
40
+ .action(downloadCommand);
41
+
42
+ // List command
43
+ program
44
+ .command('list [path]')
45
+ .alias('ls')
46
+ .description('List files in D-Drive')
47
+ .option('-l, --long', 'Use long listing format')
48
+ .action(listCommand);
49
+
50
+ // Delete command
51
+ program
52
+ .command('delete <path>')
53
+ .alias('rm')
54
+ .description('Delete a file or directory from D-Drive')
55
+ .option('-r, --recursive', 'Delete directory recursively')
56
+ .option('-f, --force', 'Force deletion without confirmation')
57
+ .action(deleteCommand);
58
+
59
+ // Help command
60
+ program.on('--help', () => {
61
+ console.log('');
62
+ console.log(chalk.bold('Examples:'));
63
+ console.log(' $ d-drive config --key YOUR_API_KEY');
64
+ console.log(' $ d-drive upload ./file.txt /backups/');
65
+ console.log(' $ d-drive upload ./myproject /backups/projects/ -r');
66
+ console.log(' $ d-drive download /backups/file.txt ./restored.txt');
67
+ console.log(' $ d-drive list /backups');
68
+ console.log(' $ d-drive delete /backups/old-file.txt');
69
+ });
70
+
71
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "moduleResolution": "node",
14
+ "declaration": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }