d-drive-cli 1.3.0 → 2.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 +65 -0
- package/dist/commands/copy.d.ts +1 -0
- package/dist/commands/copy.js +33 -0
- package/dist/commands/tasks.d.ts +7 -0
- package/dist/commands/tasks.js +167 -0
- package/dist/index.js +48 -1
- package/package.json +2 -2
- package/src/commands/copy.ts +32 -0
- package/src/commands/tasks.ts +162 -0
- package/src/index.ts +56 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Command-line tool for D-Drive cloud storage.
|
|
4
4
|
|
|
5
|
+
**Version: 2.0.0 LTS**
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -156,6 +158,69 @@ Upload build artifacts from CI/CD:
|
|
|
156
158
|
- `-f, --force` - Force deletion without confirmation
|
|
157
159
|
- `-r, --recursive` - Delete directory recursively
|
|
158
160
|
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Task Management
|
|
164
|
+
|
|
165
|
+
D-Drive supports automated SFTP backup tasks. Use the CLI to manage them:
|
|
166
|
+
|
|
167
|
+
### List Tasks
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
d-drive tasks list
|
|
171
|
+
# or
|
|
172
|
+
d-drive tasks ls
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Run a Task Immediately
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
d-drive tasks run <taskId>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Stop a Running Task
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
d-drive tasks stop <taskId>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Enable/Disable a Task
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
d-drive tasks enable <taskId>
|
|
191
|
+
d-drive tasks disable <taskId>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Delete a Task
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
d-drive tasks delete <taskId>
|
|
198
|
+
|
|
199
|
+
# Force delete without confirmation
|
|
200
|
+
d-drive tasks delete <taskId> -f
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Task Examples
|
|
204
|
+
|
|
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
|
|
217
|
+
|
|
218
|
+
# Stop a running task
|
|
219
|
+
$ d-drive tasks stop abc123
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
159
224
|
## License
|
|
160
225
|
|
|
161
226
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function copyCommand(remotePath: string): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
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.copyCommand = copyCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const api_1 = require("../api");
|
|
10
|
+
async function copyCommand(remotePath) {
|
|
11
|
+
const spinner = (0, ora_1.default)('Finding file...').start();
|
|
12
|
+
try {
|
|
13
|
+
const api = (0, api_1.createApiClient)();
|
|
14
|
+
// Find file by path
|
|
15
|
+
const filesResponse = await api.get('/files', { params: { path: remotePath } });
|
|
16
|
+
const files = filesResponse.data;
|
|
17
|
+
if (files.length === 0) {
|
|
18
|
+
spinner.fail(chalk_1.default.red(`File not found: ${remotePath}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const file = files[0];
|
|
22
|
+
spinner.text = 'Creating copy...';
|
|
23
|
+
const resp = await api.post(`/files/${file.id}/copy`);
|
|
24
|
+
spinner.succeed(chalk_1.default.green('Copy created'));
|
|
25
|
+
const created = resp.data;
|
|
26
|
+
console.log(chalk_1.default.gray(`Created: ${created.path || `/${created.name}`}`));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
spinner.fail(chalk_1.default.red('Copy failed'));
|
|
30
|
+
console.error(chalk_1.default.red(error.response?.data?.error || error.message));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function tasksListCommand(): Promise<void>;
|
|
2
|
+
export declare function taskRunCommand(taskId: string): Promise<void>;
|
|
3
|
+
export declare function taskStopCommand(taskId: string): Promise<void>;
|
|
4
|
+
export declare function taskDeleteCommand(taskId: string, options: {
|
|
5
|
+
force?: boolean;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function taskEnableCommand(taskId: string, enable: boolean): Promise<void>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.tasksListCommand = tasksListCommand;
|
|
40
|
+
exports.taskRunCommand = taskRunCommand;
|
|
41
|
+
exports.taskStopCommand = taskStopCommand;
|
|
42
|
+
exports.taskDeleteCommand = taskDeleteCommand;
|
|
43
|
+
exports.taskEnableCommand = taskEnableCommand;
|
|
44
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
45
|
+
const ora_1 = __importDefault(require("ora"));
|
|
46
|
+
const api_1 = require("../api");
|
|
47
|
+
async function tasksListCommand() {
|
|
48
|
+
const spinner = (0, ora_1.default)('Fetching tasks...').start();
|
|
49
|
+
try {
|
|
50
|
+
const client = (0, api_1.createApiClient)();
|
|
51
|
+
const response = await client.get('/tasks');
|
|
52
|
+
const tasks = response.data;
|
|
53
|
+
spinner.stop();
|
|
54
|
+
if (tasks.length === 0) {
|
|
55
|
+
console.log(chalk_1.default.yellow('No tasks found.'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.log(chalk_1.default.bold('\nBackup Tasks:\n'));
|
|
59
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
60
|
+
for (const task of tasks) {
|
|
61
|
+
const status = task.enabled ? chalk_1.default.green('●') : chalk_1.default.red('○');
|
|
62
|
+
const isRunning = task.lastStarted && (!task.lastRun || new Date(task.lastStarted) > new Date(task.lastRun));
|
|
63
|
+
const runStatus = isRunning ? chalk_1.default.blue(' [RUNNING]') : '';
|
|
64
|
+
console.log(`${status} ${chalk_1.default.bold(task.name)}${runStatus}`);
|
|
65
|
+
console.log(` ${chalk_1.default.gray('ID:')} ${task.id}`);
|
|
66
|
+
console.log(` ${chalk_1.default.gray('Schedule:')} ${task.cron}`);
|
|
67
|
+
console.log(` ${chalk_1.default.gray('SFTP:')} ${task.sftpUser}@${task.sftpHost}:${task.sftpPort}${task.sftpPath}`);
|
|
68
|
+
console.log(` ${chalk_1.default.gray('Destination:')} ${task.destinationPath || '/'}`);
|
|
69
|
+
console.log(` ${chalk_1.default.gray('Compress:')} ${task.compress} | ${chalk_1.default.gray('Max Files:')} ${task.maxFiles || 'unlimited'}`);
|
|
70
|
+
if (task.lastRun) {
|
|
71
|
+
const lastRun = new Date(task.lastRun).toLocaleString();
|
|
72
|
+
const runtime = task.lastRuntime ? ` (${formatRuntime(task.lastRuntime)})` : '';
|
|
73
|
+
console.log(` ${chalk_1.default.gray('Last Run:')} ${lastRun}${runtime}`);
|
|
74
|
+
}
|
|
75
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
76
|
+
}
|
|
77
|
+
console.log(chalk_1.default.gray(`\nTotal: ${tasks.length} task(s)`));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
spinner.fail('Failed to fetch tasks');
|
|
81
|
+
console.error(chalk_1.default.red(error.response?.data?.error || error.message));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function taskRunCommand(taskId) {
|
|
86
|
+
const spinner = (0, ora_1.default)('Starting task...').start();
|
|
87
|
+
try {
|
|
88
|
+
const client = (0, api_1.createApiClient)();
|
|
89
|
+
const response = await client.post(`/tasks/${taskId}/run`);
|
|
90
|
+
spinner.succeed(chalk_1.default.green('Task started successfully!'));
|
|
91
|
+
console.log(chalk_1.default.gray(`Task ID: ${taskId}`));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error.response?.status === 409) {
|
|
95
|
+
spinner.fail(chalk_1.default.yellow('Task is already running'));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
spinner.fail('Failed to start task');
|
|
99
|
+
}
|
|
100
|
+
console.error(chalk_1.default.red(error.response?.data?.error || error.message));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function taskStopCommand(taskId) {
|
|
105
|
+
const spinner = (0, ora_1.default)('Stopping task...').start();
|
|
106
|
+
try {
|
|
107
|
+
const client = (0, api_1.createApiClient)();
|
|
108
|
+
await client.post(`/tasks/${taskId}/stop`);
|
|
109
|
+
spinner.succeed(chalk_1.default.green('Task stopped successfully!'));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
spinner.fail('Failed to stop task');
|
|
113
|
+
console.error(chalk_1.default.red(error.response?.data?.error || error.message));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function taskDeleteCommand(taskId, options) {
|
|
118
|
+
try {
|
|
119
|
+
if (!options.force) {
|
|
120
|
+
const inquirer = await Promise.resolve().then(() => __importStar(require('inquirer')));
|
|
121
|
+
const { confirm } = await inquirer.default.prompt([
|
|
122
|
+
{
|
|
123
|
+
type: 'confirm',
|
|
124
|
+
name: 'confirm',
|
|
125
|
+
message: `Are you sure you want to delete task ${taskId}?`,
|
|
126
|
+
default: false,
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
if (!confirm) {
|
|
130
|
+
console.log(chalk_1.default.yellow('Cancelled.'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const spinner = (0, ora_1.default)('Deleting task...').start();
|
|
135
|
+
const client = (0, api_1.createApiClient)();
|
|
136
|
+
await client.delete(`/tasks/${taskId}`);
|
|
137
|
+
spinner.succeed(chalk_1.default.green('Task deleted successfully!'));
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.error(chalk_1.default.red(error.response?.data?.error || error.message));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function taskEnableCommand(taskId, enable) {
|
|
145
|
+
const action = enable ? 'Enabling' : 'Disabling';
|
|
146
|
+
const spinner = (0, ora_1.default)(`${action} task...`).start();
|
|
147
|
+
try {
|
|
148
|
+
const client = (0, api_1.createApiClient)();
|
|
149
|
+
await client.patch(`/tasks/${taskId}`, { enabled: enable });
|
|
150
|
+
spinner.succeed(chalk_1.default.green(`Task ${enable ? 'enabled' : 'disabled'} successfully!`));
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
spinner.fail(`Failed to ${enable ? 'enable' : 'disable'} task`);
|
|
154
|
+
console.error(chalk_1.default.red(error.response?.data?.error || error.message));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function formatRuntime(seconds) {
|
|
159
|
+
if (seconds < 60)
|
|
160
|
+
return `${seconds}s`;
|
|
161
|
+
const mins = Math.round(seconds / 60);
|
|
162
|
+
if (mins < 60)
|
|
163
|
+
return `${mins}m`;
|
|
164
|
+
const hours = Math.floor(mins / 60);
|
|
165
|
+
const remainingMins = mins % 60;
|
|
166
|
+
return `${hours}h ${remainingMins}m`;
|
|
167
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,11 +11,14 @@ const upload_1 = require("./commands/upload");
|
|
|
11
11
|
const download_1 = require("./commands/download");
|
|
12
12
|
const list_1 = require("./commands/list");
|
|
13
13
|
const delete_1 = require("./commands/delete");
|
|
14
|
+
const copy_1 = require("./commands/copy");
|
|
15
|
+
const tasks_1 = require("./commands/tasks");
|
|
16
|
+
const pkg = require('../package.json');
|
|
14
17
|
const program = new commander_1.Command();
|
|
15
18
|
program
|
|
16
19
|
.name('d-drive')
|
|
17
20
|
.description('D-Drive CLI - Discord-based cloud storage for developers')
|
|
18
|
-
.version('
|
|
21
|
+
.version(pkg.version || '0.0.0');
|
|
19
22
|
// Config command
|
|
20
23
|
program
|
|
21
24
|
.command('config')
|
|
@@ -52,6 +55,42 @@ program
|
|
|
52
55
|
.option('-r, --recursive', 'Delete directory recursively')
|
|
53
56
|
.option('-f, --force', 'Force deletion without confirmation')
|
|
54
57
|
.action(delete_1.deleteCommand);
|
|
58
|
+
// Copy command
|
|
59
|
+
program
|
|
60
|
+
.command('copy <path>')
|
|
61
|
+
.description('Make a copy of a file in-place (auto-numbered)')
|
|
62
|
+
.action(copy_1.copyCommand);
|
|
63
|
+
// Tasks commands
|
|
64
|
+
const tasks = program
|
|
65
|
+
.command('tasks')
|
|
66
|
+
.description('Manage backup tasks');
|
|
67
|
+
tasks
|
|
68
|
+
.command('list')
|
|
69
|
+
.alias('ls')
|
|
70
|
+
.description('List all backup tasks')
|
|
71
|
+
.action(tasks_1.tasksListCommand);
|
|
72
|
+
tasks
|
|
73
|
+
.command('run <taskId>')
|
|
74
|
+
.description('Run a task immediately')
|
|
75
|
+
.action(tasks_1.taskRunCommand);
|
|
76
|
+
tasks
|
|
77
|
+
.command('stop <taskId>')
|
|
78
|
+
.description('Stop a running task')
|
|
79
|
+
.action(tasks_1.taskStopCommand);
|
|
80
|
+
tasks
|
|
81
|
+
.command('delete <taskId>')
|
|
82
|
+
.alias('rm')
|
|
83
|
+
.description('Delete a task')
|
|
84
|
+
.option('-f, --force', 'Force deletion without confirmation')
|
|
85
|
+
.action(tasks_1.taskDeleteCommand);
|
|
86
|
+
tasks
|
|
87
|
+
.command('enable <taskId>')
|
|
88
|
+
.description('Enable a task')
|
|
89
|
+
.action((taskId) => (0, tasks_1.taskEnableCommand)(taskId, true));
|
|
90
|
+
tasks
|
|
91
|
+
.command('disable <taskId>')
|
|
92
|
+
.description('Disable a task')
|
|
93
|
+
.action((taskId) => (0, tasks_1.taskEnableCommand)(taskId, false));
|
|
55
94
|
// Help command
|
|
56
95
|
program.on('--help', () => {
|
|
57
96
|
console.log('');
|
|
@@ -62,5 +101,13 @@ program.on('--help', () => {
|
|
|
62
101
|
console.log(' $ d-drive download /backups/file.txt ./restored.txt');
|
|
63
102
|
console.log(' $ d-drive list /backups');
|
|
64
103
|
console.log(' $ d-drive delete /backups/old-file.txt');
|
|
104
|
+
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');
|
|
65
112
|
});
|
|
66
113
|
program.parse();
|
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createApiClient } from '../api';
|
|
4
|
+
|
|
5
|
+
export async function copyCommand(remotePath: string) {
|
|
6
|
+
const spinner = ora('Finding file...').start();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const api = createApiClient();
|
|
10
|
+
|
|
11
|
+
// Find file by path
|
|
12
|
+
const filesResponse = await api.get('/files', { params: { path: remotePath } });
|
|
13
|
+
const files = filesResponse.data;
|
|
14
|
+
if (files.length === 0) {
|
|
15
|
+
spinner.fail(chalk.red(`File not found: ${remotePath}`));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const file = files[0];
|
|
20
|
+
spinner.text = 'Creating copy...';
|
|
21
|
+
|
|
22
|
+
const resp = await api.post(`/files/${file.id}/copy`);
|
|
23
|
+
|
|
24
|
+
spinner.succeed(chalk.green('Copy created'));
|
|
25
|
+
const created = resp.data;
|
|
26
|
+
console.log(chalk.gray(`Created: ${created.path || `/${created.name}`}`));
|
|
27
|
+
} catch (error: any) {
|
|
28
|
+
spinner.fail(chalk.red('Copy failed'));
|
|
29
|
+
console.error(chalk.red(error.response?.data?.error || error.message));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createApiClient } from '../api';
|
|
4
|
+
|
|
5
|
+
interface Task {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
cron: string;
|
|
10
|
+
sftpHost: string;
|
|
11
|
+
sftpPort: number;
|
|
12
|
+
sftpUser: string;
|
|
13
|
+
sftpPath: string;
|
|
14
|
+
destinationPath?: string;
|
|
15
|
+
compress: string;
|
|
16
|
+
maxFiles: number;
|
|
17
|
+
lastRun?: string;
|
|
18
|
+
lastStarted?: string;
|
|
19
|
+
lastRuntime?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function tasksListCommand() {
|
|
23
|
+
const spinner = ora('Fetching tasks...').start();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const client = createApiClient();
|
|
27
|
+
const response = await client.get('/tasks');
|
|
28
|
+
const tasks: Task[] = response.data;
|
|
29
|
+
|
|
30
|
+
spinner.stop();
|
|
31
|
+
|
|
32
|
+
if (tasks.length === 0) {
|
|
33
|
+
console.log(chalk.yellow('No tasks found.'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.bold('\nBackup Tasks:\n'));
|
|
38
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
39
|
+
|
|
40
|
+
for (const task of tasks) {
|
|
41
|
+
const status = task.enabled ? chalk.green('●') : chalk.red('○');
|
|
42
|
+
const isRunning = task.lastStarted && (!task.lastRun || new Date(task.lastStarted) > new Date(task.lastRun));
|
|
43
|
+
const runStatus = isRunning ? chalk.blue(' [RUNNING]') : '';
|
|
44
|
+
|
|
45
|
+
console.log(`${status} ${chalk.bold(task.name)}${runStatus}`);
|
|
46
|
+
console.log(` ${chalk.gray('ID:')} ${task.id}`);
|
|
47
|
+
console.log(` ${chalk.gray('Schedule:')} ${task.cron}`);
|
|
48
|
+
console.log(` ${chalk.gray('SFTP:')} ${task.sftpUser}@${task.sftpHost}:${task.sftpPort}${task.sftpPath}`);
|
|
49
|
+
console.log(` ${chalk.gray('Destination:')} ${task.destinationPath || '/'}`);
|
|
50
|
+
console.log(` ${chalk.gray('Compress:')} ${task.compress} | ${chalk.gray('Max Files:')} ${task.maxFiles || 'unlimited'}`);
|
|
51
|
+
|
|
52
|
+
if (task.lastRun) {
|
|
53
|
+
const lastRun = new Date(task.lastRun).toLocaleString();
|
|
54
|
+
const runtime = task.lastRuntime ? ` (${formatRuntime(task.lastRuntime)})` : '';
|
|
55
|
+
console.log(` ${chalk.gray('Last Run:')} ${lastRun}${runtime}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(chalk.gray(`\nTotal: ${tasks.length} task(s)`));
|
|
62
|
+
|
|
63
|
+
} catch (error: any) {
|
|
64
|
+
spinner.fail('Failed to fetch tasks');
|
|
65
|
+
console.error(chalk.red(error.response?.data?.error || error.message));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function taskRunCommand(taskId: string) {
|
|
71
|
+
const spinner = ora('Starting task...').start();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const client = createApiClient();
|
|
75
|
+
const response = await client.post(`/tasks/${taskId}/run`);
|
|
76
|
+
|
|
77
|
+
spinner.succeed(chalk.green('Task started successfully!'));
|
|
78
|
+
console.log(chalk.gray(`Task ID: ${taskId}`));
|
|
79
|
+
|
|
80
|
+
} catch (error: any) {
|
|
81
|
+
if (error.response?.status === 409) {
|
|
82
|
+
spinner.fail(chalk.yellow('Task is already running'));
|
|
83
|
+
} else {
|
|
84
|
+
spinner.fail('Failed to start task');
|
|
85
|
+
}
|
|
86
|
+
console.error(chalk.red(error.response?.data?.error || error.message));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function taskStopCommand(taskId: string) {
|
|
92
|
+
const spinner = ora('Stopping task...').start();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const client = createApiClient();
|
|
96
|
+
await client.post(`/tasks/${taskId}/stop`);
|
|
97
|
+
|
|
98
|
+
spinner.succeed(chalk.green('Task stopped successfully!'));
|
|
99
|
+
|
|
100
|
+
} catch (error: any) {
|
|
101
|
+
spinner.fail('Failed to stop task');
|
|
102
|
+
console.error(chalk.red(error.response?.data?.error || error.message));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function taskDeleteCommand(taskId: string, options: { force?: boolean }) {
|
|
108
|
+
try {
|
|
109
|
+
if (!options.force) {
|
|
110
|
+
const inquirer = await import('inquirer');
|
|
111
|
+
const { confirm } = await inquirer.default.prompt([
|
|
112
|
+
{
|
|
113
|
+
type: 'confirm',
|
|
114
|
+
name: 'confirm',
|
|
115
|
+
message: `Are you sure you want to delete task ${taskId}?`,
|
|
116
|
+
default: false,
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
if (!confirm) {
|
|
121
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const spinner = ora('Deleting task...').start();
|
|
127
|
+
const client = createApiClient();
|
|
128
|
+
await client.delete(`/tasks/${taskId}`);
|
|
129
|
+
|
|
130
|
+
spinner.succeed(chalk.green('Task deleted successfully!'));
|
|
131
|
+
|
|
132
|
+
} catch (error: any) {
|
|
133
|
+
console.error(chalk.red(error.response?.data?.error || error.message));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function taskEnableCommand(taskId: string, enable: boolean) {
|
|
139
|
+
const action = enable ? 'Enabling' : 'Disabling';
|
|
140
|
+
const spinner = ora(`${action} task...`).start();
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const client = createApiClient();
|
|
144
|
+
await client.patch(`/tasks/${taskId}`, { enabled: enable });
|
|
145
|
+
|
|
146
|
+
spinner.succeed(chalk.green(`Task ${enable ? 'enabled' : 'disabled'} successfully!`));
|
|
147
|
+
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
spinner.fail(`Failed to ${enable ? 'enable' : 'disable'} task`);
|
|
150
|
+
console.error(chalk.red(error.response?.data?.error || error.message));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function formatRuntime(seconds: number): string {
|
|
156
|
+
if (seconds < 60) return `${seconds}s`;
|
|
157
|
+
const mins = Math.round(seconds / 60);
|
|
158
|
+
if (mins < 60) return `${mins}m`;
|
|
159
|
+
const hours = Math.floor(mins / 60);
|
|
160
|
+
const remainingMins = mins % 60;
|
|
161
|
+
return `${hours}h ${remainingMins}m`;
|
|
162
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,13 +7,16 @@ import { uploadCommand } from './commands/upload';
|
|
|
7
7
|
import { downloadCommand } from './commands/download';
|
|
8
8
|
import { listCommand } from './commands/list';
|
|
9
9
|
import { deleteCommand } from './commands/delete';
|
|
10
|
+
import { copyCommand } from './commands/copy';
|
|
11
|
+
import { tasksListCommand, taskRunCommand, taskStopCommand, taskDeleteCommand, taskEnableCommand } from './commands/tasks';
|
|
12
|
+
const pkg = require('../package.json');
|
|
10
13
|
|
|
11
14
|
const program = new Command();
|
|
12
15
|
|
|
13
16
|
program
|
|
14
17
|
.name('d-drive')
|
|
15
18
|
.description('D-Drive CLI - Discord-based cloud storage for developers')
|
|
16
|
-
.version('
|
|
19
|
+
.version(pkg.version || '0.0.0');
|
|
17
20
|
|
|
18
21
|
// Config command
|
|
19
22
|
program
|
|
@@ -56,6 +59,50 @@ program
|
|
|
56
59
|
.option('-f, --force', 'Force deletion without confirmation')
|
|
57
60
|
.action(deleteCommand);
|
|
58
61
|
|
|
62
|
+
// Copy command
|
|
63
|
+
program
|
|
64
|
+
.command('copy <path>')
|
|
65
|
+
.description('Make a copy of a file in-place (auto-numbered)')
|
|
66
|
+
.action(copyCommand);
|
|
67
|
+
|
|
68
|
+
// Tasks commands
|
|
69
|
+
const tasks = program
|
|
70
|
+
.command('tasks')
|
|
71
|
+
.description('Manage backup tasks');
|
|
72
|
+
|
|
73
|
+
tasks
|
|
74
|
+
.command('list')
|
|
75
|
+
.alias('ls')
|
|
76
|
+
.description('List all backup tasks')
|
|
77
|
+
.action(tasksListCommand);
|
|
78
|
+
|
|
79
|
+
tasks
|
|
80
|
+
.command('run <taskId>')
|
|
81
|
+
.description('Run a task immediately')
|
|
82
|
+
.action(taskRunCommand);
|
|
83
|
+
|
|
84
|
+
tasks
|
|
85
|
+
.command('stop <taskId>')
|
|
86
|
+
.description('Stop a running task')
|
|
87
|
+
.action(taskStopCommand);
|
|
88
|
+
|
|
89
|
+
tasks
|
|
90
|
+
.command('delete <taskId>')
|
|
91
|
+
.alias('rm')
|
|
92
|
+
.description('Delete a task')
|
|
93
|
+
.option('-f, --force', 'Force deletion without confirmation')
|
|
94
|
+
.action(taskDeleteCommand);
|
|
95
|
+
|
|
96
|
+
tasks
|
|
97
|
+
.command('enable <taskId>')
|
|
98
|
+
.description('Enable a task')
|
|
99
|
+
.action((taskId: string) => taskEnableCommand(taskId, true));
|
|
100
|
+
|
|
101
|
+
tasks
|
|
102
|
+
.command('disable <taskId>')
|
|
103
|
+
.description('Disable a task')
|
|
104
|
+
.action((taskId: string) => taskEnableCommand(taskId, false));
|
|
105
|
+
|
|
59
106
|
// Help command
|
|
60
107
|
program.on('--help', () => {
|
|
61
108
|
console.log('');
|
|
@@ -66,6 +113,14 @@ program.on('--help', () => {
|
|
|
66
113
|
console.log(' $ d-drive download /backups/file.txt ./restored.txt');
|
|
67
114
|
console.log(' $ d-drive list /backups');
|
|
68
115
|
console.log(' $ d-drive delete /backups/old-file.txt');
|
|
116
|
+
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');
|
|
69
124
|
});
|
|
70
125
|
|
|
71
126
|
program.parse();
|