@cacinie/cace-timer 1.1.1 → 1.3.2
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/LICENSE +21 -21
- package/README.md +491 -192
- package/dist/commands/delete.d.ts +4 -0
- package/dist/commands/delete.js +48 -0
- package/dist/commands/export.d.ts +4 -0
- package/dist/commands/export.js +104 -0
- package/dist/commands/help.d.ts +1 -0
- package/dist/commands/help.js +73 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.js +42 -0
- package/dist/commands/mark.d.ts +1 -0
- package/dist/commands/mark.js +27 -0
- package/dist/commands/pomodoro.d.ts +6 -0
- package/dist/commands/pomodoro.js +135 -0
- package/dist/commands/resume.d.ts +4 -0
- package/dist/commands/resume.js +54 -0
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +36 -0
- package/dist/commands/start.d.ts +4 -0
- package/dist/commands/start.js +47 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +48 -0
- package/dist/commands/stop.d.ts +3 -0
- package/dist/commands/stop.js +104 -0
- package/dist/commands/summary.d.ts +6 -0
- package/dist/commands/summary.js +128 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +62 -0
- package/dist/data.d.ts +18 -0
- package/dist/data.js +161 -0
- package/dist/i18n.d.ts +7 -0
- package/dist/i18n.js +418 -0
- package/dist/index.js +142 -498
- package/dist/mascot.d.ts +9 -0
- package/dist/mascot.js +257 -0
- package/dist/parser.d.ts +6 -0
- package/dist/parser.js +45 -0
- package/dist/tui/countdown.d.ts +8 -0
- package/dist/tui/countdown.js +135 -0
- package/dist/tui/dashboard.d.ts +2 -0
- package/dist/tui/dashboard.js +135 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.js +8 -0
- package/dist/tui/lifecycle.d.ts +28 -0
- package/dist/tui/lifecycle.js +82 -0
- package/dist/tui/reflection.d.ts +5 -0
- package/dist/tui/reflection.js +71 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +44 -0
- package/package.json +20 -4
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdStop = cmdStop;
|
|
4
|
+
const data_1 = require("../data");
|
|
5
|
+
const mascot_1 = require("../mascot");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const i18n_1 = require("../i18n");
|
|
8
|
+
const tui_1 = require("../tui");
|
|
9
|
+
const reflection_1 = require("../tui/reflection");
|
|
10
|
+
async function cmdStop(options) {
|
|
11
|
+
const data = (0, data_1.loadData)();
|
|
12
|
+
if (!data.current) {
|
|
13
|
+
console.log(`\x1b[33m⚠ ${(0, i18n_1.t)('cmd.stop.noActive')}\x1b[0m`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const session = data.current;
|
|
17
|
+
session.end = new Date().toISOString();
|
|
18
|
+
const duration = new Date(session.end).getTime() - new Date(session.start).getTime();
|
|
19
|
+
const durationMinutes = duration / 60000;
|
|
20
|
+
// Calculate efficiency score
|
|
21
|
+
const hasEstimate = session.estimatedMinutes && session.estimatedMinutes > 0;
|
|
22
|
+
let efficiency = -1;
|
|
23
|
+
if (hasEstimate) {
|
|
24
|
+
if (durationMinutes < 0.01) {
|
|
25
|
+
efficiency = 100; // sub-second task = perfect
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
efficiency = Math.min(100, Math.round((session.estimatedMinutes / durationMinutes) * 100));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Calculate points
|
|
32
|
+
const effForPoints = hasEstimate ? efficiency : 50; // default mid efficiency if no estimate
|
|
33
|
+
const points = (0, data_1.calculatePoints)(durationMinutes, effForPoints);
|
|
34
|
+
session.pointsEarned = points;
|
|
35
|
+
// Save reflection: CLI flag > TUI input > none
|
|
36
|
+
if (options?.reflection) {
|
|
37
|
+
session.reflection = options.reflection;
|
|
38
|
+
}
|
|
39
|
+
else if ((0, tui_1.isInteractiveTerminal)()) {
|
|
40
|
+
const result = await (0, reflection_1.showReflectionInput)();
|
|
41
|
+
if (result.text) {
|
|
42
|
+
session.reflection = result.text;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Update score, streak
|
|
46
|
+
data.score = (data.score || 0) + points;
|
|
47
|
+
(0, data_1.updateStreak)(data);
|
|
48
|
+
const level = (0, data_1.scoreToLevel)(data.score);
|
|
49
|
+
const levelProgress = (0, data_1.pointsToNextLevel)(data.score);
|
|
50
|
+
data.history.unshift(session);
|
|
51
|
+
data.current = null;
|
|
52
|
+
(0, data_1.saveData)(data);
|
|
53
|
+
console.log();
|
|
54
|
+
(0, mascot_1.showCaceSmall)((0, i18n_1.t)('cmd.stop.taskComplete'), 'celebrating');
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(' ┌─────────────────────────────────┐');
|
|
57
|
+
console.log(` │ ${(0, i18n_1.t)('cmd.stop.taskSummary')} │`);
|
|
58
|
+
console.log(' └─────────────────────────────────┘');
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(` 📌 ${(0, i18n_1.t)('common.task')}: ${session.task}`);
|
|
61
|
+
console.log(` 🕐 ${(0, i18n_1.t)('common.duration')}: ${(0, utils_1.formatDuration)(duration)}`);
|
|
62
|
+
if (session.tags.length > 0) {
|
|
63
|
+
console.log(` 🏷 ${(0, i18n_1.t)('common.tags')}: ${session.tags.join(', ')}`);
|
|
64
|
+
}
|
|
65
|
+
if (session.marks.length > 0) {
|
|
66
|
+
console.log(` 📍 ${(0, i18n_1.t)('common.marks')}: ${session.marks.length} ${(0, i18n_1.t)('cmd.stop.marksCount')}`);
|
|
67
|
+
}
|
|
68
|
+
// Efficiency display
|
|
69
|
+
if (hasEstimate) {
|
|
70
|
+
let effEmoji = '⭐';
|
|
71
|
+
let effColor = '\x1b[32m';
|
|
72
|
+
if (efficiency < 50) {
|
|
73
|
+
effEmoji = '💀';
|
|
74
|
+
effColor = '\x1b[31m';
|
|
75
|
+
}
|
|
76
|
+
else if (efficiency < 80) {
|
|
77
|
+
effEmoji = '💪';
|
|
78
|
+
effColor = '\x1b[33m';
|
|
79
|
+
}
|
|
80
|
+
else if (efficiency >= 100) {
|
|
81
|
+
effEmoji = '🏆';
|
|
82
|
+
}
|
|
83
|
+
console.log(` ${effEmoji} ${(0, i18n_1.t)('cmd.stop.efficiencyScore')}: ${effColor}${efficiency}%\x1b[0m`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(` ⏱ ${(0, i18n_1.t)('cmd.stop.noEstimate')}`);
|
|
87
|
+
}
|
|
88
|
+
// Points & Level
|
|
89
|
+
console.log(` \x1b[33m${(0, i18n_1.t)('score.earned', { points: String(points) })}\x1b[0m`);
|
|
90
|
+
console.log(` ${(0, i18n_1.t)('cmd.status.level', { level: String(level), score: String(data.score) })}`);
|
|
91
|
+
console.log(` ${(0, i18n_1.t)('score.progress', { current: String(levelProgress.current), needed: String(levelProgress.needed) })}`);
|
|
92
|
+
// Streak
|
|
93
|
+
if (data.streak > 1) {
|
|
94
|
+
console.log(` ${(0, i18n_1.t)('score.streakFire', { days: String(data.streak) })}`);
|
|
95
|
+
}
|
|
96
|
+
else if (data.streak === 1) {
|
|
97
|
+
console.log(` ${(0, i18n_1.t)('score.newStreak')}`);
|
|
98
|
+
}
|
|
99
|
+
// Reflection
|
|
100
|
+
if (session.reflection) {
|
|
101
|
+
console.log(` 💭 ${session.reflection}`);
|
|
102
|
+
}
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdSummary = cmdSummary;
|
|
4
|
+
const data_1 = require("../data");
|
|
5
|
+
const mascot_1 = require("../mascot");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const i18n_1 = require("../i18n");
|
|
8
|
+
function computeDailyHours(sessions, days) {
|
|
9
|
+
const result = [];
|
|
10
|
+
const now = new Date();
|
|
11
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
12
|
+
const day = new Date(now);
|
|
13
|
+
day.setDate(now.getDate() - i);
|
|
14
|
+
day.setHours(0, 0, 0, 0);
|
|
15
|
+
const dayEnd = new Date(day);
|
|
16
|
+
dayEnd.setDate(dayEnd.getDate() + 1);
|
|
17
|
+
const daySessions = sessions.filter(s => s.start >= day.toISOString() && s.start < dayEnd.toISOString());
|
|
18
|
+
const totalMs = daySessions.reduce((acc, s) => acc + (new Date(s.end).getTime() - new Date(s.start).getTime()), 0);
|
|
19
|
+
result.push({
|
|
20
|
+
label: day.toLocaleDateString((0, i18n_2.getLocale)() === 'zh' ? 'zh-CN' : 'en-US', {
|
|
21
|
+
weekday: 'short',
|
|
22
|
+
month: 'short',
|
|
23
|
+
day: 'numeric',
|
|
24
|
+
}),
|
|
25
|
+
hours: totalMs / 3600000,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
const i18n_2 = require("../i18n");
|
|
31
|
+
function cmdSummary(options) {
|
|
32
|
+
const data = (0, data_1.loadData)();
|
|
33
|
+
const allCompleted = data.history.filter(s => s.end); // all completed, for daily chart
|
|
34
|
+
let sessions = allCompleted;
|
|
35
|
+
// Time filtering
|
|
36
|
+
const now = new Date();
|
|
37
|
+
if (options.today) {
|
|
38
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
|
|
39
|
+
sessions = sessions.filter(s => s.start >= todayStart);
|
|
40
|
+
}
|
|
41
|
+
else if (options.week) {
|
|
42
|
+
// Start from Monday
|
|
43
|
+
const dayOfWeek = now.getDay(); // 0=Sun, 1=Mon...
|
|
44
|
+
const mondayOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
45
|
+
const weekStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() - mondayOffset);
|
|
46
|
+
sessions = sessions.filter(s => s.start >= weekStart.toISOString());
|
|
47
|
+
}
|
|
48
|
+
else if (options.month) {
|
|
49
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
|
|
50
|
+
sessions = sessions.filter(s => s.start >= monthStart);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Default to today
|
|
54
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
|
|
55
|
+
sessions = sessions.filter(s => s.start >= todayStart);
|
|
56
|
+
}
|
|
57
|
+
// Tag filtering
|
|
58
|
+
if (options.tag) {
|
|
59
|
+
sessions = sessions.filter(s => s.tags.includes(options.tag));
|
|
60
|
+
}
|
|
61
|
+
console.log();
|
|
62
|
+
if (sessions.length === 0) {
|
|
63
|
+
(0, mascot_1.showCaceSmall)((0, i18n_1.t)('cmd.summary.noData'), 'sleepy');
|
|
64
|
+
console.log();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Compute stats
|
|
68
|
+
const totalSessions = sessions.length;
|
|
69
|
+
const durations = sessions.map(s => new Date(s.end).getTime() - new Date(s.start).getTime());
|
|
70
|
+
const totalDuration = durations.reduce((a, b) => a + b, 0);
|
|
71
|
+
const avgDuration = totalDuration / totalSessions;
|
|
72
|
+
// Tag distribution
|
|
73
|
+
const tagMap = new Map();
|
|
74
|
+
for (const s of sessions) {
|
|
75
|
+
const dur = new Date(s.end).getTime() - new Date(s.start).getTime();
|
|
76
|
+
for (const tag of s.tags) {
|
|
77
|
+
const entry = tagMap.get(tag) || { count: 0, ms: 0 };
|
|
78
|
+
entry.count++;
|
|
79
|
+
entry.ms += dur;
|
|
80
|
+
tagMap.set(tag, entry);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Top 5 longest
|
|
84
|
+
const top5 = [...sessions]
|
|
85
|
+
.sort((a, b) => {
|
|
86
|
+
const da = new Date(a.end).getTime() - new Date(a.start).getTime();
|
|
87
|
+
const db = new Date(b.end).getTime() - new Date(b.start).getTime();
|
|
88
|
+
return db - da;
|
|
89
|
+
})
|
|
90
|
+
.slice(0, 5);
|
|
91
|
+
// Daily hours bar chart (last 7 days, uses ALL completed sessions not filtered by time)
|
|
92
|
+
const dailyHours = computeDailyHours(allCompleted, 7);
|
|
93
|
+
// Render
|
|
94
|
+
(0, mascot_1.showCaceSmall)((0, i18n_1.t)('cmd.summary.title'), 'happy');
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(` ${(0, i18n_1.t)('cmd.summary.totalSessions')}: ${totalSessions} ${(0, i18n_1.t)('cmd.summary.sessions')}`);
|
|
97
|
+
console.log(` ${(0, i18n_1.t)('cmd.summary.totalDuration')}: ${(0, utils_1.formatDuration)(totalDuration)}`);
|
|
98
|
+
console.log(` ${(0, i18n_1.t)('cmd.summary.avgDuration')}: ${(0, utils_1.formatDuration)(avgDuration)}`);
|
|
99
|
+
console.log();
|
|
100
|
+
// Tag breakdown
|
|
101
|
+
if (tagMap.size > 0) {
|
|
102
|
+
console.log(` ${(0, i18n_1.t)('cmd.summary.tagBreakdown')}:`);
|
|
103
|
+
for (const [tag, { count, ms }] of tagMap) {
|
|
104
|
+
const pct = Math.round((count / totalSessions) * 100);
|
|
105
|
+
const barLen = Math.round(pct / 5); // 20 chars max
|
|
106
|
+
const bar = '█'.repeat(barLen);
|
|
107
|
+
const durStr = (0, utils_1.formatDuration)(ms);
|
|
108
|
+
console.log(` #${tag} ${bar} ${count}x (${pct}%) ${durStr}`);
|
|
109
|
+
}
|
|
110
|
+
console.log();
|
|
111
|
+
}
|
|
112
|
+
// Daily bar chart
|
|
113
|
+
console.log(` ${(0, i18n_1.t)('cmd.summary.dailyHours')} (${(0, i18n_1.t)('cmd.summary.dailyHoursLabel')}):`);
|
|
114
|
+
const maxHours = Math.max(...dailyHours.map(d => d.hours), 1);
|
|
115
|
+
for (const day of dailyHours) {
|
|
116
|
+
const barWidth = Math.round((day.hours / maxHours) * 20);
|
|
117
|
+
const bar = '█'.repeat(barWidth);
|
|
118
|
+
console.log(` ${day.label} ${bar} ${day.hours.toFixed(1)}h`);
|
|
119
|
+
}
|
|
120
|
+
console.log();
|
|
121
|
+
// Top 5
|
|
122
|
+
console.log(` ${(0, i18n_1.t)('cmd.summary.top5')}:`);
|
|
123
|
+
top5.forEach((s, i) => {
|
|
124
|
+
const dur = new Date(s.end).getTime() - new Date(s.start).getTime();
|
|
125
|
+
console.log(` ${i + 1}. ${s.task} - ${(0, utils_1.formatDuration)(dur)}`);
|
|
126
|
+
});
|
|
127
|
+
console.log();
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cmdSync(filePath: string): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.cmdSync = cmdSync;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const data_1 = require("../data");
|
|
40
|
+
const mascot_1 = require("../mascot");
|
|
41
|
+
const i18n_1 = require("../i18n");
|
|
42
|
+
function cmdSync(filePath) {
|
|
43
|
+
if (!filePath || filePath.trim() === '') {
|
|
44
|
+
console.log(`\x1b[33m${(0, i18n_1.t)('cmd.sync.emptyPath')}\x1b[0m`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const data = (0, data_1.loadData)();
|
|
48
|
+
const absPath = path.resolve(filePath);
|
|
49
|
+
const parentDir = path.dirname(absPath);
|
|
50
|
+
if (!fs.existsSync(parentDir)) {
|
|
51
|
+
console.log(`\x1b[33m${(0, i18n_1.t)('cmd.sync.dirNotExist', { dir: parentDir })}\x1b[0m`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
data.syncPath = absPath;
|
|
55
|
+
(0, data_1.saveData)(data);
|
|
56
|
+
console.log();
|
|
57
|
+
(0, mascot_1.showCaceSmall)((0, i18n_1.t)('cmd.sync.configured'));
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(` ${(0, i18n_1.t)('cmd.sync.syncPath', { path: absPath })}`);
|
|
60
|
+
console.log(` ${(0, i18n_1.t)('cmd.sync.syncTip')}`);
|
|
61
|
+
console.log();
|
|
62
|
+
}
|
package/dist/data.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TimeKeeperData } from './types';
|
|
2
|
+
export declare function getDataFile(): string;
|
|
3
|
+
export declare function setDataFile(p: string): void;
|
|
4
|
+
export declare function loadData(): TimeKeeperData;
|
|
5
|
+
export declare function saveData(data: TimeKeeperData): void;
|
|
6
|
+
/** Calculate points earned for a completed session */
|
|
7
|
+
export declare function calculatePoints(durationMinutes: number, efficiency: number): number;
|
|
8
|
+
/** Convert total score to level (1-99) */
|
|
9
|
+
export declare function scoreToLevel(score: number): number;
|
|
10
|
+
/** Points needed for next level */
|
|
11
|
+
export declare function pointsToNextLevel(score: number): {
|
|
12
|
+
current: number;
|
|
13
|
+
needed: number;
|
|
14
|
+
};
|
|
15
|
+
/** Update streak based on today's date. Call on session completion. */
|
|
16
|
+
export declare function updateStreak(data: TimeKeeperData): void;
|
|
17
|
+
/** Get today's date as YYYY-MM-DD */
|
|
18
|
+
export declare function getTodayStr(): string;
|
package/dist/data.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getDataFile = getDataFile;
|
|
37
|
+
exports.setDataFile = setDataFile;
|
|
38
|
+
exports.loadData = loadData;
|
|
39
|
+
exports.saveData = saveData;
|
|
40
|
+
exports.calculatePoints = calculatePoints;
|
|
41
|
+
exports.scoreToLevel = scoreToLevel;
|
|
42
|
+
exports.pointsToNextLevel = pointsToNextLevel;
|
|
43
|
+
exports.updateStreak = updateStreak;
|
|
44
|
+
exports.getTodayStr = getTodayStr;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
// ============ Data Management ============
|
|
49
|
+
let DATA_FILE = path.join(os.homedir(), '.cace-timer.json');
|
|
50
|
+
function getDataFile() {
|
|
51
|
+
return DATA_FILE;
|
|
52
|
+
}
|
|
53
|
+
function setDataFile(p) {
|
|
54
|
+
DATA_FILE = p;
|
|
55
|
+
}
|
|
56
|
+
function ensureDefaults(data) {
|
|
57
|
+
return {
|
|
58
|
+
score: data.score ?? 0,
|
|
59
|
+
streak: data.streak ?? 0,
|
|
60
|
+
lastActiveDate: data.lastActiveDate,
|
|
61
|
+
syncPath: data.syncPath,
|
|
62
|
+
lang: data.lang,
|
|
63
|
+
current: data.current ?? null,
|
|
64
|
+
history: data.history ?? [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function loadData() {
|
|
68
|
+
try {
|
|
69
|
+
if (fs.existsSync(DATA_FILE)) {
|
|
70
|
+
const content = fs.readFileSync(DATA_FILE, 'utf-8');
|
|
71
|
+
return ensureDefaults(JSON.parse(content));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Ignore errors, return default
|
|
76
|
+
}
|
|
77
|
+
return ensureDefaults({});
|
|
78
|
+
}
|
|
79
|
+
function saveData(data) {
|
|
80
|
+
try {
|
|
81
|
+
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
throw new Error(`Failed to save data to ${DATA_FILE}: ${e.message}`);
|
|
85
|
+
}
|
|
86
|
+
// Sync to external path if set
|
|
87
|
+
if (data.syncPath && fs.existsSync(path.dirname(data.syncPath))) {
|
|
88
|
+
try {
|
|
89
|
+
fs.writeFileSync(data.syncPath, JSON.stringify(data, null, 2));
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
process.stderr.write(`Warning: sync to ${data.syncPath} failed: ${e.message}\n`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ============ Scoring & Streak ============
|
|
97
|
+
/** Calculate points earned for a completed session */
|
|
98
|
+
function calculatePoints(durationMinutes, efficiency) {
|
|
99
|
+
let points = 10; // base points
|
|
100
|
+
if (efficiency >= 80)
|
|
101
|
+
points += 5; // efficiency bonus
|
|
102
|
+
if (efficiency >= 100)
|
|
103
|
+
points += 5; // perfect bonus
|
|
104
|
+
if (durationMinutes >= 25)
|
|
105
|
+
points += 5; // deep work bonus (25+ min)
|
|
106
|
+
return points;
|
|
107
|
+
}
|
|
108
|
+
/** Convert total score to level (1-99) */
|
|
109
|
+
function scoreToLevel(score) {
|
|
110
|
+
// Each level needs progressively more points: Lv.N needs N*20 total
|
|
111
|
+
// Lv.1 = 0pts, Lv.2 = 20pts, Lv.3 = 60pts, Lv.4 = 120pts...
|
|
112
|
+
if (score <= 0)
|
|
113
|
+
return 1;
|
|
114
|
+
let level = 1;
|
|
115
|
+
let totalNeeded = 0;
|
|
116
|
+
while (level < 99) {
|
|
117
|
+
totalNeeded += level * 20;
|
|
118
|
+
if (score < totalNeeded)
|
|
119
|
+
break;
|
|
120
|
+
level++;
|
|
121
|
+
}
|
|
122
|
+
return level;
|
|
123
|
+
}
|
|
124
|
+
/** Points needed for next level */
|
|
125
|
+
function pointsToNextLevel(score) {
|
|
126
|
+
const level = scoreToLevel(score);
|
|
127
|
+
let totalForCurrent = 0;
|
|
128
|
+
for (let i = 1; i < level; i++) {
|
|
129
|
+
totalForCurrent += i * 20;
|
|
130
|
+
}
|
|
131
|
+
const totalForNext = totalForCurrent + level * 20;
|
|
132
|
+
return {
|
|
133
|
+
current: score - totalForCurrent,
|
|
134
|
+
needed: level * 20,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Update streak based on today's date. Call on session completion. */
|
|
138
|
+
function updateStreak(data) {
|
|
139
|
+
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
140
|
+
if (!data.lastActiveDate) {
|
|
141
|
+
data.streak = 1;
|
|
142
|
+
data.lastActiveDate = today;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (data.lastActiveDate === today)
|
|
146
|
+
return; // already active today
|
|
147
|
+
const last = new Date(data.lastActiveDate);
|
|
148
|
+
const now = new Date(today);
|
|
149
|
+
const diffDays = Math.floor((now.getTime() - last.getTime()) / 86400000);
|
|
150
|
+
if (diffDays === 1) {
|
|
151
|
+
data.streak++;
|
|
152
|
+
}
|
|
153
|
+
else if (diffDays > 1) {
|
|
154
|
+
data.streak = 1;
|
|
155
|
+
}
|
|
156
|
+
data.lastActiveDate = today;
|
|
157
|
+
}
|
|
158
|
+
/** Get today's date as YYYY-MM-DD */
|
|
159
|
+
function getTodayStr() {
|
|
160
|
+
return new Date().toISOString().split('T')[0];
|
|
161
|
+
}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type Locale = 'zh' | 'en';
|
|
2
|
+
export type LocaleStrings = Record<string, string>;
|
|
3
|
+
export declare function setLocale(locale: Locale): void;
|
|
4
|
+
export declare function getLocale(): Locale;
|
|
5
|
+
export declare function detectLocale(): Locale;
|
|
6
|
+
export declare function resolveLocale(cliFlag?: string, storedLang?: string): Locale;
|
|
7
|
+
export declare function t(key: string, params?: Record<string, string | number>): string;
|