@cacinie/cace-timer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,333 @@
1
+ # CACE TIMER
2
+
3
+ 一个极简的时间记录 CLI 工具,配有可爱的短发小女孩动画吉祥物。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ cd timekeeper
9
+ npm link
10
+ ```
11
+
12
+ 安装后全局命令 `tk` 即可使用。
13
+
14
+ ## 快速开始
15
+
16
+ ```bash
17
+ # 开始一个任务
18
+ tk start "写周报" --tag work --estimate 30
19
+
20
+ # 记录进度节点
21
+ tk mark "完成数据分析"
22
+
23
+ # 查看当前状态
24
+ tk status
25
+
26
+ # 结束任务(显示效率评分)
27
+ tk stop
28
+ ```
29
+
30
+ ## 命令一览
31
+
32
+ ### tk start
33
+
34
+ 开始新任务。
35
+
36
+ ```bash
37
+ tk start <任务名> [选项]
38
+
39
+ 选项:
40
+ --tag <标签> 添加标签
41
+ --estimate <分钟> 预估时长(用于计算效率分)
42
+ ```
43
+
44
+ 示例:
45
+ ```bash
46
+ tk start "开发登录功能" --tag coding --estimate 60
47
+ ```
48
+
49
+ ### tk mark
50
+
51
+ 记录时间节点。
52
+
53
+ ```bash
54
+ tk mark <备注>
55
+ ```
56
+
57
+ 示例:
58
+ ```bash
59
+ tk mark "完成API接口"
60
+ tk mark "开始写测试"
61
+ ```
62
+
63
+ ### tk stop
64
+
65
+ 结束当前任务,显示总结和效率评分。
66
+
67
+ ```bash
68
+ tk stop
69
+ ```
70
+
71
+ 效率评分规则:
72
+ - 实际时长 ≤ 预估时长 → 100%(🏆 优秀)
73
+ - 实际时长稍超预估 → 80-99%(💪 良好)
74
+ - 实际时长远超预估 → <50%(💀 需改进)
75
+
76
+ ### tk status
77
+
78
+ 查看当前进行中的任务状态。
79
+
80
+ ```bash
81
+ tk status
82
+ ```
83
+
84
+ ### tk list
85
+
86
+ 查看历史记录。
87
+
88
+ ```bash
89
+ tk list [选项]
90
+
91
+ 选项:
92
+ --today 仅显示今天
93
+ --tag <标签> 按标签筛选
94
+ --limit <数量> 限制显示数量(默认10)
95
+ ```
96
+
97
+ 示例:
98
+ ```bash
99
+ tk list --today
100
+ tk list --tag coding --limit 20
101
+ ```
102
+
103
+ ### tk search
104
+
105
+ 搜索历史记录。
106
+
107
+ ```bash
108
+ tk search <关键词>
109
+ ```
110
+
111
+ 会搜索任务名、标签、标记备注。
112
+
113
+ ```bash
114
+ tk search "登录"
115
+ tk search "coding"
116
+ ```
117
+
118
+ ### tk sync
119
+
120
+ 设置同步文件路径,实现跨设备同步。
121
+
122
+ ```bash
123
+ tk sync <文件路径>
124
+ ```
125
+
126
+ 建议将同步文件放在云盘目录:
127
+ ```bash
128
+ tk sync ~/Dropbox/cace-timer.json
129
+ tk sync ~/OneDrive/cace-timer.json
130
+ ```
131
+
132
+ ## 数据同步指南
133
+
134
+ ### 工作原理
135
+
136
+ CACE TIMER 采用**单文件同步**策略:
137
+
138
+ ```
139
+ ┌─────────────┐ ┌─────────────┐
140
+ │ 设备 A │ │ 设备 B │
141
+ │ ~/(本地) │ │ ~/(本地) │
142
+ │ .cace-timer │ │ .cace-timer │
143
+ │ .json │ │ .json │
144
+ └──────┬──────┘ └──────┬──────┘
145
+ │ │
146
+ ▼ ▼
147
+ ┌─────────────────────────────────────┐
148
+ │ 云盘同步目录 │
149
+ │ Dropbox / iCloud / OneDrive │
150
+ │ cace-timer.json │
151
+ └─────────────────────────────────────┘
152
+ ```
153
+
154
+ 每次执行 `tk` 命令时:
155
+ 1. 写入本地 `~/.cace-timer.json`
156
+ 2. 同时写入配置的 `syncPath`(如果设置了)
157
+
158
+ ### 设置步骤
159
+
160
+ #### Dropbox
161
+
162
+ ```bash
163
+ # macOS / Linux
164
+ tk sync ~/Dropbox/cace-timer.json
165
+
166
+ # Windows
167
+ tk sync "%USERPROFILE%\Dropbox\cace-timer.json"
168
+ ```
169
+
170
+ #### iCloud
171
+
172
+ ```bash
173
+ # macOS
174
+ tk sync ~/Library/Mobile\ Documents/com~apple~CloudDocs/cace-timer.json
175
+ ```
176
+
177
+ #### OneDrive
178
+
179
+ ```bash
180
+ # macOS
181
+ tk sync ~/OneDrive/cace-timer.json
182
+
183
+ # Windows
184
+ tk sync "%USERPROFILE%\OneDrive\cace-timer.json"
185
+ ```
186
+
187
+ #### 自建同步 (Syncthing / Resilio)
188
+
189
+ ```bash
190
+ tk sync ~/Sync/cace-timer.json
191
+ ```
192
+
193
+ ### 多设备使用
194
+
195
+ **首次在设备 B 使用:**
196
+
197
+ ```bash
198
+ # 1. 安装 CACE TIMER
199
+ cd timekeeper && npm link
200
+
201
+ # 2. 设置同步路径(指向云盘已同步的文件)
202
+ tk sync ~/Dropbox/cace-timer.json
203
+
204
+ # 3. 查看同步的数据
205
+ tk list
206
+ ```
207
+
208
+ **注意:** 新设备需要先设置 `tk sync`,然后才能读取云盘数据。
209
+
210
+ ### 同步冲突处理
211
+
212
+ 由于采用单文件策略,建议:
213
+
214
+ | 场景 | 建议 |
215
+ |------|------|
216
+ | 多设备同时使用 | 避免同时在多个设备操作 |
217
+ | 云盘同步延迟 | 操作后等待几秒再切换设备 |
218
+ | 冲突文件 | 手动合并 JSON,保留需要的记录 |
219
+
220
+ ### 手动备份
221
+
222
+ ```bash
223
+ # 导出备份
224
+ cp ~/.cace-timer.json ~/backup/cace-timer-$(date +%Y%m%d).json
225
+
226
+ # 恢复备份
227
+ cp ~/backup/cace-timer-20240302.json ~/.cace-timer.json
228
+ ```
229
+
230
+ ### 数据格式
231
+
232
+ 同步文件为标准 JSON 格式:
233
+
234
+ ```json
235
+ {
236
+ "syncPath": "/Users/you/Dropbox/cace-timer.json",
237
+ "current": null,
238
+ "history": [
239
+ {
240
+ "id": "lq3x9k2",
241
+ "task": "开发功能",
242
+ "start": "2024-03-02T10:00:00.000Z",
243
+ "end": "2024-03-02T11:30:00.000Z",
244
+ "tags": ["coding"],
245
+ "marks": [
246
+ { "time": "2024-03-02T10:30:00.000Z", "note": "完成API" }
247
+ ],
248
+ "estimatedMinutes": 60
249
+ }
250
+ ]
251
+ }
252
+ ```
253
+
254
+ ### 取消同步
255
+
256
+ 编辑 `~/.cace-timer.json`,删除 `syncPath` 字段即可。
257
+
258
+ ---
259
+
260
+ ### tk help
261
+
262
+ 显示帮助信息。
263
+
264
+ ```bash
265
+ tk help
266
+ ```
267
+
268
+ ## CACE 吉祥物
269
+
270
+ CACE 是一个可爱的短发小女孩头像:
271
+
272
+ ```
273
+ ▄▄▄▄▄▄▄▄▄▄▄
274
+ █░░░░░░░░░░░█ ← 刘海
275
+ █░▄▄▄▄▄▄▄▄▄░█
276
+ █░│ ● ● │░█ ← 大眼睛
277
+ █░│ ▽ │░█ ← 小鼻子
278
+ █░│ ─── │░█ ← 嘴巴
279
+ ╰───────────╯
280
+ ```
281
+
282
+ **动画表情:**
283
+
284
+ | 状态 | 眼睛 | 嘴巴 | 触发场景 |
285
+ |------|------|------|----------|
286
+ | 正常 | ● ● | ─── | 默认状态 |
287
+ | 开心 | ★ ★ | ◡◡◡ | 完成任务 |
288
+ | 眨眼 | ─ ─ | ─── | 动画帧 |
289
+ | 超开心 | ◉ ◉ | ▽△▽ | 动画帧 |
290
+ | 困倦 | ─ ─ | ─── | 无任务时 |
291
+
292
+ 动画循环:正常 → 眨眼 → 开心 → 超开心(循环2次)
293
+
294
+ ## 数据存储
295
+
296
+ 数据保存在 `~/.cace-timer.json`:
297
+
298
+ ```json
299
+ {
300
+ "syncPath": "~/Dropbox/cace-timer.json",
301
+ "current": null,
302
+ "history": [
303
+ {
304
+ "id": "xxx",
305
+ "task": "写周报",
306
+ "start": "2024-03-02T10:00:00Z",
307
+ "end": "2024-03-02T10:30:00Z",
308
+ "tags": ["work"],
309
+ "marks": [
310
+ { "time": "2024-03-02T10:15:00Z", "note": "完成数据分析" }
311
+ ],
312
+ "estimatedMinutes": 30
313
+ }
314
+ ]
315
+ }
316
+ ```
317
+
318
+ ## 使用场景
319
+
320
+ - **编程开发**:记录编码时间,评估效率
321
+ - **学习复习**:跟踪学习时长和进度
322
+ - **项目管理**:标记关键节点,回顾时间分配
323
+ - **自由职业**:为客户项目计时
324
+
325
+ ## 卸载
326
+
327
+ ```bash
328
+ npm unlink -g cace-timer
329
+ ```
330
+
331
+ ## License
332
+
333
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,521 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ // ============ CACE TIMER Animation ============
41
+ // 可爱短发小女孩头像
42
+ const CACE_FRAMES = [
43
+ // Frame 1: 正常
44
+ `
45
+ ▄▄▄▄▄▄▄▄▄▄▄▄
46
+ █░░░░░░░░░░░░█
47
+ █░▄▄▄▄▄▄▄▄▄░█
48
+ █░│ ● ● │░█
49
+ █░│ ▽ │░█
50
+ █░│ ─── │░█
51
+ ╰────────────╯
52
+ `,
53
+ // Frame 2: 眨眼
54
+ `
55
+ ▄▄▄▄▄▄▄▄▄▄▄▄
56
+ █░░░░░░░░░░░░█
57
+ █░▄▄▄▄▄▄▄▄▄░█
58
+ █░│ ─ ─ │░█
59
+ █░│ ▽ │░█
60
+ █░│ ─── │░█
61
+ ╰────────────╯
62
+ `,
63
+ // Frame 3: 开心
64
+ `
65
+ ▄▄▄▄▄▄▄▄▄▄▄▄
66
+ █░░░░░░░░░░░░█
67
+ █░▄▄▄▄▄▄▄▄▄░█
68
+ █░│ ★ ★ │░█
69
+ █░│ ▽ │░█
70
+ █░│ ◡◡◡ │░█
71
+ ╰────────────╯
72
+ `,
73
+ // Frame 4: 超开心
74
+ `
75
+ ▄▄▄▄▄▄▄▄▄▄▄▄
76
+ █░░░░░░░░░░░░█
77
+ █░▄▄▄▄▄▄▄▄▄░█
78
+ █░│ ◉ ◉ │░█
79
+ █░│ ▽ │░█
80
+ █░│ ▽△▽ │░█
81
+ ╰────────────╯
82
+ `,
83
+ ];
84
+ const CACE_SMALL = `
85
+ ▄▄▄▄▄▄▄▄▄▄
86
+ █░░░░░░░░░░█
87
+ █░▄▄▄▄▄▄▄░█
88
+ █│ ● ● │
89
+ █│ ▽ │
90
+ █│ ─── │
91
+ ╰─────────╯`;
92
+ const CACE_HAPPY = `
93
+ ▄▄▄▄▄▄▄▄▄▄
94
+ █░░░░░░░░░░█
95
+ █░▄▄▄▄▄▄▄░█
96
+ █│ ★ ★ │
97
+ █│ ▽ │
98
+ █│ ◡◡◡ │
99
+ ╰─────────╯`;
100
+ const CACE_SLEEPY = `
101
+ ▄▄▄▄▄▄▄▄▄▄
102
+ █░░░░░░░░░░█
103
+ █░▄▄▄▄▄▄▄░█
104
+ █│ ─ ─ │
105
+ █│ ▽ │
106
+ █│ ─── │
107
+ ╰─────────╯`;
108
+ function getGreeting() {
109
+ const hour = new Date().getHours();
110
+ if (hour < 6)
111
+ return '夜深了';
112
+ if (hour < 12)
113
+ return '早上好';
114
+ if (hour < 18)
115
+ return '下午好';
116
+ return '晚上好';
117
+ }
118
+ async function showCaceAnimation(message = '') {
119
+ const cyan = '\x1b[36m';
120
+ const bold = '\x1b[1m';
121
+ const reset = '\x1b[0m';
122
+ // Animation loop
123
+ for (let i = 0; i < 2; i++) {
124
+ for (const frame of CACE_FRAMES) {
125
+ console.clear();
126
+ console.log(cyan + frame + reset);
127
+ console.log();
128
+ console.log(cyan + bold + ' ════════════════════════════════' + reset);
129
+ console.log(cyan + bold + ' C A C E T I M E R' + reset);
130
+ console.log(cyan + bold + ' ════════════════════════════════' + reset);
131
+ if (message) {
132
+ console.log();
133
+ console.log(' ' + message);
134
+ }
135
+ await sleep(180);
136
+ }
137
+ }
138
+ }
139
+ function showCaceSmall(status = '', mood = 'normal') {
140
+ const cyan = '\x1b[36m';
141
+ const reset = '\x1b[0m';
142
+ let face = CACE_SMALL;
143
+ if (mood === 'happy')
144
+ face = CACE_HAPPY;
145
+ else if (mood === 'sleepy')
146
+ face = CACE_SLEEPY;
147
+ console.log(cyan + face + reset);
148
+ if (status) {
149
+ console.log(cyan + ' ' + status + reset);
150
+ }
151
+ }
152
+ // ============ Utilities ============
153
+ function sleep(ms) {
154
+ return new Promise(resolve => setTimeout(resolve, ms));
155
+ }
156
+ function generateId() {
157
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
158
+ }
159
+ function formatDuration(ms) {
160
+ const seconds = Math.floor(ms / 1000);
161
+ const minutes = Math.floor(seconds / 60);
162
+ const hours = Math.floor(minutes / 60);
163
+ if (hours > 0) {
164
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
165
+ }
166
+ else if (minutes > 0) {
167
+ return `${minutes}m ${seconds % 60}s`;
168
+ }
169
+ else {
170
+ return `${seconds}s`;
171
+ }
172
+ }
173
+ function formatTime(isoString) {
174
+ const date = new Date(isoString);
175
+ return date.toLocaleString('zh-CN', {
176
+ month: '2-digit',
177
+ day: '2-digit',
178
+ hour: '2-digit',
179
+ minute: '2-digit'
180
+ });
181
+ }
182
+ function formatDate(isoString) {
183
+ const date = new Date(isoString);
184
+ return date.toLocaleDateString('zh-CN');
185
+ }
186
+ // ============ Data Management ============
187
+ const DATA_FILE = path.join(os.homedir(), '.cace-timer.json');
188
+ function loadData() {
189
+ try {
190
+ if (fs.existsSync(DATA_FILE)) {
191
+ const content = fs.readFileSync(DATA_FILE, 'utf-8');
192
+ return JSON.parse(content);
193
+ }
194
+ }
195
+ catch (e) {
196
+ // Ignore errors
197
+ }
198
+ return { current: null, history: [] };
199
+ }
200
+ function saveData(data) {
201
+ fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
202
+ // Sync to external path if set
203
+ if (data.syncPath && fs.existsSync(path.dirname(data.syncPath))) {
204
+ fs.writeFileSync(data.syncPath, JSON.stringify(data, null, 2));
205
+ }
206
+ }
207
+ // ============ Commands ============
208
+ async function cmdStart(task, options) {
209
+ const data = loadData();
210
+ if (data.current) {
211
+ console.log('\x1b[33m⚠ 已有进行中的任务,请先使用 tk stop 结束\x1b[0m');
212
+ return;
213
+ }
214
+ const session = {
215
+ id: generateId(),
216
+ start: new Date().toISOString(),
217
+ task: task || '未命名任务',
218
+ tags: options.tag ? [options.tag] : [],
219
+ marks: [],
220
+ estimatedMinutes: options.estimate
221
+ };
222
+ data.current = session;
223
+ saveData(data);
224
+ await showCaceAnimation(`${getGreeting()}!开始记录: ${session.task}`);
225
+ console.log();
226
+ console.log(` 📌 任务: ${session.task}`);
227
+ if (session.tags.length > 0) {
228
+ console.log(` 🏷 标签: ${session.tags.join(', ')}`);
229
+ }
230
+ if (session.estimatedMinutes) {
231
+ console.log(` ⏱ 预计: ${session.estimatedMinutes} 分钟`);
232
+ }
233
+ console.log(` 🕐 开始: ${formatTime(session.start)}`);
234
+ console.log();
235
+ }
236
+ async function cmdMark(note) {
237
+ const data = loadData();
238
+ if (!data.current) {
239
+ console.log('\x1b[33m⚠ 没有进行中的任务,请先用 tk start 开始\x1b[0m');
240
+ return;
241
+ }
242
+ const mark = {
243
+ time: new Date().toISOString(),
244
+ note: note || '标记点'
245
+ };
246
+ data.current.marks.push(mark);
247
+ saveData(data);
248
+ const elapsed = Date.now() - new Date(data.current.start).getTime();
249
+ console.log();
250
+ showCaceSmall('✓ 已标记', 'happy');
251
+ console.log();
252
+ console.log(` 📍 ${formatTime(mark.time)} - ${mark.note}`);
253
+ console.log(` ⏱ 已用时: ${formatDuration(elapsed)}`);
254
+ console.log();
255
+ }
256
+ async function cmdStop() {
257
+ const data = loadData();
258
+ if (!data.current) {
259
+ console.log('\x1b[33m⚠ 没有进行中的任务\x1b[0m');
260
+ return;
261
+ }
262
+ const session = data.current;
263
+ session.end = new Date().toISOString();
264
+ const duration = new Date(session.end).getTime() - new Date(session.start).getTime();
265
+ const durationMinutes = duration / 60000;
266
+ // Calculate efficiency score
267
+ let efficiency = 100;
268
+ if (session.estimatedMinutes && session.estimatedMinutes > 0) {
269
+ efficiency = Math.min(100, Math.round((session.estimatedMinutes / durationMinutes) * 100));
270
+ }
271
+ data.history.unshift(session);
272
+ data.current = null;
273
+ saveData(data);
274
+ console.log();
275
+ showCaceSmall('任务完成!', 'happy');
276
+ console.log();
277
+ console.log(' ┌─────────────────────────────────┐');
278
+ console.log(' │ 📊 任务总结 │');
279
+ console.log(' └─────────────────────────────────┘');
280
+ console.log();
281
+ console.log(` 📌 任务: ${session.task}`);
282
+ console.log(` 🕐 时长: ${formatDuration(duration)}`);
283
+ if (session.tags.length > 0) {
284
+ console.log(` 🏷 标签: ${session.tags.join(', ')}`);
285
+ }
286
+ if (session.marks.length > 0) {
287
+ console.log(` 📍 标记: ${session.marks.length} 个`);
288
+ }
289
+ // Efficiency display
290
+ let effEmoji = '⭐';
291
+ let effColor = '\x1b[32m';
292
+ if (efficiency < 50) {
293
+ effEmoji = '💀';
294
+ effColor = '\x1b[31m';
295
+ }
296
+ else if (efficiency < 80) {
297
+ effEmoji = '💪';
298
+ effColor = '\x1b[33m';
299
+ }
300
+ else if (efficiency >= 100) {
301
+ effEmoji = '🏆';
302
+ }
303
+ console.log(` ${effEmoji} 效率分: ${effColor}${efficiency}%\x1b[0m`);
304
+ console.log();
305
+ }
306
+ function cmdStatus() {
307
+ const data = loadData();
308
+ console.log();
309
+ showCaceSmall('', data.current ? 'normal' : 'sleepy');
310
+ if (!data.current) {
311
+ console.log();
312
+ console.log(' 😴 当前没有进行中的任务');
313
+ console.log(' 使用 tk start "任务名" 开始新任务');
314
+ console.log();
315
+ return;
316
+ }
317
+ const elapsed = Date.now() - new Date(data.current.start).getTime();
318
+ console.log();
319
+ console.log(' 🔥 进行中');
320
+ console.log();
321
+ console.log(` 📌 任务: ${data.current.task}`);
322
+ console.log(` 🕐 开始: ${formatTime(data.current.start)}`);
323
+ console.log(` ⏱ 已用: ${formatDuration(elapsed)}`);
324
+ if (data.current.tags.length > 0) {
325
+ console.log(` 🏷 标签: ${data.current.tags.join(', ')}`);
326
+ }
327
+ if (data.current.marks.length > 0) {
328
+ console.log(` 📍 标记: ${data.current.marks.length} 个`);
329
+ data.current.marks.forEach((m, i) => {
330
+ console.log(` ${i + 1}. ${formatTime(m.time)} - ${m.note}`);
331
+ });
332
+ }
333
+ console.log();
334
+ }
335
+ function cmdList(options) {
336
+ const data = loadData();
337
+ let sessions = data.history;
338
+ // Filter by today
339
+ if (options.today) {
340
+ const today = formatDate(new Date().toISOString());
341
+ sessions = sessions.filter(s => formatDate(s.start) === today);
342
+ }
343
+ // Filter by tag
344
+ if (options.tag) {
345
+ sessions = sessions.filter(s => s.tags.includes(options.tag));
346
+ }
347
+ // Limit
348
+ const limit = options.limit || 10;
349
+ sessions = sessions.slice(0, limit);
350
+ console.log();
351
+ showCaceSmall('历史记录');
352
+ console.log();
353
+ if (sessions.length === 0) {
354
+ console.log(' 📭 暂无记录');
355
+ console.log();
356
+ return;
357
+ }
358
+ sessions.forEach((session, i) => {
359
+ const duration = session.end
360
+ ? formatDuration(new Date(session.end).getTime() - new Date(session.start).getTime())
361
+ : '进行中';
362
+ console.log(` ${i + 1}. ${session.task}`);
363
+ console.log(` 📅 ${formatTime(session.start)} | ⏱ ${duration}`);
364
+ if (session.tags.length > 0) {
365
+ console.log(` 🏷 ${session.tags.map(t => '#' + t).join(' ')}`);
366
+ }
367
+ console.log();
368
+ });
369
+ }
370
+ function cmdSearch(keyword) {
371
+ const data = loadData();
372
+ const results = data.history.filter(s => s.task.toLowerCase().includes(keyword.toLowerCase()) ||
373
+ s.tags.some(t => t.toLowerCase().includes(keyword.toLowerCase())) ||
374
+ s.marks.some(m => m.note.toLowerCase().includes(keyword.toLowerCase())));
375
+ console.log();
376
+ showCaceSmall(`搜索: "${keyword}"`);
377
+ console.log();
378
+ if (results.length === 0) {
379
+ console.log(' 🔍 未找到匹配记录');
380
+ console.log();
381
+ return;
382
+ }
383
+ console.log(` 找到 ${results.length} 条记录:\n`);
384
+ results.forEach((session, i) => {
385
+ const duration = session.end
386
+ ? formatDuration(new Date(session.end).getTime() - new Date(session.start).getTime())
387
+ : '进行中';
388
+ console.log(` ${i + 1}. ${session.task}`);
389
+ console.log(` 📅 ${formatTime(session.start)} | ⏱ ${duration}`);
390
+ console.log();
391
+ });
392
+ }
393
+ function cmdSync(filePath) {
394
+ const data = loadData();
395
+ const absPath = path.resolve(filePath);
396
+ data.syncPath = absPath;
397
+ saveData(data);
398
+ console.log();
399
+ showCaceSmall('同步已配置');
400
+ console.log();
401
+ console.log(` 📂 同步路径: ${absPath}`);
402
+ console.log(' 💡 提示: 可将文件放在 Dropbox/iCloud/OneDrive 等同步目录');
403
+ console.log();
404
+ }
405
+ function cmdHelp() {
406
+ console.log();
407
+ showCaceSmall('');
408
+ console.log();
409
+ console.log(' ╔═══════════════════════════════════════════╗');
410
+ console.log(' ║ CACE TIMER - 命令帮助 ║');
411
+ console.log(' ╚═══════════════════════════════════════════╝');
412
+ console.log();
413
+ console.log(' tk start <任务名> [选项]');
414
+ console.log(' 开始新任务');
415
+ console.log(' --tag <标签> 添加标签');
416
+ console.log(' --estimate <分钟> 预估时长');
417
+ console.log();
418
+ console.log(' tk mark <备注>');
419
+ console.log(' 记录时间点');
420
+ console.log();
421
+ console.log(' tk stop');
422
+ console.log(' 结束当前任务(显示效率评分)');
423
+ console.log();
424
+ console.log(' tk status');
425
+ console.log(' 查看当前状态');
426
+ console.log();
427
+ console.log(' tk list [选项]');
428
+ console.log(' 查看历史记录');
429
+ console.log(' --today 仅今天');
430
+ console.log(' --tag <标签> 按标签筛选');
431
+ console.log(' --limit <数量> 限制数量');
432
+ console.log();
433
+ console.log(' tk search <关键词>');
434
+ console.log(' 搜索记录');
435
+ console.log();
436
+ console.log(' tk sync <文件路径>');
437
+ console.log(' 设置同步文件路径');
438
+ console.log();
439
+ console.log(' tk help');
440
+ console.log(' 显示帮助');
441
+ console.log();
442
+ }
443
+ function parseArgs(args) {
444
+ const result = {
445
+ command: '',
446
+ positional: [],
447
+ options: {}
448
+ };
449
+ let i = 0;
450
+ while (i < args.length) {
451
+ const arg = args[i];
452
+ if (arg.startsWith('--')) {
453
+ const key = arg.slice(2);
454
+ const nextArg = args[i + 1];
455
+ if (nextArg && !nextArg.startsWith('-')) {
456
+ result.options[key] = nextArg;
457
+ i += 2;
458
+ }
459
+ else {
460
+ result.options[key] = true;
461
+ i++;
462
+ }
463
+ }
464
+ else if (!result.command) {
465
+ result.command = arg;
466
+ i++;
467
+ }
468
+ else {
469
+ result.positional.push(arg);
470
+ i++;
471
+ }
472
+ }
473
+ return result;
474
+ }
475
+ // ============ Main ============
476
+ async function main() {
477
+ const args = process.argv.slice(2);
478
+ const parsed = parseArgs(args);
479
+ const { command, positional, options } = parsed;
480
+ switch (command) {
481
+ case 'start':
482
+ await cmdStart(positional[0] || '', {
483
+ tag: options.tag,
484
+ estimate: options.estimate ? parseInt(options.estimate) : undefined
485
+ });
486
+ break;
487
+ case 'mark':
488
+ await cmdMark(positional[0] || '标记点');
489
+ break;
490
+ case 'stop':
491
+ await cmdStop();
492
+ break;
493
+ case 'status':
494
+ cmdStatus();
495
+ break;
496
+ case 'list':
497
+ case 'ls':
498
+ cmdList({
499
+ today: !!options.today,
500
+ tag: options.tag,
501
+ limit: options.limit ? parseInt(options.limit) : 10
502
+ });
503
+ break;
504
+ case 'search':
505
+ cmdSearch(positional[0] || '');
506
+ break;
507
+ case 'sync':
508
+ cmdSync(positional[0] || '');
509
+ break;
510
+ case 'help':
511
+ case '--help':
512
+ case '-h':
513
+ case '':
514
+ cmdHelp();
515
+ break;
516
+ default:
517
+ console.log(`\x1b[31m未知命令: ${command}\x1b[0m`);
518
+ console.log('使用 tk help 查看帮助');
519
+ }
520
+ }
521
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@cacinie/cace-timer",
3
+ "version": "1.0.0",
4
+ "description": "A minimal time tracking CLI with cute anime girl mascot",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "tk": "dist/index.js",
8
+ "cace-timer": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "ts-node src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "files": ["dist", "README.md", "LICENSE"],
17
+ "keywords": ["time", "tracker", "cli", "timer", "productivity", "anime", "mascot"],
18
+ "author": "CacinieP <cacinie@gmail.com>",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/CacinieP/cace-timer.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/CacinieP/cace-timer/issues"
26
+ },
27
+ "homepage": "https://github.com/CacinieP/cace-timer#readme",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.10.0",
33
+ "ts-node": "^10.9.2",
34
+ "typescript": "^5.3.0"
35
+ }
36
+ }