shawnxixi-cli 0.3.1 → 1.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 (82) hide show
  1. package/.env.example +8 -2
  2. package/.github/workflows/ci.yml +9 -22
  3. package/.github/workflows/release.yml +30 -0
  4. package/CLAUDE.md +167 -63
  5. package/README.md +345 -65
  6. package/commitlint.config.js +10 -0
  7. package/dist/commands/biz/calendar.d.ts +17 -2
  8. package/dist/commands/biz/calendar.d.ts.map +1 -1
  9. package/dist/commands/biz/calendar.js +47 -9
  10. package/dist/commands/biz/calendar.js.map +1 -1
  11. package/dist/commands/biz/finance.d.ts +32 -1
  12. package/dist/commands/biz/finance.d.ts.map +1 -1
  13. package/dist/commands/biz/finance.js +99 -12
  14. package/dist/commands/biz/finance.js.map +1 -1
  15. package/dist/commands/biz/health.d.ts +32 -0
  16. package/dist/commands/biz/health.d.ts.map +1 -0
  17. package/dist/commands/biz/health.js +92 -0
  18. package/dist/commands/biz/health.js.map +1 -0
  19. package/dist/commands/biz/item.d.ts +41 -0
  20. package/dist/commands/biz/item.d.ts.map +1 -1
  21. package/dist/commands/biz/item.js +89 -5
  22. package/dist/commands/biz/item.js.map +1 -1
  23. package/dist/commands/biz/record.d.ts +0 -8
  24. package/dist/commands/biz/record.d.ts.map +1 -1
  25. package/dist/commands/biz/record.js +29 -8
  26. package/dist/commands/biz/record.js.map +1 -1
  27. package/dist/commands/biz/task.d.ts +38 -0
  28. package/dist/commands/biz/task.d.ts.map +1 -0
  29. package/dist/commands/biz/task.js +110 -0
  30. package/dist/commands/biz/task.js.map +1 -0
  31. package/dist/commands/biz/todo.d.ts +52 -8
  32. package/dist/commands/biz/todo.d.ts.map +1 -1
  33. package/dist/commands/biz/todo.js +167 -17
  34. package/dist/commands/biz/todo.js.map +1 -1
  35. package/dist/commands/sys/health.d.ts.map +1 -1
  36. package/dist/commands/sys/health.js +20 -3
  37. package/dist/commands/sys/health.js.map +1 -1
  38. package/dist/index.d.ts +5 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +279 -41
  41. package/dist/index.js.map +1 -1
  42. package/dist/services/api.d.ts +22 -8
  43. package/dist/services/api.d.ts.map +1 -1
  44. package/dist/services/api.js +108 -28
  45. package/dist/services/api.js.map +1 -1
  46. package/dist/utils/env.d.ts +16 -0
  47. package/dist/utils/env.d.ts.map +1 -0
  48. package/dist/utils/env.js +78 -0
  49. package/dist/utils/env.js.map +1 -0
  50. package/dist/utils/finance.model.d.ts +80 -0
  51. package/dist/utils/finance.model.d.ts.map +1 -0
  52. package/dist/utils/finance.model.js +150 -0
  53. package/dist/utils/finance.model.js.map +1 -0
  54. package/dist/utils/output.d.ts +42 -0
  55. package/dist/utils/output.d.ts.map +1 -0
  56. package/dist/utils/output.js +124 -0
  57. package/dist/utils/output.js.map +1 -0
  58. package/dist/utils/todo.model.d.ts +55 -0
  59. package/dist/utils/todo.model.d.ts.map +1 -0
  60. package/dist/utils/todo.model.js +135 -0
  61. package/dist/utils/todo.model.js.map +1 -0
  62. package/package.json +18 -2
  63. package/src/commands/biz/calendar.ts +61 -10
  64. package/src/commands/biz/finance.ts +112 -13
  65. package/src/commands/biz/health.ts +96 -0
  66. package/src/commands/biz/item.ts +113 -6
  67. package/src/commands/biz/record.ts +32 -8
  68. package/src/commands/biz/task.ts +115 -0
  69. package/src/commands/biz/todo.ts +193 -21
  70. package/src/commands/sys/health.ts +23 -3
  71. package/src/index.ts +309 -53
  72. package/src/services/api.ts +111 -30
  73. package/src/utils/env.ts +85 -0
  74. package/src/utils/finance.model.ts +182 -0
  75. package/src/utils/output.ts +126 -0
  76. package/src/utils/todo.model.ts +167 -0
  77. package/tests/commands/finance.test.ts +281 -0
  78. package/tests/commands/item.test.ts +215 -0
  79. package/tests/commands/todo.test.ts +292 -9
  80. package/tests/services/api.test.ts +292 -20
  81. package/tests/utils/finance.model.test.ts +319 -0
  82. package/tests/utils/todo.model.test.ts +315 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * biz health 命令
3
+ * 健康数据管理(对标 Apple Health)
4
+ * 子命令:log / list
5
+ */
6
+
7
+ import { apiService } from '../../services/api';
8
+ import { output, outputError, createLoading, isDryRun, dryRunLog } from '../../utils/output';
9
+
10
+ export const healthCommands = {
11
+ /**
12
+ * 记录健康数据
13
+ * biz health log --type heart_rate --value 72 --unit bpm --date 2026-04-12
14
+ */
15
+ log: async (options: { type?: string; value?: string; unit?: string; date?: string } = {}) => {
16
+ const loading = createLoading('记录健康数据...');
17
+
18
+ try {
19
+ if (!options.type || !options.value) {
20
+ loading.fail('缺少必需参数 --type 和 --value');
21
+ outputError('Usage: biz health log --type <type> --value <value> [--unit <unit>] [--date <date>]');
22
+ process.exit(1);
23
+ }
24
+
25
+ if (isDryRun()) {
26
+ loading.stop();
27
+ dryRunLog('记录健康数据', options);
28
+ return;
29
+ }
30
+
31
+ loading.start();
32
+ const result = await apiService.logHealthData(options.type, options.value, options.unit, options.date);
33
+ loading.succeed('健康数据记录成功');
34
+ output(result);
35
+ } catch (error: any) {
36
+ loading.fail('记录健康数据失败');
37
+ outputError(error.message);
38
+ process.exit(1);
39
+ }
40
+ },
41
+
42
+ /**
43
+ * 查询健康数据
44
+ * biz health list --type heart_rate --from 2026-04-01 --to 2026-04-12
45
+ */
46
+ list: async (options: { type?: string; from?: string; to?: string } = {}) => {
47
+ const loading = createLoading('加载健康数据...');
48
+
49
+ try {
50
+ if (isDryRun()) {
51
+ loading.stop();
52
+ dryRunLog('查询健康数据', options);
53
+ return;
54
+ }
55
+
56
+ loading.start();
57
+ const result = await apiService.listHealthData(options.type, options.from, options.to);
58
+ loading.succeed('健康数据加载成功');
59
+
60
+ if (result.data && result.data.length > 0) {
61
+ output(result.data);
62
+ } else {
63
+ output({ message: '暂无健康数据' });
64
+ }
65
+ } catch (error: any) {
66
+ loading.fail('加载健康数据失败');
67
+ outputError(error.message);
68
+ process.exit(1);
69
+ }
70
+ },
71
+
72
+ /**
73
+ * 同步健康数据(保留兼容)
74
+ * biz health sync
75
+ */
76
+ sync: async () => {
77
+ const loading = createLoading('同步健康数据...');
78
+
79
+ try {
80
+ if (isDryRun()) {
81
+ loading.stop();
82
+ dryRunLog('同步健康数据', {});
83
+ return;
84
+ }
85
+
86
+ loading.start();
87
+ const result = await apiService.syncHealthData();
88
+ loading.succeed('健康数据同步成功');
89
+ output(result);
90
+ } catch (error: any) {
91
+ loading.fail('同步健康数据失败');
92
+ outputError(error.message);
93
+ process.exit(1);
94
+ }
95
+ },
96
+ };
@@ -1,22 +1,129 @@
1
1
  import { apiService } from '../../services/api';
2
+ import { output, outputError, createLoading, isDryRun, dryRunLog } from '../../utils/output';
3
+
4
+ export interface ItemData {
5
+ id: string;
6
+ name: string;
7
+ category?: string;
8
+ location?: string;
9
+ expireDate?: string;
10
+ quantity?: number;
11
+ unit?: string;
12
+ brand?: string;
13
+ barcode?: string;
14
+ price?: number;
15
+ supplier?: string;
16
+ purchaseDate?: string;
17
+ }
2
18
 
3
19
  export const itemCommands = {
4
- create: async (name: string, options: { location?: string; expire?: string; category?: string } = {}) => {
20
+ /**
21
+ * 创建物品
22
+ * biz item add <name> --category <cat> --location <loc> --expire <date> --quantity <n> --unit <unit> --brand <brand> --barcode <code> --price <price> --supplier <supplier> --purchase-date <date>
23
+ */
24
+ create: async (name: string, options: { location?: string; expire?: string; category?: string; quantity?: number; unit?: string; brand?: string; barcode?: string; price?: number; supplier?: string; purchaseDate?: string } = {}) => {
25
+ const loading = createLoading('创建物品...');
26
+
5
27
  try {
6
- const result = await apiService.createItem(name, options.category, options.location, options.expire);
7
- console.log(JSON.stringify(result, null, 2));
28
+ if (isDryRun()) {
29
+ loading.stop();
30
+ dryRunLog('创建物品', { name, ...options });
31
+ return;
32
+ }
33
+
34
+ loading.start();
35
+ const result = await apiService.createItem(name, options.category, options.location, options.expire, options.quantity, options.unit, options.brand, options.barcode, options.price, options.supplier, options.purchaseDate);
36
+ loading.succeed('物品创建成功');
37
+ output(result);
8
38
  } catch (error: any) {
9
- console.error('创建物品失败:', error.message);
39
+ loading.fail('创建物品失败');
40
+ outputError(error.message);
10
41
  process.exit(1);
11
42
  }
12
43
  },
13
44
 
45
+ /**
46
+ * 列出物品
47
+ * biz item list --category <cat> --expiring
48
+ */
14
49
  list: async (options: { category?: string; expiring?: boolean } = {}) => {
50
+ const loading = createLoading('加载物品列表...');
51
+
15
52
  try {
53
+ if (isDryRun()) {
54
+ loading.stop();
55
+ dryRunLog('列出物品', options);
56
+ return;
57
+ }
58
+
59
+ loading.start();
16
60
  const result = await apiService.listItems(options.category);
17
- console.log(JSON.stringify(result, null, 2));
61
+ loading.succeed('物品列表加载成功');
62
+
63
+ if (result.data && result.data.length > 0) {
64
+ output(result.data);
65
+ } else {
66
+ output({ message: '暂无物品' });
67
+ }
68
+ } catch (error: any) {
69
+ loading.fail('加载物品列表失败');
70
+ outputError(error.message);
71
+ process.exit(1);
72
+ }
73
+ },
74
+
75
+ /**
76
+ * 查看即将过期的物品
77
+ * biz item expiring --days 7
78
+ */
79
+ expiring: async (options: { days?: number } = {}) => {
80
+ const loading = createLoading('查找即将过期物品...');
81
+
82
+ try {
83
+ if (isDryRun()) {
84
+ loading.stop();
85
+ dryRunLog('即将过期物品', options);
86
+ return;
87
+ }
88
+
89
+ loading.start();
90
+ const days = options.days || 7;
91
+ const result = await apiService.getExpiringItems(days);
92
+ loading.succeed('即将过期物品加载成功');
93
+
94
+ if (result.data && result.data.length > 0) {
95
+ output(result.data);
96
+ } else {
97
+ output({ message: '暂无即将过期的物品' });
98
+ }
99
+ } catch (error: any) {
100
+ loading.fail('查找即将过期物品失败');
101
+ outputError(error.message);
102
+ process.exit(1);
103
+ }
104
+ },
105
+
106
+ /**
107
+ * 物品库存统计
108
+ * biz item stats
109
+ */
110
+ stats: async () => {
111
+ const loading = createLoading('计算物品统计...');
112
+
113
+ try {
114
+ if (isDryRun()) {
115
+ loading.stop();
116
+ dryRunLog('物品统计', {});
117
+ return;
118
+ }
119
+
120
+ loading.start();
121
+ const result = await apiService.getItemStats();
122
+ loading.succeed('统计完成');
123
+ output(result);
18
124
  } catch (error: any) {
19
- console.error('查询物品失败:', error.message);
125
+ loading.fail('统计失败');
126
+ outputError(error.message);
20
127
  process.exit(1);
21
128
  }
22
129
  },
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { apiService } from '../../services/api';
7
+ import { output, outputError, createLoading, isDryRun, dryRunLog } from '../../utils/output';
7
8
 
8
9
  export interface RecordItem {
9
10
  id: string;
@@ -13,26 +14,49 @@ export interface RecordItem {
13
14
 
14
15
  export const recordCommands = {
15
16
  create: async (title: string, content: string, options: { type?: string; tags?: string } = {}) => {
17
+ const loading = createLoading('创建记录...');
18
+
16
19
  try {
20
+ if (isDryRun()) {
21
+ loading.stop();
22
+ dryRunLog('创建记录', { title, content, ...options });
23
+ return;
24
+ }
25
+
26
+ loading.start();
17
27
  const result = await apiService.createRecord(title, content, options.type, options.tags);
18
- console.log(JSON.stringify(result, null, 2));
28
+ loading.succeed('记录创建成功');
29
+ output(result);
19
30
  } catch (error: any) {
20
- console.error('创建记录失败:', error.message);
31
+ loading.fail('创建记录失败');
32
+ outputError(error.message);
21
33
  process.exit(1);
22
34
  }
23
35
  },
24
36
 
25
37
  list: async (options: { type?: string; tags?: string } = {}) => {
38
+ const loading = createLoading('加载记录列表...');
39
+
26
40
  try {
41
+ if (isDryRun()) {
42
+ loading.stop();
43
+ dryRunLog('列出记录', options);
44
+ return;
45
+ }
46
+
47
+ loading.start();
27
48
  const result = await apiService.listRecords(options.type, options.tags);
28
- console.log(JSON.stringify(result, null, 2));
49
+ loading.succeed('记录列表加载成功');
50
+
51
+ if (result.data && result.data.length > 0) {
52
+ output(result.data);
53
+ } else {
54
+ output({ message: '暂无记录' });
55
+ }
29
56
  } catch (error: any) {
30
- console.error('查询记录失败:', error.message);
57
+ loading.fail('加载记录列表失败');
58
+ outputError(error.message);
31
59
  process.exit(1);
32
60
  }
33
61
  },
34
62
  };
35
-
36
- // 兼容旧导出
37
- export const createRecord = recordCommands.create;
38
- export const listRecords = recordCommands.list;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * biz task 命令
3
+ * 系统任务管理(对标系统任务)
4
+ * 子命令:list / create
5
+ */
6
+
7
+ import { apiService } from '../../services/api';
8
+ import { output, outputError, createLoading, isDryRun, dryRunLog } from '../../utils/output';
9
+
10
+ export const taskCommands = {
11
+ /**
12
+ * 列出任务
13
+ * biz task list --status running --source openclaw
14
+ */
15
+ list: async (options: { status?: string; source?: string } = {}) => {
16
+ const loading = createLoading('加载任务列表...');
17
+
18
+ try {
19
+ if (isDryRun()) {
20
+ loading.stop();
21
+ dryRunLog('列出任务', { status: options.status, source: options.source });
22
+ return;
23
+ }
24
+
25
+ loading.start();
26
+ const result = await apiService.listTasks(options.status, options.source);
27
+ loading.succeed('任务列表加载成功');
28
+
29
+ if (result.data && result.data.length > 0) {
30
+ output(result.data);
31
+ } else {
32
+ output({ message: '暂无任务' });
33
+ }
34
+ } catch (error: any) {
35
+ loading.fail('加载任务列表失败');
36
+ outputError(error.message);
37
+ process.exit(1);
38
+ }
39
+ },
40
+
41
+ /**
42
+ * 创建任务
43
+ * biz task create <title> --source openclaw --openclaw-id <id>
44
+ */
45
+ create: async (title: string, options: { source?: string; openclawId?: string; priority?: string } = {}) => {
46
+ const loading = createLoading('创建任务...');
47
+
48
+ try {
49
+ if (isDryRun()) {
50
+ loading.stop();
51
+ dryRunLog('创建任务', { title, ...options });
52
+ return;
53
+ }
54
+
55
+ loading.start();
56
+ const result = await apiService.createTask(title, options.source, options.openclawId, options.priority);
57
+ loading.succeed('任务创建成功');
58
+ output(result);
59
+ } catch (error: any) {
60
+ loading.fail('创建任务失败');
61
+ outputError(error.message);
62
+ process.exit(1);
63
+ }
64
+ },
65
+
66
+ /**
67
+ * 更新任务状态
68
+ * biz task update <id> --status running
69
+ */
70
+ update: async (id: number, options: { status?: string; priority?: string } = {}) => {
71
+ const loading = createLoading('更新任务...');
72
+
73
+ try {
74
+ if (isDryRun()) {
75
+ loading.stop();
76
+ dryRunLog('更新任务', { id, ...options });
77
+ return;
78
+ }
79
+
80
+ loading.start();
81
+ const result = await apiService.updateTask(id, options);
82
+ loading.succeed('任务更新成功');
83
+ output(result);
84
+ } catch (error: any) {
85
+ loading.fail('更新任务失败');
86
+ outputError(error.message);
87
+ process.exit(1);
88
+ }
89
+ },
90
+
91
+ /**
92
+ * 删除任务
93
+ * biz task delete <id>
94
+ */
95
+ delete: async (id: number) => {
96
+ const loading = createLoading('删除任务...');
97
+
98
+ try {
99
+ if (isDryRun()) {
100
+ loading.stop();
101
+ dryRunLog('删除任务', { id });
102
+ return;
103
+ }
104
+
105
+ loading.start();
106
+ const result = await apiService.deleteTask(id);
107
+ loading.succeed('任务删除成功');
108
+ output(result);
109
+ } catch (error: any) {
110
+ loading.fail('删除任务失败');
111
+ outputError(error.message);
112
+ process.exit(1);
113
+ }
114
+ },
115
+ };
@@ -1,74 +1,246 @@
1
1
  /**
2
2
  * biz todo 命令
3
- * 待办相关命令:create / list
3
+ * 待办管理(对标滴答清单)
4
+ * 子命令:create / list / done / delete / edit / repeat / subtask / subtasks
4
5
  */
5
6
 
6
7
  import { apiService } from '../../services/api';
8
+ import { output, outputError, createLoading, isDryRun, dryRunLog } from '../../utils/output';
7
9
 
8
10
  export interface TodoItem {
9
11
  id: string;
10
12
  title: string;
11
13
  completed: boolean;
12
14
  createdAt: string;
15
+ startDate?: string;
16
+ tags?: string[];
17
+ estimatedDuration?: number;
18
+ repeatRule?: 'daily' | 'weekly' | 'monthly';
19
+ repeatEndDate?: string;
20
+ parentId?: string;
13
21
  }
14
22
 
15
23
  export const todoCommands = {
16
- create: async (title: string, options: { due?: string; priority?: string } = {}) => {
24
+ /**
25
+ * 创建待办
26
+ * biz todo add <title> --due <date> --priority <p> --tags <tags> --repeat daily --end-date <date> --parent-id <id>
27
+ */
28
+ create: async (title: string, options: { due?: string; priority?: string; tags?: string; repeat?: string; endDate?: string; parentId?: string } = {}) => {
29
+ const loading = createLoading('创建待办...');
30
+
17
31
  try {
18
- const result = await apiService.createTodo(title, options.due, options.priority);
19
- console.log(JSON.stringify(result, null, 2));
32
+ if (isDryRun()) {
33
+ loading.stop();
34
+ dryRunLog('创建待办', { title, ...options });
35
+ return;
36
+ }
37
+
38
+ loading.start();
39
+ const result = await apiService.createTodo(title, options.due, options.priority, options.tags, options.repeat, options.endDate, options.parentId);
40
+ loading.succeed('待办创建成功');
41
+ output(result);
20
42
  } catch (error: any) {
21
- console.error('创建待办失败:', error.message);
43
+ loading.fail('创建待办失败');
44
+ outputError(error.message);
22
45
  process.exit(1);
23
46
  }
24
47
  },
25
48
 
26
- list: async (options: { status?: string } = {}) => {
49
+ /**
50
+ * 列出待办
51
+ * biz todo list --status pending --priority high --tags work,important
52
+ */
53
+ list: async (options: { status?: string; priority?: string; tags?: string } = {}) => {
54
+ const loading = createLoading('加载待办列表...');
55
+
27
56
  try {
28
- const result = await apiService.listTodos(options.status);
29
- console.log(JSON.stringify(result, null, 2));
57
+ if (isDryRun()) {
58
+ loading.stop();
59
+ dryRunLog('列出待办', options);
60
+ return;
61
+ }
62
+
63
+ loading.start();
64
+ const result = await apiService.listTodos(options.status, options.priority, options.tags);
65
+ loading.succeed('待办列表加载成功');
66
+
67
+ if (result.data && result.data.length > 0) {
68
+ output(result.data);
69
+ } else {
70
+ output({ message: '暂无待办' });
71
+ }
30
72
  } catch (error: any) {
31
- console.error('查询待办失败:', error.message);
73
+ loading.fail('加载待办列表失败');
74
+ outputError(error.message);
32
75
  process.exit(1);
33
76
  }
34
77
  },
35
78
 
36
- update: async (id: number, options: { title?: string; status?: string; priority?: string; due?: string } = {}) => {
79
+ /**
80
+ * 编辑待办
81
+ * biz todo edit <id> --title <new> --priority high --tags work
82
+ */
83
+ update: async (id: number, options: { title?: string; status?: string; priority?: string; due?: string; tags?: string } = {}) => {
84
+ const loading = createLoading('更新待办...');
85
+
37
86
  try {
38
- const updates: Partial<{title: string; status: string; priority: string; dueDate: string}> = {};
87
+ if (isDryRun()) {
88
+ loading.stop();
89
+ dryRunLog('更新待办', { id, ...options });
90
+ return;
91
+ }
92
+
93
+ loading.start();
94
+ const updates: Partial<{title: string; status: string; priority: string; dueDate: string; tags: string}> = {};
39
95
  if (options.title) updates.title = options.title;
40
96
  if (options.status) updates.status = options.status;
41
97
  if (options.priority) updates.priority = options.priority;
42
98
  if (options.due) updates.dueDate = options.due;
99
+ if (options.tags) updates.tags = options.tags;
100
+
43
101
  const result = await apiService.updateTodo(id, updates);
44
- console.log(JSON.stringify(result, null, 2));
102
+ loading.succeed('待办更新成功');
103
+ output(result);
45
104
  } catch (error: any) {
46
- console.error('更新待办失败:', error.message);
105
+ loading.fail('更新待办失败');
106
+ outputError(error.message);
47
107
  process.exit(1);
48
108
  }
49
109
  },
50
110
 
111
+ /**
112
+ * 标记待办完成
113
+ * biz todo done <id>
114
+ */
51
115
  done: async (id: number) => {
116
+ const loading = createLoading('标记待办完成...');
117
+
52
118
  try {
119
+ if (isDryRun()) {
120
+ loading.stop();
121
+ dryRunLog('标记待办完成', { id });
122
+ return;
123
+ }
124
+
125
+ loading.start();
53
126
  const result = await apiService.updateTodo(id, { status: 'done' });
54
- console.log(JSON.stringify(result, null, 2));
127
+ loading.succeed('待办已完成');
128
+ output(result);
55
129
  } catch (error: any) {
56
- console.error('标记待办完成失败:', error.message);
130
+ loading.fail('标记待办完成失败');
131
+ outputError(error.message);
57
132
  process.exit(1);
58
133
  }
59
134
  },
60
135
 
136
+ /**
137
+ * 删除待办
138
+ * biz todo delete <id>
139
+ */
61
140
  delete: async (id: number) => {
141
+ const loading = createLoading('删除待办...');
142
+
62
143
  try {
144
+ if (isDryRun()) {
145
+ loading.stop();
146
+ dryRunLog('删除待办', { id });
147
+ return;
148
+ }
149
+
150
+ loading.start();
63
151
  const result = await apiService.deleteTodo(id);
64
- console.log(JSON.stringify(result, null, 2));
152
+ loading.succeed('待办已删除');
153
+ output(result);
65
154
  } catch (error: any) {
66
- console.error('删除待办失败:', error.message);
155
+ loading.fail('删除待办失败');
156
+ outputError(error.message);
67
157
  process.exit(1);
68
158
  }
69
159
  },
70
- };
71
160
 
72
- // 兼容旧导出
73
- export const createTodo = todoCommands.create;
74
- export const listTodos = todoCommands.list;
161
+ /**
162
+ * 完成待办并生成下次 occurrence
163
+ * biz todo repeat <id>
164
+ */
165
+ repeat: async (id: number) => {
166
+ const loading = createLoading('处理重复待办...');
167
+
168
+ try {
169
+ if (isDryRun()) {
170
+ loading.stop();
171
+ dryRunLog('重复待办', { id });
172
+ return;
173
+ }
174
+
175
+ loading.start();
176
+ const result = await apiService.repeatTodo(id);
177
+ loading.succeed('下次 occurrence 已生成');
178
+ output(result);
179
+ } catch (error: any) {
180
+ loading.fail('处理重复待办失败');
181
+ outputError(error.message);
182
+ process.exit(1);
183
+ }
184
+ },
185
+
186
+ /**
187
+ * 创建子待办
188
+ * biz todo subtask <parent-id> --title <subtask title>
189
+ */
190
+ subtask: async (parentId: number, options: { title?: string } = {}) => {
191
+ const loading = createLoading('创建子待办...');
192
+
193
+ try {
194
+ if (!options.title) {
195
+ loading.fail('缺少必需参数 --title');
196
+ outputError('Usage: biz todo subtask <parent-id> --title <subtask title>');
197
+ process.exit(1);
198
+ }
199
+
200
+ if (isDryRun()) {
201
+ loading.stop();
202
+ dryRunLog('创建子待办', { parentId, ...options });
203
+ return;
204
+ }
205
+
206
+ loading.start();
207
+ const result = await apiService.createTodo(options.title, undefined, undefined, undefined, undefined, undefined, String(parentId));
208
+ loading.succeed('子待办创建成功');
209
+ output(result);
210
+ } catch (error: any) {
211
+ loading.fail('创建子待办失败');
212
+ outputError(error.message);
213
+ process.exit(1);
214
+ }
215
+ },
216
+
217
+ /**
218
+ * 列出子待办
219
+ * biz todo subtasks <parent-id>
220
+ */
221
+ subtasks: async (parentId: number) => {
222
+ const loading = createLoading('加载子待办列表...');
223
+
224
+ try {
225
+ if (isDryRun()) {
226
+ loading.stop();
227
+ dryRunLog('列出子待办', { parentId });
228
+ return;
229
+ }
230
+
231
+ loading.start();
232
+ const result = await apiService.listSubtasks(parentId);
233
+ loading.succeed('子待办列表加载成功');
234
+
235
+ if (result.data && result.data.length > 0) {
236
+ output(result.data);
237
+ } else {
238
+ output({ message: '暂无子待办' });
239
+ }
240
+ } catch (error: any) {
241
+ loading.fail('加载子待办列表失败');
242
+ outputError(error.message);
243
+ process.exit(1);
244
+ }
245
+ },
246
+ };