chandao4 0.1.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.
Files changed (49) hide show
  1. package/.env.example +10 -0
  2. package/CHANGELOG.md +21 -0
  3. package/LICENSE +21 -0
  4. package/README.md +184 -0
  5. package/dist/commands/bug.d.ts +3 -0
  6. package/dist/commands/bug.js +373 -0
  7. package/dist/commands/config.d.ts +2 -0
  8. package/dist/commands/config.js +38 -0
  9. package/dist/commands/login.d.ts +3 -0
  10. package/dist/commands/login.js +83 -0
  11. package/dist/commands/product.d.ts +3 -0
  12. package/dist/commands/product.js +41 -0
  13. package/dist/commands/project.d.ts +3 -0
  14. package/dist/commands/project.js +70 -0
  15. package/dist/commands/task.d.ts +3 -0
  16. package/dist/commands/task.js +445 -0
  17. package/dist/config/config.d.ts +17 -0
  18. package/dist/config/config.js +216 -0
  19. package/dist/config/defaults.d.ts +5 -0
  20. package/dist/config/defaults.js +23 -0
  21. package/dist/core/api-client.d.ts +20 -0
  22. package/dist/core/api-client.js +127 -0
  23. package/dist/core/auth.d.ts +44 -0
  24. package/dist/core/auth.js +244 -0
  25. package/dist/core/errors.d.ts +17 -0
  26. package/dist/core/errors.js +61 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +125 -0
  29. package/dist/services/bug.service.d.ts +59 -0
  30. package/dist/services/bug.service.js +232 -0
  31. package/dist/services/product.service.d.ts +12 -0
  32. package/dist/services/product.service.js +43 -0
  33. package/dist/services/project.service.d.ts +18 -0
  34. package/dist/services/project.service.js +35 -0
  35. package/dist/services/task.service.d.ts +55 -0
  36. package/dist/services/task.service.js +254 -0
  37. package/dist/types/api.d.ts +31 -0
  38. package/dist/types/api.js +3 -0
  39. package/dist/types/config.d.ts +18 -0
  40. package/dist/types/config.js +3 -0
  41. package/dist/types/models.d.ts +65 -0
  42. package/dist/types/models.js +33 -0
  43. package/dist/utils/format.d.ts +38 -0
  44. package/dist/utils/format.js +201 -0
  45. package/dist/utils/prompt.d.ts +12 -0
  46. package/dist/utils/prompt.js +154 -0
  47. package/dist/utils/validators.d.ts +14 -0
  48. package/dist/utils/validators.js +42 -0
  49. package/package.json +63 -0
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ // 登录/登出命令
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
+ exports.createLoginCommand = createLoginCommand;
8
+ exports.createLogoutCommand = createLogoutCommand;
9
+ const commander_1 = require("commander");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const ora_1 = __importDefault(require("ora"));
12
+ const config_1 = require("../config/config");
13
+ const auth_1 = require("../core/auth");
14
+ const prompt_1 = require("../utils/prompt");
15
+ function createLoginCommand() {
16
+ const login = new commander_1.Command('login')
17
+ .description('交互式登录,保存凭据到本地')
18
+ .action(async () => {
19
+ console.log(chalk_1.default.bold('\n🔐 禅道登录\n'));
20
+ const rawCfg = (0, config_1.loadRawConfig)();
21
+ const currentUrl = rawCfg.server.url || '';
22
+ const currentUser = rawCfg.server.username || '';
23
+ const url = await (0, prompt_1.prompt)('禅道地址', currentUrl || 'https://your-company.chandao.com');
24
+ if (!url) {
25
+ console.error(chalk_1.default.red('错误: 地址不能为空'));
26
+ process.exit(1);
27
+ }
28
+ const username = await (0, prompt_1.prompt)('用户名', currentUser || '');
29
+ if (!username) {
30
+ console.error(chalk_1.default.red('错误: 用户名不能为空'));
31
+ process.exit(1);
32
+ }
33
+ const password = await (0, prompt_1.promptPassword)('密码');
34
+ if (!password) {
35
+ console.error(chalk_1.default.red('错误: 密码不能为空'));
36
+ process.exit(1);
37
+ }
38
+ (0, config_1.saveUserConfig)('server.url', url);
39
+ (0, config_1.saveUserConfig)('server.username', username);
40
+ (0, config_1.saveUserConfig)('server.password', password);
41
+ console.log('');
42
+ const spinner = (0, ora_1.default)('正在验证登录...').start();
43
+ try {
44
+ const cfg = (0, config_1.loadConfig)();
45
+ const auth = new auth_1.AuthManager(cfg);
46
+ const ok = await auth.login();
47
+ if (ok) {
48
+ spinner.succeed(chalk_1.default.green('登录成功!'));
49
+ console.log(chalk_1.default.gray(`\n配置已保存到 ~/.chandao4/config.json`));
50
+ }
51
+ else {
52
+ spinner.fail('登录失败,请检查地址和凭据');
53
+ }
54
+ }
55
+ catch (err) {
56
+ spinner.fail('连接失败');
57
+ console.error(String(err));
58
+ }
59
+ });
60
+ return login;
61
+ }
62
+ function createLogoutCommand() {
63
+ const logout = new commander_1.Command('logout')
64
+ .description('清除本地保存的凭据')
65
+ .action(async () => {
66
+ console.log(chalk_1.default.bold('\n👋 退出登录\n'));
67
+ const rawCfg = (0, config_1.loadRawConfig)();
68
+ if (!rawCfg.server.username && !rawCfg.server.password) {
69
+ console.log(chalk_1.default.gray('没有保存的登录凭据'));
70
+ return;
71
+ }
72
+ console.log(` 服务器: ${rawCfg.server.url || chalk_1.default.gray('(未设置)')}`);
73
+ console.log(` 用户名: ${rawCfg.server.username || chalk_1.default.gray('(未设置)')}`);
74
+ const confirmed = await (0, prompt_1.confirm)('确定要清除所有本地凭据吗?');
75
+ if (!confirmed) {
76
+ console.log(chalk_1.default.gray('已取消'));
77
+ return;
78
+ }
79
+ (0, config_1.clearUserConfig)();
80
+ console.log(chalk_1.default.green('✓ 凭据已清除'));
81
+ });
82
+ return logout;
83
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import { ProductService } from '../services/product.service';
3
+ export declare function createProductCommand(productService: ProductService, getUseJson: () => boolean): Command;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ // Product 辅助命令
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
+ exports.createProductCommand = createProductCommand;
8
+ const commander_1 = require("commander");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const format_1 = require("../utils/format");
11
+ function createProductCommand(productService, getUseJson) {
12
+ const product = new commander_1.Command('product')
13
+ .description('产品查询');
14
+ product.command('list')
15
+ .description('列出所有产品')
16
+ .option('-l, --limit <n>', '每页条数(客户端分页)', (v) => parseInt(v, 10), 0)
17
+ .option('--page <n>', '页码', (v) => parseInt(v, 10), 1)
18
+ .action(async (options) => {
19
+ try {
20
+ const { products, total } = await productService.getList();
21
+ // 客户端分页
22
+ const limit = options.limit || 0;
23
+ const page = options.page || 1;
24
+ const paged = limit > 0
25
+ ? products.slice((page - 1) * limit, page * limit)
26
+ : products;
27
+ if (getUseJson()) {
28
+ console.log((0, format_1.formatJson)(paged));
29
+ }
30
+ else {
31
+ console.log((0, format_1.formatProductTable)(paged));
32
+ const pageInfo = limit ? `第 ${page} 页,共 ${total} 条` : `共 ${total} 条`;
33
+ console.log(chalk_1.default.gray(`\n${pageInfo}`));
34
+ }
35
+ }
36
+ catch (err) {
37
+ process.exit(1);
38
+ }
39
+ });
40
+ return product;
41
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import { ProjectService } from '../services/project.service';
3
+ export declare function createProjectCommand(projectService: ProjectService, getUseJson: () => boolean): Command;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ // Project 命令
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
+ exports.createProjectCommand = createProjectCommand;
8
+ const commander_1 = require("commander");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const format_1 = require("../utils/format");
11
+ function createProjectCommand(projectService, getUseJson) {
12
+ const project = new commander_1.Command('project')
13
+ .description('项目管理');
14
+ project.command('list')
15
+ .description('列出所有项目')
16
+ .option('-l, --limit <n>', '每页条数(客户端分页)', (v) => parseInt(v, 10), 0)
17
+ .option('--page <n>', '页码', (v) => parseInt(v, 10), 1)
18
+ .action(async (options) => {
19
+ try {
20
+ const { projects, total } = await projectService.getList();
21
+ const limit = options.limit || 0;
22
+ const page = options.page || 1;
23
+ const paged = limit > 0
24
+ ? projects.slice((page - 1) * limit, page * limit)
25
+ : projects;
26
+ if (getUseJson()) {
27
+ console.log((0, format_1.formatJson)(paged));
28
+ }
29
+ else {
30
+ console.log((0, format_1.formatProjectTable)(paged));
31
+ const pageInfo = limit ? `第 ${page} 页,共 ${total} 条` : `共 ${total} 条`;
32
+ console.log(chalk_1.default.gray(`\n${pageInfo}`));
33
+ }
34
+ }
35
+ catch (err) {
36
+ process.exit(1);
37
+ }
38
+ });
39
+ project.command('show <id>')
40
+ .description('查看项目详情')
41
+ .action(async (id) => {
42
+ const pid = parseInt(id, 10);
43
+ if (isNaN(pid)) {
44
+ console.error(chalk_1.default.red(`错误: 无效的项目 ID "${id}"`));
45
+ process.exit(1);
46
+ }
47
+ try {
48
+ const p = await projectService.getDetail(pid);
49
+ if (!p) {
50
+ console.error(chalk_1.default.red(`错误: 项目 #${pid} 未找到`));
51
+ process.exit(1);
52
+ }
53
+ if (getUseJson()) {
54
+ console.log((0, format_1.formatJson)(p));
55
+ }
56
+ else {
57
+ console.log(chalk_1.default.bold(`\n项目 #${p.id}: ${p.name}\n`));
58
+ console.log(`${chalk_1.default.gray('代号:')} ${p.code || '-'}`);
59
+ console.log(`${chalk_1.default.gray('状态:')} ${p.status || '-'}`);
60
+ console.log(`${chalk_1.default.gray('开始:')} ${p.begin || '-'}`);
61
+ console.log(`${chalk_1.default.gray('结束:')} ${p.end || '-'}`);
62
+ console.log(chalk_1.default.gray(`\n💡 查看项目任务: zentao task list -p ${p.id}`));
63
+ }
64
+ }
65
+ catch (err) {
66
+ process.exit(1);
67
+ }
68
+ });
69
+ return project;
70
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import { TaskService } from '../services/task.service';
3
+ export declare function createTaskCommand(taskService: TaskService, getUseJson: () => boolean): Command;
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+ // Task 命令
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
+ exports.createTaskCommand = createTaskCommand;
8
+ const commander_1 = require("commander");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const format_1 = require("../utils/format");
11
+ const prompt_1 = require("../utils/prompt");
12
+ const validators_1 = require("../utils/validators");
13
+ const config_1 = require("../config/config");
14
+ function createTaskCommand(taskService, getUseJson) {
15
+ const task = new commander_1.Command('task')
16
+ .description('任务管理:查看、创建、更新、删除任务');
17
+ // task my - 获取当前用户的任务
18
+ task.command('my')
19
+ .description('获取当前账号的任务')
20
+ .option('-l, --limit <n>', '显示条数', (v) => parseInt(v, 10), 20)
21
+ .option('--page <n>', '页码', (v) => parseInt(v, 10), 1)
22
+ .action(async (options) => {
23
+ try {
24
+ const { tasks, total } = await taskService.getMyTasks();
25
+ const limit = options.limit || 0;
26
+ const page = options.page || 1;
27
+ const paged = limit > 0
28
+ ? tasks.slice((page - 1) * limit, page * limit)
29
+ : tasks;
30
+ if (paged.length === 0) {
31
+ console.log(chalk_1.default.gray('没有找到你的任务'));
32
+ }
33
+ else if (getUseJson()) {
34
+ console.log((0, format_1.formatJson)(paged));
35
+ }
36
+ else {
37
+ console.log((0, format_1.formatTaskTable)(paged));
38
+ const pageInfo = limit
39
+ ? `第 ${page} 页,共 ${total} 条`
40
+ : `共 ${total} 条`;
41
+ console.log(chalk_1.default.gray(`\n${pageInfo}`));
42
+ }
43
+ }
44
+ catch (err) {
45
+ process.exit(1);
46
+ }
47
+ });
48
+ // task list
49
+ task.command('list')
50
+ .description('列出任务')
51
+ .option('-p, --project <id>', '项目 ID', (v) => parseInt(v, 10))
52
+ .option('-s, --status <status>', '状态过滤 (wait|doing|done|pause|cancel|closed)')
53
+ .option('-l, --limit <n>', '显示条数', (v) => parseInt(v, 10), 20)
54
+ .option('--page <n>', '页码', (v) => parseInt(v, 10), 1)
55
+ .action(async (options) => {
56
+ if (!options.project) {
57
+ console.error(chalk_1.default.red('错误: 需要指定 --project <id>(项目 ID)'));
58
+ process.exit(1);
59
+ }
60
+ if (options.status && !(0, validators_1.isValidTaskStatus)(options.status)) {
61
+ console.error(chalk_1.default.red(`错误: 无效的状态 "${options.status}",有效值: wait, doing, done, pause, cancel, closed`));
62
+ process.exit(1);
63
+ }
64
+ try {
65
+ const { tasks, total } = await taskService.getList(options.project);
66
+ const limit = options.limit || 0;
67
+ const page = options.page || 1;
68
+ const paged = limit > 0
69
+ ? tasks.slice((page - 1) * limit, page * limit)
70
+ : tasks;
71
+ if (paged.length === 0) {
72
+ console.log(chalk_1.default.gray('没有找到任务'));
73
+ }
74
+ else if (getUseJson()) {
75
+ console.log((0, format_1.formatJson)(paged));
76
+ }
77
+ else {
78
+ console.log((0, format_1.formatTaskTable)(paged));
79
+ const pageInfo = limit
80
+ ? `第 ${page} 页,共 ${total} 条`
81
+ : `共 ${total} 条`;
82
+ console.log(chalk_1.default.gray(`\n${pageInfo}`));
83
+ }
84
+ }
85
+ catch (err) {
86
+ process.exit(1);
87
+ }
88
+ });
89
+ // task show
90
+ task.command('show <id>')
91
+ .description('查看任务详情')
92
+ .action(async (id) => {
93
+ const taskId = parseInt(id, 10);
94
+ if (!(0, validators_1.isValidId)(taskId)) {
95
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
96
+ process.exit(1);
97
+ }
98
+ try {
99
+ const task = await taskService.getDetail(taskId);
100
+ if (!task) {
101
+ console.error(chalk_1.default.red(`错误: 任务 #${taskId} 未找到`));
102
+ process.exit(1);
103
+ }
104
+ if (getUseJson()) {
105
+ console.log((0, format_1.formatJson)(task));
106
+ }
107
+ else {
108
+ console.log((0, format_1.formatTaskDetail)(task));
109
+ }
110
+ }
111
+ catch (err) {
112
+ process.exit(1);
113
+ }
114
+ });
115
+ // task create
116
+ task.command('create')
117
+ .description('创建任务(交互式)')
118
+ .requiredOption('-p, --project <id>', '项目 ID', (v) => parseInt(v, 10))
119
+ .option('-n, --name <name>', '任务名称')
120
+ .option('-P, --priority <n>', '优先级 (1-4)', (v) => parseInt(v, 10))
121
+ .option('--type <type>', '类型', 'devel')
122
+ .option('--estimate <h>', '预估工时(小时)', parseFloat)
123
+ .option('-a, --assigned-to <user>', '指派给(默认当前登录账号)')
124
+ .option('--est-started <date>', '预计开始 (YYYY-MM-DD)')
125
+ .option('--deadline <date>', '截止日期 (YYYY-MM-DD)')
126
+ .option('--desc <text>', '描述')
127
+ .action(async (options) => {
128
+ try {
129
+ let name = options.name;
130
+ let priority = options.priority;
131
+ let assignedTo = options.assignedTo;
132
+ let estStarted = options.estStarted;
133
+ let deadline = options.deadline;
134
+ const rawCfg = (0, config_1.loadRawConfig)();
135
+ const today = new Date().toISOString().split('T')[0];
136
+ if (!name) {
137
+ name = await (0, prompt_1.prompt)('任务名称');
138
+ if (!name) {
139
+ console.error(chalk_1.default.red('错误: 任务名称不能为空'));
140
+ process.exit(1);
141
+ }
142
+ }
143
+ if (!priority) {
144
+ const input = await (0, prompt_1.prompt)('优先级 (1=紧急, 2=高, 3=中, 4=低)', '3');
145
+ priority = parseInt(input, 10);
146
+ }
147
+ if (!(0, validators_1.isValidPriority)(priority)) {
148
+ console.error(chalk_1.default.red(`错误: 无效的优先级 "${priority}",有效值: 1-4`));
149
+ process.exit(1);
150
+ }
151
+ const defaultUser = rawCfg.server.username || '';
152
+ assignedTo = assignedTo || await (0, prompt_1.prompt)(`指派给`, defaultUser) || defaultUser;
153
+ estStarted = estStarted || await (0, prompt_1.prompt)('预计开始 (YYYY-MM-DD)', today) || today;
154
+ deadline = deadline || await (0, prompt_1.prompt)('截止日期 (YYYY-MM-DD)', estStarted) || estStarted;
155
+ if (!options.desc) {
156
+ const descInput = await (0, prompt_1.prompt)('描述(可跳过)');
157
+ if (descInput)
158
+ options.desc = descInput;
159
+ }
160
+ const task = await taskService.create(options.project, {
161
+ name,
162
+ priority,
163
+ type: options.type,
164
+ estimate: options.estimate,
165
+ assignedTo,
166
+ estStarted,
167
+ deadline,
168
+ desc: options.desc,
169
+ });
170
+ if (task) {
171
+ console.log(chalk_1.default.green(`✓ 任务 #${task.id} 创建成功: ${task.name}`));
172
+ }
173
+ else {
174
+ console.log(chalk_1.default.green('✓ 任务创建成功'));
175
+ }
176
+ }
177
+ catch (err) {
178
+ process.exit(1);
179
+ }
180
+ });
181
+ // task update
182
+ task.command('update <id>')
183
+ .description('更新任务')
184
+ .option('-n, --name <name>', '任务名称')
185
+ .option('-s, --status <status>', '状态 (wait|doing|done|pause|cancel|closed)')
186
+ .option('--priority <n>', '优先级 (1-4)', (v) => parseInt(v, 10))
187
+ .option('-a, --assigned-to <user>', '指派给')
188
+ .option('--estimate <h>', '预估工时(小时)', parseFloat)
189
+ .option('--consumed <h>', '已消耗工时(小时)', parseFloat)
190
+ .option('--left <h>', '剩余工时(小时)', parseFloat)
191
+ .option('--est-started <date>', '预计开始 (YYYY-MM-DD)')
192
+ .option('--deadline <date>', '截止日期 (YYYY-MM-DD)')
193
+ .option('--desc <text>', '描述')
194
+ .action(async (id, options) => {
195
+ const taskId = parseInt(id, 10);
196
+ if (!(0, validators_1.isValidId)(taskId)) {
197
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
198
+ process.exit(1);
199
+ }
200
+ if (options.status && !(0, validators_1.isValidTaskStatus)(options.status)) {
201
+ console.error(chalk_1.default.red(`错误: 无效的状态 "${options.status}"`));
202
+ process.exit(1);
203
+ }
204
+ const updates = {};
205
+ if (options.name)
206
+ updates.name = options.name;
207
+ if (options.status)
208
+ updates.status = options.status;
209
+ if (options.priority)
210
+ updates.priority = options.priority;
211
+ if (options.assignedTo)
212
+ updates.assignedTo = options.assignedTo;
213
+ if (options.estimate !== undefined)
214
+ updates.estimate = options.estimate;
215
+ if (options.consumed !== undefined)
216
+ updates.consumed = options.consumed;
217
+ if (options.left !== undefined)
218
+ updates.left = options.left;
219
+ if (options.deadline)
220
+ updates.deadline = options.deadline;
221
+ if (options.estStarted)
222
+ updates.estStarted = options.estStarted;
223
+ if (options.desc)
224
+ updates.desc = options.desc;
225
+ if (Object.keys(updates).length === 0) {
226
+ console.error(chalk_1.default.yellow('没有指定要更新的字段'));
227
+ process.exit(0);
228
+ }
229
+ try {
230
+ const task = await taskService.update(taskId, updates);
231
+ if (task) {
232
+ console.log(chalk_1.default.green(`✓ 任务 #${task.id} 更新成功`));
233
+ }
234
+ else {
235
+ console.log(chalk_1.default.green('✓ 任务更新成功'));
236
+ }
237
+ }
238
+ catch (err) {
239
+ process.exit(1);
240
+ }
241
+ });
242
+ // task start
243
+ task.command('start <id>')
244
+ .description('开始任务(wait/pause → doing)')
245
+ .option('--consumed <h>', '已消耗工时(小时)', parseFloat)
246
+ .option('--left <h>', '剩余工时(小时)', parseFloat)
247
+ .option('-a, --assigned-to <user>', '指派给')
248
+ .option('-c, --comment <text>', '备注')
249
+ .option('-f, --force', '跳过确认')
250
+ .action(async (id, options) => {
251
+ const taskId = parseInt(id, 10);
252
+ if (!(0, validators_1.isValidId)(taskId)) {
253
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
254
+ process.exit(1);
255
+ }
256
+ try {
257
+ if (!options.force) {
258
+ const confirmed = await (0, prompt_1.confirm)(`确定要开始任务 #${taskId} 吗?`);
259
+ if (!confirmed) {
260
+ console.log(chalk_1.default.gray('已取消'));
261
+ return;
262
+ }
263
+ }
264
+ const task = await taskService.start(taskId, {
265
+ consumed: options.consumed,
266
+ left: options.left,
267
+ assignedTo: options.assignedTo,
268
+ comment: options.comment,
269
+ });
270
+ if (task) {
271
+ console.log(chalk_1.default.green(`✓ 任务 #${task.id} 已开始(状态: ${task.status})`));
272
+ }
273
+ else {
274
+ console.error(chalk_1.default.red('开始任务失败'));
275
+ process.exit(1);
276
+ }
277
+ }
278
+ catch (err) {
279
+ process.exit(1);
280
+ }
281
+ });
282
+ // task finish
283
+ task.command('finish <id>')
284
+ .description('完成任务(doing → done)')
285
+ .option('--consumed <h>', '已消耗工时(小时)', parseFloat)
286
+ .option('-c, --comment <text>', '备注')
287
+ .option('-f, --force', '跳过确认')
288
+ .action(async (id, options) => {
289
+ const taskId = parseInt(id, 10);
290
+ if (!(0, validators_1.isValidId)(taskId)) {
291
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
292
+ process.exit(1);
293
+ }
294
+ try {
295
+ if (!options.force) {
296
+ const confirmed = await (0, prompt_1.confirm)(`确定要完成任务 #${taskId} 吗?`);
297
+ if (!confirmed) {
298
+ console.log(chalk_1.default.gray('已取消'));
299
+ return;
300
+ }
301
+ }
302
+ const task = await taskService.finish(taskId, {
303
+ consumed: options.consumed,
304
+ comment: options.comment,
305
+ });
306
+ if (task) {
307
+ console.log(chalk_1.default.green(`✓ 任务 #${task.id} 已完成`));
308
+ }
309
+ else {
310
+ console.error(chalk_1.default.red('完成任务失败'));
311
+ process.exit(1);
312
+ }
313
+ }
314
+ catch (err) {
315
+ process.exit(1);
316
+ }
317
+ });
318
+ // task close
319
+ task.command('close <id>')
320
+ .description('关闭任务(done/cancel → closed)')
321
+ .option('-c, --comment <text>', '备注')
322
+ .option('-f, --force', '跳过确认')
323
+ .action(async (id, options) => {
324
+ const taskId = parseInt(id, 10);
325
+ if (!(0, validators_1.isValidId)(taskId)) {
326
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
327
+ process.exit(1);
328
+ }
329
+ try {
330
+ if (!options.force) {
331
+ const confirmed = await (0, prompt_1.confirm)(`确定要关闭任务 #${taskId} 吗?`);
332
+ if (!confirmed) {
333
+ console.log(chalk_1.default.gray('已取消'));
334
+ return;
335
+ }
336
+ }
337
+ const task = await taskService.close(taskId, options.comment);
338
+ if (task) {
339
+ console.log(chalk_1.default.green(`✓ 任务 #${task.id} 已关闭`));
340
+ }
341
+ else {
342
+ console.error(chalk_1.default.red('关闭任务失败'));
343
+ process.exit(1);
344
+ }
345
+ }
346
+ catch (err) {
347
+ process.exit(1);
348
+ }
349
+ });
350
+ // task cancel
351
+ task.command('cancel <id>')
352
+ .description('取消任务')
353
+ .option('-c, --comment <text>', '取消原因')
354
+ .option('-f, --force', '跳过确认')
355
+ .action(async (id, options) => {
356
+ const taskId = parseInt(id, 10);
357
+ if (!(0, validators_1.isValidId)(taskId)) {
358
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
359
+ process.exit(1);
360
+ }
361
+ try {
362
+ if (!options.force) {
363
+ const confirmed = await (0, prompt_1.confirm)(`确定要取消任务 #${taskId} 吗?`);
364
+ if (!confirmed) {
365
+ console.log(chalk_1.default.gray('已取消'));
366
+ return;
367
+ }
368
+ }
369
+ const task = await taskService.cancel(taskId, options.comment);
370
+ if (task) {
371
+ console.log(chalk_1.default.green(`✓ 任务 #${task.id} 已取消`));
372
+ }
373
+ else {
374
+ console.error(chalk_1.default.red('取消任务失败'));
375
+ process.exit(1);
376
+ }
377
+ }
378
+ catch (err) {
379
+ process.exit(1);
380
+ }
381
+ });
382
+ // task activate
383
+ task.command('activate <id>')
384
+ .description('激活任务(cancel/closed → wait)')
385
+ .option('--left <h>', '剩余工时(小时)', parseFloat)
386
+ .option('-c, --comment <text>', '备注')
387
+ .option('-f, --force', '跳过确认')
388
+ .action(async (id, options) => {
389
+ const taskId = parseInt(id, 10);
390
+ if (!(0, validators_1.isValidId)(taskId)) {
391
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
392
+ process.exit(1);
393
+ }
394
+ try {
395
+ if (!options.force) {
396
+ const confirmed = await (0, prompt_1.confirm)(`确定要激活任务 #${taskId} 吗?`);
397
+ if (!confirmed) {
398
+ console.log(chalk_1.default.gray('已取消'));
399
+ return;
400
+ }
401
+ }
402
+ const task = await taskService.activate(taskId, {
403
+ left: options.left,
404
+ comment: options.comment,
405
+ });
406
+ if (task) {
407
+ console.log(chalk_1.default.green(`✓ 任务 #${task.id} 已激活(状态: ${task.status})`));
408
+ }
409
+ else {
410
+ console.error(chalk_1.default.red('激活任务失败'));
411
+ process.exit(1);
412
+ }
413
+ }
414
+ catch (err) {
415
+ process.exit(1);
416
+ }
417
+ });
418
+ // task delete
419
+ task.command('delete <id>')
420
+ .description('删除任务')
421
+ .option('-p, --project <id>', '项目 ID', (v) => parseInt(v, 10))
422
+ .option('-f, --force', '跳过确认')
423
+ .action(async (id, options) => {
424
+ const taskId = parseInt(id, 10);
425
+ if (!(0, validators_1.isValidId)(taskId)) {
426
+ console.error(chalk_1.default.red(`错误: 无效的任务 ID "${id}"`));
427
+ process.exit(1);
428
+ }
429
+ try {
430
+ if (!options.force) {
431
+ const confirmed = await (0, prompt_1.confirm)(`确定要删除任务 #${taskId} 吗?`);
432
+ if (!confirmed) {
433
+ console.log(chalk_1.default.gray('已取消'));
434
+ return;
435
+ }
436
+ }
437
+ await taskService.delete(taskId, options.project);
438
+ console.log(chalk_1.default.green(`✓ 任务 #${taskId} 已删除`));
439
+ }
440
+ catch (err) {
441
+ process.exit(1);
442
+ }
443
+ });
444
+ return task;
445
+ }
@@ -0,0 +1,17 @@
1
+ import { ZentaoConfig, RuntimeConfig } from '../types/config';
2
+ /**
3
+ * 不校验,直接读取原始配置(用于 login 之前读取已有配置)
4
+ */
5
+ export declare function loadRawConfig(): ZentaoConfig;
6
+ /**
7
+ * 加载并校验完整配置
8
+ */
9
+ export declare function loadConfig(): RuntimeConfig;
10
+ /**
11
+ * 保存配置到用户目录
12
+ */
13
+ export declare function saveUserConfig(key: string, value: string): void;
14
+ /**
15
+ * 清除用户配置
16
+ */
17
+ export declare function clearUserConfig(): void;