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 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,IAAI,cAAc,SAAQ,CAAC;AAClC,eAAO,IAAI,eAAe,SAAQ,CAAC;AACnC,eAAO,IAAI,gBAAgB,SAAQ,CAAC;AAEpC,wBAAgB,cAAc,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,QAIzF;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CAQzE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAIjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAInE;AAMD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAgD3D;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,IAAI,CAO7D"}
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * 全局输出格式化工具
4
+ * 支持 --json / --quiet / --dry-run 全局参数
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.globalDryRunFlag = exports.globalQuietFlag = exports.globalJsonFlag = void 0;
8
+ exports.setGlobalFlags = setGlobalFlags;
9
+ exports.output = output;
10
+ exports.outputError = outputError;
11
+ exports.outputSuccess = outputSuccess;
12
+ exports.createLoading = createLoading;
13
+ exports.isDryRun = isDryRun;
14
+ exports.dryRunLog = dryRunLog;
15
+ // 全局标志(由 index.ts 在解析全局参数后设置)
16
+ exports.globalJsonFlag = false;
17
+ exports.globalQuietFlag = false;
18
+ exports.globalDryRunFlag = false;
19
+ function setGlobalFlags(args) {
20
+ if (args.json !== undefined)
21
+ exports.globalJsonFlag = args.json;
22
+ if (args.quiet !== undefined)
23
+ exports.globalQuietFlag = args.quiet;
24
+ if (args.dryRun !== undefined)
25
+ exports.globalDryRunFlag = args.dryRun;
26
+ }
27
+ /**
28
+ * 统一输出函数
29
+ */
30
+ function output(data, options = {}) {
31
+ if (exports.globalQuietFlag || options.quiet)
32
+ return;
33
+ if (exports.globalJsonFlag) {
34
+ console.log(JSON.stringify(data, null, 2));
35
+ }
36
+ else {
37
+ console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
38
+ }
39
+ }
40
+ /**
41
+ * 错误输出
42
+ */
43
+ function outputError(message, ...args) {
44
+ if (!exports.globalQuietFlag) {
45
+ console.error(`\x1b[31m✖ ${message}\x1b[0m`, ...args);
46
+ }
47
+ }
48
+ /**
49
+ * 成功输出
50
+ */
51
+ function outputSuccess(message, ...args) {
52
+ if (!exports.globalQuietFlag) {
53
+ console.log(`\x1b[32m✔ ${message}\x1b[0m`, ...args);
54
+ }
55
+ }
56
+ // Simple loading indicator (ora ESM issue workaround)
57
+ const spinners = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
58
+ let spinnerInterval = null;
59
+ /**
60
+ * 创建 Loading 实例(简单实现,兼容 CJS)
61
+ */
62
+ function createLoading(text) {
63
+ let currentText = text;
64
+ let index = 0;
65
+ let started = false;
66
+ const instance = {
67
+ start: () => {
68
+ if (started || exports.globalQuietFlag)
69
+ return;
70
+ started = true;
71
+ process.stdout.write('\x1b[?25l'); // hide cursor
72
+ spinnerInterval = setInterval(() => {
73
+ process.stdout.write(`\r${spinners[index % spinners.length]} ${currentText}`);
74
+ index++;
75
+ }, 80);
76
+ },
77
+ succeed: (text) => {
78
+ if (spinnerInterval) {
79
+ clearInterval(spinnerInterval);
80
+ spinnerInterval = null;
81
+ }
82
+ process.stdout.write('\x1b[?25h'); // show cursor
83
+ process.stdout.write('\r' + ' '.repeat(currentText.length + 3) + '\r');
84
+ if (!exports.globalQuietFlag) {
85
+ console.log(`\x1b[32m✔ ${text || currentText}\x1b[0m`);
86
+ }
87
+ },
88
+ fail: (text) => {
89
+ if (spinnerInterval) {
90
+ clearInterval(spinnerInterval);
91
+ spinnerInterval = null;
92
+ }
93
+ process.stdout.write('\x1b[?25h'); // show cursor
94
+ process.stdout.write('\r' + ' '.repeat(currentText.length + 3) + '\r');
95
+ if (!exports.globalQuietFlag) {
96
+ console.error(`\x1b[31m✖ ${text || currentText}\x1b[0m`);
97
+ }
98
+ },
99
+ stop: () => {
100
+ if (spinnerInterval) {
101
+ clearInterval(spinnerInterval);
102
+ spinnerInterval = null;
103
+ }
104
+ process.stdout.write('\x1b[?25h'); // show cursor
105
+ process.stdout.write('\r' + ' '.repeat(currentText.length + 3) + '\r');
106
+ },
107
+ };
108
+ return instance;
109
+ }
110
+ /**
111
+ * Dry-run 检查,如果处于预览模式则输出并返回 true
112
+ */
113
+ function isDryRun() {
114
+ return exports.globalDryRunFlag;
115
+ }
116
+ function dryRunLog(action, details) {
117
+ if (exports.globalDryRunFlag) {
118
+ console.log(`\x1b[33m[DRY-RUN]\x1b[0m ${action}`);
119
+ if (details && !exports.globalQuietFlag) {
120
+ console.log(JSON.stringify(details, null, 2));
121
+ }
122
+ }
123
+ }
124
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Todo Model Utilities
3
+ * 包含重复规则计算、标签解析、时长格式化等功能
4
+ */
5
+ export type RepeatType = 'none' | 'daily' | 'weekly' | 'monthly';
6
+ export interface RepeatRule {
7
+ type: RepeatType;
8
+ interval: number;
9
+ endDate?: string;
10
+ }
11
+ /**
12
+ * 解析重复规则字符串
13
+ * 格式: "daily", "weekly", "monthly", "2d", "3w", "1m" 等
14
+ */
15
+ export declare function parseRepeatRule(rule: string): RepeatRule;
16
+ /**
17
+ * 计算下一次重复日期
18
+ */
19
+ export declare function calculateNextOccurrence(currentDate: Date, rule: RepeatRule): Date | null;
20
+ /**
21
+ * 解析标签字符串
22
+ * 格式: "work,important,urgent" -> ["work", "important", "urgent"]
23
+ */
24
+ export declare function parseTags(tagsString: string | undefined): string[];
25
+ /**
26
+ * 格式化时长
27
+ * 秒数转换为可读格式: "1h30m", "45m", "2h"
28
+ */
29
+ export declare function formatDuration(seconds: number): string;
30
+ /**
31
+ * 解析时长字符串为秒数
32
+ * 格式: "1h30m", "45m", "2h" -> 秒数
33
+ */
34
+ export declare function parseDuration(duration: string): number;
35
+ export interface TodoItem {
36
+ id: string;
37
+ title: string;
38
+ completed: boolean;
39
+ priority: 'low' | 'medium' | 'high';
40
+ dueDate?: string;
41
+ tags?: string[];
42
+ repeatRule?: RepeatRule;
43
+ estimatedDuration?: number;
44
+ createdAt: string;
45
+ completedAt?: string;
46
+ }
47
+ /**
48
+ * 序列化 Todo 为 JSON
49
+ */
50
+ export declare function serializeTodo(todo: TodoItem): string;
51
+ /**
52
+ * 反序列化 Todo
53
+ */
54
+ export declare function deserializeTodo(json: string): TodoItem | null;
55
+ //# sourceMappingURL=todo.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todo.model.d.ts","sourceRoot":"","sources":["../../src/utils/todo.model.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAiCxD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,IAAI,EACjB,IAAI,EAAE,UAAU,GACf,IAAI,GAAG,IAAI,CA4Bb;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAQlE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEpD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAM7D"}
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * Todo Model Utilities
4
+ * 包含重复规则计算、标签解析、时长格式化等功能
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.parseRepeatRule = parseRepeatRule;
8
+ exports.calculateNextOccurrence = calculateNextOccurrence;
9
+ exports.parseTags = parseTags;
10
+ exports.formatDuration = formatDuration;
11
+ exports.parseDuration = parseDuration;
12
+ exports.serializeTodo = serializeTodo;
13
+ exports.deserializeTodo = deserializeTodo;
14
+ /**
15
+ * 解析重复规则字符串
16
+ * 格式: "daily", "weekly", "monthly", "2d", "3w", "1m" 等
17
+ */
18
+ function parseRepeatRule(rule) {
19
+ if (!rule || rule === 'none') {
20
+ return { type: 'none', interval: 1 };
21
+ }
22
+ const lower = rule.toLowerCase();
23
+ if (lower === 'daily') {
24
+ return { type: 'daily', interval: 1 };
25
+ }
26
+ if (lower === 'weekly') {
27
+ return { type: 'weekly', interval: 1 };
28
+ }
29
+ if (lower === 'monthly') {
30
+ return { type: 'monthly', interval: 1 };
31
+ }
32
+ // 解析 "2d", "3w", "1m" 格式
33
+ const match = rule.match(/^(\d+)([dwm])$/i);
34
+ if (match) {
35
+ const num = parseInt(match[1], 10);
36
+ const unit = match[2].toLowerCase();
37
+ switch (unit) {
38
+ case 'd':
39
+ return { type: 'daily', interval: num };
40
+ case 'w':
41
+ return { type: 'weekly', interval: num };
42
+ case 'm':
43
+ return { type: 'monthly', interval: num };
44
+ }
45
+ }
46
+ return { type: 'none', interval: 1 };
47
+ }
48
+ /**
49
+ * 计算下一次重复日期
50
+ */
51
+ function calculateNextOccurrence(currentDate, rule) {
52
+ if (rule.type === 'none') {
53
+ return null;
54
+ }
55
+ const next = new Date(currentDate);
56
+ switch (rule.type) {
57
+ case 'daily':
58
+ next.setDate(next.getDate() + rule.interval);
59
+ break;
60
+ case 'weekly':
61
+ next.setDate(next.getDate() + 7 * rule.interval);
62
+ break;
63
+ case 'monthly':
64
+ next.setMonth(next.getMonth() + rule.interval);
65
+ break;
66
+ }
67
+ // 检查是否超过结束日期
68
+ if (rule.endDate) {
69
+ const endDate = new Date(rule.endDate);
70
+ if (next > endDate) {
71
+ return null;
72
+ }
73
+ }
74
+ return next;
75
+ }
76
+ /**
77
+ * 解析标签字符串
78
+ * 格式: "work,important,urgent" -> ["work", "important", "urgent"]
79
+ */
80
+ function parseTags(tagsString) {
81
+ if (!tagsString || tagsString.trim() === '') {
82
+ return [];
83
+ }
84
+ return tagsString
85
+ .split(',')
86
+ .map(tag => tag.trim())
87
+ .filter(tag => tag.length > 0);
88
+ }
89
+ /**
90
+ * 格式化时长
91
+ * 秒数转换为可读格式: "1h30m", "45m", "2h"
92
+ */
93
+ function formatDuration(seconds) {
94
+ if (seconds < 0) {
95
+ return '0m';
96
+ }
97
+ const hours = Math.floor(seconds / 3600);
98
+ const minutes = Math.floor((seconds % 3600) / 60);
99
+ if (hours === 0) {
100
+ return `${minutes}m`;
101
+ }
102
+ if (minutes === 0) {
103
+ return `${hours}h`;
104
+ }
105
+ return `${hours}h${minutes}m`;
106
+ }
107
+ /**
108
+ * 解析时长字符串为秒数
109
+ * 格式: "1h30m", "45m", "2h" -> 秒数
110
+ */
111
+ function parseDuration(duration) {
112
+ const hourMatch = duration.match(/(\d+)h/);
113
+ const minMatch = duration.match(/(\d+)m/);
114
+ const hours = hourMatch ? parseInt(hourMatch[1], 10) : 0;
115
+ const minutes = minMatch ? parseInt(minMatch[1], 10) : 0;
116
+ return hours * 3600 + minutes * 60;
117
+ }
118
+ /**
119
+ * 序列化 Todo 为 JSON
120
+ */
121
+ function serializeTodo(todo) {
122
+ return JSON.stringify(todo);
123
+ }
124
+ /**
125
+ * 反序列化 Todo
126
+ */
127
+ function deserializeTodo(json) {
128
+ try {
129
+ return JSON.parse(json);
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
135
+ //# sourceMappingURL=todo.model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todo.model.js","sourceRoot":"","sources":["../../src/utils/todo.model.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shawnxixi-cli",
3
- "version": "0.3.1",
3
+ "version": "1.1.0",
4
4
  "description": "肖嘻 CLI 工具 - OpenClaw 执行肢",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -27,15 +27,31 @@
27
27
  },
28
28
  "homepage": "https://github.com/LyuShawn/shawnxixi-cli#readme",
29
29
  "devDependencies": {
30
+ "@commitlint/cli": "^19.0.0",
31
+ "@commitlint/config-conventional": "^19.0.0",
30
32
  "@types/jest": "^29.5.14",
31
33
  "@types/node": "^20.11.0",
32
34
  "jest": "^29.7.0",
35
+ "semantic-release": "^24.0.0",
33
36
  "ts-jest": "^29.2.0",
34
37
  "ts-node": "^10.9.2",
35
38
  "typescript": "^5.3.3"
36
39
  },
40
+ "release": {
41
+ "branches": [
42
+ "main"
43
+ ],
44
+ "plugins": [
45
+ "@semantic-release/commit-analyzer",
46
+ "@semantic-release/release-notes-generator",
47
+ "@semantic-release/npm",
48
+ "@semantic-release/github"
49
+ ]
50
+ },
37
51
  "dependencies": {
38
52
  "axios": "^1.15.0",
39
- "commander": "^14.0.3"
53
+ "commander": "^14.0.3",
54
+ "dotenv": "^17.4.1",
55
+ "ora": "^9.3.0"
40
56
  }
41
57
  }
@@ -1,26 +1,77 @@
1
+ /**
2
+ * biz calendar 命令
3
+ * 日程管理(对标日历 App)
4
+ * 子命令:add / list
5
+ */
6
+
1
7
  import { apiService } from '../../services/api';
8
+ import { output, outputError, createLoading, isDryRun, dryRunLog } from '../../utils/output';
2
9
 
3
10
  export const calendarCommands = {
4
- create: async (title: string, options: { start?: string; end?: string; desc?: string } = {}) => {
5
- if (!options.start || !options.end) {
6
- console.error('必须指定 --start --end 时间');
7
- process.exit(1);
8
- }
11
+ /**
12
+ * 创建日程
13
+ * biz calendar add <title> --from <datetime> --to <datetime> --location <loc> --color #ff0000
14
+ */
15
+ create: async (title: string, options: { from?: string; to?: string; location?: string; color?: string; desc?: string } = {}) => {
16
+ const loading = createLoading('创建日程...');
17
+
9
18
  try {
10
- const result = await apiService.createCalendarEvent(title, options.start!, options.end!, options.desc);
11
- console.log(JSON.stringify(result, null, 2));
19
+ if (!options.from || !options.to) {
20
+ loading.fail('缺少必需参数 --from 和 --to');
21
+ outputError('Usage: biz calendar add <title> --from <datetime> --to <datetime> [--location <loc>] [--color <color>] [--desc <desc>]');
22
+ process.exit(1);
23
+ }
24
+
25
+ if (isDryRun()) {
26
+ loading.stop();
27
+ dryRunLog('创建日程', { title, ...options });
28
+ return;
29
+ }
30
+
31
+ loading.start();
32
+ const result = await apiService.createCalendarEvent(
33
+ title,
34
+ options.from,
35
+ options.to,
36
+ options.desc,
37
+ options.location,
38
+ options.color
39
+ );
40
+ loading.succeed('日程创建成功');
41
+ output(result);
12
42
  } catch (error: any) {
13
- console.error('创建日程失败:', error.message);
43
+ loading.fail('创建日程失败');
44
+ outputError(error.message);
14
45
  process.exit(1);
15
46
  }
16
47
  },
17
48
 
49
+ /**
50
+ * 列出日程
51
+ * biz calendar list --from 2026-04-01 --to 2026-04-30
52
+ */
18
53
  list: async (options: { from?: string; to?: string } = {}) => {
54
+ const loading = createLoading('加载日程列表...');
55
+
19
56
  try {
57
+ if (isDryRun()) {
58
+ loading.stop();
59
+ dryRunLog('列出日程', options);
60
+ return;
61
+ }
62
+
63
+ loading.start();
20
64
  const result = await apiService.listCalendarEvents(options.from, options.to);
21
- console.log(JSON.stringify(result, null, 2));
65
+ loading.succeed('日程列表加载成功');
66
+
67
+ if (result.data && result.data.length > 0) {
68
+ output(result.data);
69
+ } else {
70
+ output({ message: '暂无日程' });
71
+ }
22
72
  } catch (error: any) {
23
- console.error('查询日程失败:', error.message);
73
+ loading.fail('加载日程列表失败');
74
+ outputError(error.message);
24
75
  process.exit(1);
25
76
  }
26
77
  },
@@ -1,29 +1,128 @@
1
+ /**
2
+ * biz finance 命令
3
+ * 财务管理(对标记账类 App)
4
+ * 子命令:add / list / stats / report
5
+ */
6
+
1
7
  import { apiService } from '../../services/api';
8
+ import { output, outputError, createLoading, isDryRun, dryRunLog } from '../../utils/output';
2
9
 
3
10
  export const financeCommands = {
4
- create: async (amount: string, options: { type?: string; category?: string; desc?: string } = {}) => {
5
- const num = parseFloat(amount);
6
- if (isNaN(num)) {
7
- console.error('金额必须是数字:', amount);
11
+ /**
12
+ * 新增账单
13
+ * biz finance add --type expense --amount 100 --category 餐饮 --note 午饭 --account cash --date 2026-04-12 --tags food,lunch
14
+ */
15
+ create: async (amount: string, options: { type?: string; category?: string; note?: string; account?: string; date?: string; tags?: string } = {}) => {
16
+ const loading = createLoading('记录账单...');
17
+
18
+ try {
19
+ const num = parseFloat(amount);
20
+ if (isNaN(num)) {
21
+ loading.fail('金额必须是数字');
22
+ outputError('金额必须是数字:', amount);
23
+ process.exit(1);
24
+ }
25
+
26
+ if (isDryRun()) {
27
+ loading.stop();
28
+ dryRunLog('记录账单', { amount: num, ...options });
29
+ return;
30
+ }
31
+
32
+ loading.start();
33
+ const type = options.type || (num >= 0 ? 'income' : 'expense');
34
+ const category = options.category || 'other';
35
+ const account = options.account || 'cash';
36
+ const result = await apiService.createFinance(Math.abs(num), type, category, options.note, account, options.date, options.tags);
37
+ loading.succeed('账单记录成功');
38
+ output(result);
39
+ } catch (error: any) {
40
+ loading.fail('记录账单失败');
41
+ outputError(error.message);
42
+ process.exit(1);
43
+ }
44
+ },
45
+
46
+ /**
47
+ * 列出账单
48
+ * biz finance list --type expense --month 2026-04
49
+ */
50
+ list: async (options: { type?: string; category?: string; month?: string } = {}) => {
51
+ const loading = createLoading('加载账单列表...');
52
+
53
+ try {
54
+ if (isDryRun()) {
55
+ loading.stop();
56
+ dryRunLog('列出账单', options);
57
+ return;
58
+ }
59
+
60
+ loading.start();
61
+ const result = await apiService.listFinances(options.type, options.category, options.month);
62
+ loading.succeed('账单列表加载成功');
63
+
64
+ if (result.data && result.data.length > 0) {
65
+ output(result.data);
66
+ } else {
67
+ output({ message: '暂无账单记录' });
68
+ }
69
+ } catch (error: any) {
70
+ loading.fail('加载账单列表失败');
71
+ outputError(error.message);
8
72
  process.exit(1);
9
73
  }
10
- const type = options.type || (num >= 0 ? 'income' : 'expense');
11
- const category = options.category || 'other';
74
+ },
75
+
76
+ /**
77
+ * 统计账单
78
+ * biz finance stats --month 2026-04
79
+ */
80
+ stats: async (options: { month?: string } = {}) => {
81
+ const loading = createLoading('计算账单统计...');
82
+
12
83
  try {
13
- const result = await apiService.createFinance(Math.abs(num), type, category, options.desc);
14
- console.log(JSON.stringify(result, null, 2));
84
+ if (isDryRun()) {
85
+ loading.stop();
86
+ dryRunLog('账单统计', options);
87
+ return;
88
+ }
89
+
90
+ const month = options.month || new Date().toISOString().slice(0, 7); // 默认当月
91
+
92
+ loading.start();
93
+ const result = await apiService.getFinanceStats(month);
94
+ loading.succeed('统计完成');
95
+ output(result);
15
96
  } catch (error: any) {
16
- console.error('创建财务记录失败:', error.message);
97
+ loading.fail('统计失败');
98
+ outputError(error.message);
17
99
  process.exit(1);
18
100
  }
19
101
  },
20
102
 
21
- list: async (options: { type?: string; category?: string } = {}) => {
103
+ /**
104
+ * 月度报表
105
+ * biz finance report --month 2026-04
106
+ */
107
+ report: async (options: { month?: string } = {}) => {
108
+ const loading = createLoading('生成月度报表...');
109
+
22
110
  try {
23
- const result = await apiService.listFinances(options.type, options.category);
24
- console.log(JSON.stringify(result, null, 2));
111
+ if (isDryRun()) {
112
+ loading.stop();
113
+ dryRunLog('月度报表', options);
114
+ return;
115
+ }
116
+
117
+ const month = options.month || new Date().toISOString().slice(0, 7); // 默认当月
118
+
119
+ loading.start();
120
+ const result = await apiService.getFinanceReport(month);
121
+ loading.succeed('报表生成成功');
122
+ output(result);
25
123
  } catch (error: any) {
26
- console.error('查询财务记录失败:', error.message);
124
+ loading.fail('生成报表失败');
125
+ outputError(error.message);
27
126
  process.exit(1);
28
127
  }
29
128
  },