@cacinie/cace-timer 1.3.1 → 1.4.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/README.md +61 -30
- package/dist/commands/export.js +4 -3
- package/dist/commands/help.js +5 -0
- package/dist/commands/mark.js +2 -1
- package/dist/commands/mascot.d.ts +5 -0
- package/dist/commands/mascot.js +77 -0
- package/dist/commands/pomodoro.js +26 -17
- package/dist/commands/resume.js +3 -2
- package/dist/commands/start.js +4 -3
- package/dist/commands/stop.js +7 -6
- package/dist/commands/summary.js +9 -9
- package/dist/commands/sync.js +3 -2
- package/dist/data.js +0 -1
- package/dist/i18n.js +23 -1
- package/dist/index.js +34 -4
- package/dist/mascot/assets/mint.d.ts +171 -0
- package/dist/mascot/assets/mint.js +218 -0
- package/dist/mascot/frames.d.ts +25 -0
- package/dist/mascot/frames.js +114 -0
- package/dist/mascot/state.d.ts +3 -0
- package/dist/mascot/state.js +25 -0
- package/dist/mascot.d.ts +5 -5
- package/dist/mascot.js +23 -177
- package/dist/parser.js +12 -1
- package/dist/terminal.d.ts +15 -0
- package/dist/terminal.js +68 -0
- package/dist/tui/animation.d.ts +5 -0
- package/dist/tui/animation.js +29 -0
- package/dist/tui/countdown.d.ts +2 -5
- package/dist/tui/countdown.js +54 -123
- package/dist/tui/dashboard.js +68 -114
- package/dist/tui/index.js +5 -2
- package/dist/tui/layout.d.ts +15 -0
- package/dist/tui/layout.js +36 -0
- package/dist/tui/reflection.js +6 -4
- package/dist/tui/surface.d.ts +5 -0
- package/dist/tui/surface.js +64 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -12,20 +12,17 @@ npm install -g @cacinie/cace-timer
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
│ │
|
|
27
|
-
╰──┬┬──╯
|
|
28
|
-
╭┘└╮
|
|
15
|
+
_,
|
|
16
|
+
//
|
|
17
|
+
.-'/`--.
|
|
18
|
+
/ //\ \
|
|
19
|
+
/// \ h* \
|
|
20
|
+
(|:(O')-(O'):|)
|
|
21
|
+
|:(// //):|
|
|
22
|
+
\: \ v / :/
|
|
23
|
+
'- `._.' -'
|
|
24
|
+
/ >o< \
|
|
25
|
+
'-----'
|
|
29
26
|
CACE TIMER
|
|
30
27
|
```
|
|
31
28
|
|
|
@@ -210,15 +207,37 @@ Consecutive days with at least one completed task. Break a day and the streak re
|
|
|
210
207
|
|
|
211
208
|
## CACE Mascot
|
|
212
209
|
|
|
213
|
-
CACE
|
|
210
|
+
CACE uses **MINT**, a teal bob-haired girl with delicate round glasses, a slim,
|
|
211
|
+
rounded jaw and 16 expressions. The dashboard and countdown blink gently;
|
|
212
|
+
one-shot commands print immediately. The artwork automatically steps down from
|
|
213
|
+
full to compact, tiny or text-only as the terminal shrinks.
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
215
|
+
```bash
|
|
216
|
+
tk mascot preview # live preview; n/p selects an expression
|
|
217
|
+
tk mascot preview --all --no-tui # export all 16 expressions
|
|
218
|
+
tk mascot preview --size compact --ascii # ASCII art without Unicode glyphs
|
|
219
|
+
tk --no-animation # static mascot, live timer
|
|
220
|
+
tk --no-tui # plain command output
|
|
221
|
+
tk --color never # no foreground colours
|
|
222
|
+
tk --theme light # light-terminal palette
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`--ascii` changes mascot art; task names and translated UI text keep their original
|
|
226
|
+
characters. Colour modes are `auto`, `always` (truecolor for plain output), `256`
|
|
227
|
+
and `never`. Auto mode respects `NO_COLOR`, `TERM=dumb` and redirected output.
|
|
228
|
+
The blessed TUI maps colours to its supported terminal palette. No terminal
|
|
229
|
+
background, shell configuration or Python runtime is required by the application.
|
|
230
|
+
Preview with `--all` or an explicit `--size` is a static export. Add
|
|
231
|
+
`--expression little_smile` (or another expression key) to select one portrait.
|
|
232
|
+
|
|
233
|
+
Cancel a countdown with q/Esc/Ctrl+C. Cancellation exits 130 and retains the
|
|
234
|
+
active record; use `tk stop` to finish it. Rendering failures exit 1 without
|
|
235
|
+
adding a completed session. SIGTERM exits 143. Normal q/Esc in the dashboard or
|
|
236
|
+
preview exits 0. Normal timer completion rings once in a supported TTY.
|
|
237
|
+
|
|
238
|
+
For contributors: `npm test`, `npm run test:cli`, and, on POSIX with Python 3,
|
|
239
|
+
`npm run test:pty`. Tests use temporary timer files. See
|
|
240
|
+
[the design and reference](docs/design/mint.md).
|
|
222
241
|
|
|
223
242
|
## Data
|
|
224
243
|
|
|
@@ -456,15 +475,27 @@ tk stop
|
|
|
456
475
|
|
|
457
476
|
## CACE 吉祥物
|
|
458
477
|
|
|
459
|
-
CACE
|
|
478
|
+
CACE 采用 **MINT 蓝绿短 bob 女孩**:细圆框、大眼、平顺内收的下颌与小圆下巴,
|
|
479
|
+
无鼻子、无手部,共 16 种表情。仪表盘与番茄钟自然眨眼;开始/恢复等一次性命令即时输出。
|
|
480
|
+
终端缩小时自动切换完整、紧凑、迷你或纯文字布局。
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
tk mascot preview # 动态预览,n/p 切换表情
|
|
484
|
+
tk mascot preview --all --no-tui # 静态输出 16 种表情
|
|
485
|
+
tk mascot preview --size compact --ascii # 纯 ASCII 角色
|
|
486
|
+
tk --no-animation # 关闭动画,计时继续
|
|
487
|
+
tk --no-tui # 普通命令输出
|
|
488
|
+
tk --color never # 无颜色
|
|
489
|
+
tk --theme light # 浅色终端配色
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
`--ascii` 只替换角色字符,任务名和中文文案保留原文。颜色支持 `auto|always|256|never`;
|
|
493
|
+
自动模式尊重 `NO_COLOR`、`TERM=dumb` 和输出重定向。TUI 颜色适配 blessed 支持的终端调色板。
|
|
494
|
+
应用不需要 Python,也不修改终端背景或 shell 配置。
|
|
460
495
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
| 开心 | ★ ★ | ◡◡◡ | 任务完成 |
|
|
465
|
-
| 专注 | ◆ ◆ | ▬▬▬ | 番茄钟 / 活跃任务 |
|
|
466
|
-
| 庆祝 | ◉ ◉ | ▽△▽ | 获得积分 + 举手 |
|
|
467
|
-
| 困了 | ─ ─ | ~~ | 无活跃任务 |
|
|
496
|
+
番茄钟中 q/Esc/Ctrl+C 取消并返回 130,当前记录保留,可用 `tk stop` 结束;
|
|
497
|
+
渲染错误返回 1,不写入已完成历史;SIGTERM 返回 143。仪表盘和预览中 q/Esc 正常退出。
|
|
498
|
+
`--all` 或显式 `--size` 为静态导出,可加 `--expression <表情名称>` 指定单张。
|
|
468
499
|
|
|
469
500
|
## 数据
|
|
470
501
|
|
package/dist/commands/export.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.cmdExport = cmdExport;
|
|
37
|
+
const terminal_1 = require("../terminal");
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const data_1 = require("../data");
|
|
39
40
|
const mascot_1 = require("../mascot");
|
|
@@ -57,7 +58,7 @@ function cmdExport(options) {
|
|
|
57
58
|
output = toCsv(sessions);
|
|
58
59
|
}
|
|
59
60
|
else {
|
|
60
|
-
console.log(
|
|
61
|
+
console.log((0, terminal_1.paint)(`${(0, i18n_1.t)('cmd.export.unsupportedFormat')}`, 'yellow'));
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
if (options.output) {
|
|
@@ -73,7 +74,7 @@ function cmdExport(options) {
|
|
|
73
74
|
function toCsv(sessions) {
|
|
74
75
|
const header = 'ID,Task,Start,End,Duration,Tags,Marks\n';
|
|
75
76
|
const rows = sessions
|
|
76
|
-
.map(s => {
|
|
77
|
+
.map((s) => {
|
|
77
78
|
const duration = s.end
|
|
78
79
|
? (0, utils_1.formatDuration)(new Date(s.end).getTime() - new Date(s.start).getTime())
|
|
79
80
|
: 'running';
|
|
@@ -97,7 +98,7 @@ function toMarkdown(sessions) {
|
|
|
97
98
|
const duration = s.end
|
|
98
99
|
? (0, utils_1.formatDuration)(new Date(s.end).getTime() - new Date(s.start).getTime())
|
|
99
100
|
: (0, i18n_1.t)('common.running');
|
|
100
|
-
const tags = s.tags.map(tg => '#' + tg).join(' ');
|
|
101
|
+
const tags = s.tags.map((tg) => '#' + tg).join(' ');
|
|
101
102
|
md += `| ${i + 1} | ${s.task} | ${(0, utils_1.formatTime)(s.start)} | ${duration} | ${tags} |\n`;
|
|
102
103
|
});
|
|
103
104
|
return md;
|
package/dist/commands/help.js
CHANGED
|
@@ -66,6 +66,11 @@ function cmdHelp() {
|
|
|
66
66
|
console.log(` ${(0, i18n_1.t)('cmd.help.focusDesc')}`);
|
|
67
67
|
console.log();
|
|
68
68
|
console.log(` ${(0, i18n_1.t)('cmd.help.lang')}`);
|
|
69
|
+
console.log(` ${(0, i18n_1.t)('cmd.help.display')}`);
|
|
70
|
+
console.log(' --no-animation --no-tui --ascii');
|
|
71
|
+
console.log(' --color auto|always|256|never --theme dark|light');
|
|
72
|
+
console.log(' tk mascot preview [--all] [--expression <name>] [--size full|compact|tiny]');
|
|
73
|
+
console.log(` ${(0, i18n_1.t)('mascot.help')}`);
|
|
69
74
|
console.log();
|
|
70
75
|
console.log(` ${(0, i18n_1.t)('cmd.help.help')}`);
|
|
71
76
|
console.log(` ${(0, i18n_1.t)('cmd.help.helpDesc')}`);
|
package/dist/commands/mark.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cmdMark = cmdMark;
|
|
4
|
+
const terminal_1 = require("../terminal");
|
|
4
5
|
const data_1 = require("../data");
|
|
5
6
|
const mascot_1 = require("../mascot");
|
|
6
7
|
const utils_1 = require("../utils");
|
|
@@ -8,7 +9,7 @@ const i18n_1 = require("../i18n");
|
|
|
8
9
|
async function cmdMark(note) {
|
|
9
10
|
const data = (0, data_1.loadData)();
|
|
10
11
|
if (!data.current) {
|
|
11
|
-
console.log(
|
|
12
|
+
console.log((0, terminal_1.paint)(`⚠ ${(0, i18n_1.t)('cmd.mark.noActive')}`, 'yellow'));
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
const mark = {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cmdMascot = cmdMascot;
|
|
7
|
+
const blessed_1 = __importDefault(require("blessed"));
|
|
8
|
+
const mint_1 = require("../mascot/assets/mint");
|
|
9
|
+
const frames_1 = require("../mascot/frames");
|
|
10
|
+
const terminal_1 = require("../terminal");
|
|
11
|
+
const i18n_1 = require("../i18n");
|
|
12
|
+
const tui_1 = require("../tui");
|
|
13
|
+
const surface_1 = require("../tui/surface");
|
|
14
|
+
const animation_1 = require("../tui/animation");
|
|
15
|
+
const lifecycle_1 = require("../tui/lifecycle");
|
|
16
|
+
async function cmdMascot(options) {
|
|
17
|
+
if (options.expression && !mint_1.EXPRESSIONS.some((item) => item.key === options.expression)) {
|
|
18
|
+
throw new Error((0, i18n_1.t)('mascot.invalidExpression', { names: mint_1.EXPRESSIONS.map((item) => item.key).join(', ') }));
|
|
19
|
+
}
|
|
20
|
+
if (options.size && !['full', 'compact', 'tiny'].includes(options.size)) {
|
|
21
|
+
throw new Error((0, i18n_1.t)('mascot.invalidSize'));
|
|
22
|
+
}
|
|
23
|
+
const display = (0, terminal_1.getDisplay)();
|
|
24
|
+
if (options.all || options.size || !(0, tui_1.isInteractiveTerminal)()) {
|
|
25
|
+
const selected = options.all
|
|
26
|
+
? mint_1.EXPRESSIONS
|
|
27
|
+
: mint_1.EXPRESSIONS.filter((item) => item.key === (options.expression ?? 'little_smile'));
|
|
28
|
+
for (const item of selected) {
|
|
29
|
+
console.log(`${item.key} / ${item[(0, i18n_1.getLocale)()]}`);
|
|
30
|
+
console.log((0, frames_1.renderFrame)((0, frames_1.getFrame)({
|
|
31
|
+
expression: item.key,
|
|
32
|
+
size: (options.size ?? 'full'),
|
|
33
|
+
ascii: display.ascii,
|
|
34
|
+
}), display));
|
|
35
|
+
console.log();
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
const screen = blessed_1.default.screen({ smartCSR: true, title: 'CACE / MINT', fullUnicode: true });
|
|
41
|
+
const surface = (0, surface_1.createSurface)(screen);
|
|
42
|
+
let loop = undefined;
|
|
43
|
+
let index = Math.max(0, mint_1.EXPRESSIONS.findIndex((item) => item.key === options.expression));
|
|
44
|
+
let settled = false;
|
|
45
|
+
const disposeSignals = (0, lifecycle_1.installSignalCleanup)(screen);
|
|
46
|
+
screen.once('destroy', () => {
|
|
47
|
+
loop?.dispose();
|
|
48
|
+
disposeSignals();
|
|
49
|
+
});
|
|
50
|
+
const finish = (error) => {
|
|
51
|
+
if (settled)
|
|
52
|
+
return;
|
|
53
|
+
settled = true;
|
|
54
|
+
loop?.dispose();
|
|
55
|
+
(0, lifecycle_1.destroyScreen)(screen);
|
|
56
|
+
if (error !== undefined)
|
|
57
|
+
reject(error);
|
|
58
|
+
else
|
|
59
|
+
resolve();
|
|
60
|
+
};
|
|
61
|
+
screen.key(['escape', 'q'], () => finish());
|
|
62
|
+
screen.key(['C-c'], () => process.emit('SIGINT'));
|
|
63
|
+
screen.key(['right', 'n', 'space'], () => {
|
|
64
|
+
index = (index + 1) % mint_1.EXPRESSIONS.length;
|
|
65
|
+
loop?.refresh();
|
|
66
|
+
});
|
|
67
|
+
screen.key(['left', 'p'], () => {
|
|
68
|
+
index = (index + mint_1.EXPRESSIONS.length - 1) % mint_1.EXPRESSIONS.length;
|
|
69
|
+
loop?.refresh();
|
|
70
|
+
});
|
|
71
|
+
screen.on('resize', () => loop?.refresh());
|
|
72
|
+
loop = (0, animation_1.startRefreshLoop)((elapsed) => {
|
|
73
|
+
const item = mint_1.EXPRESSIONS[index];
|
|
74
|
+
surface.render(item.key, elapsed, [`C A C E / M I N T`, `${index + 1}/16 ${item[(0, i18n_1.getLocale)()]}`, item.key], (0, i18n_1.t)('mascot.controls'));
|
|
75
|
+
}, finish, display.animation ? 100 : 1000);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cmdPomodoro = cmdPomodoro;
|
|
4
|
+
const terminal_1 = require("../terminal");
|
|
4
5
|
const data_1 = require("../data");
|
|
5
6
|
const mascot_1 = require("../mascot");
|
|
6
7
|
const utils_1 = require("../utils");
|
|
@@ -9,19 +10,23 @@ const tui_1 = require("../tui");
|
|
|
9
10
|
const countdown_1 = require("../tui/countdown");
|
|
10
11
|
function getRandomEncouragement() {
|
|
11
12
|
const keys = [
|
|
12
|
-
'cmd.focus.encourage1',
|
|
13
|
-
'cmd.focus.
|
|
13
|
+
'cmd.focus.encourage1',
|
|
14
|
+
'cmd.focus.encourage2',
|
|
15
|
+
'cmd.focus.encourage3',
|
|
16
|
+
'cmd.focus.encourage4',
|
|
17
|
+
'cmd.focus.encourage5',
|
|
18
|
+
'cmd.focus.encourage6',
|
|
14
19
|
];
|
|
15
20
|
return (0, i18n_1.t)(keys[Math.floor(Math.random() * keys.length)]);
|
|
16
21
|
}
|
|
17
22
|
function normalizeTags(rawTag) {
|
|
18
23
|
if (Array.isArray(rawTag)) {
|
|
19
|
-
return rawTag.flatMap(tg => tg.split(',').map(s => s.trim())).filter(Boolean);
|
|
24
|
+
return rawTag.flatMap((tg) => tg.split(',').map((s) => s.trim())).filter(Boolean);
|
|
20
25
|
}
|
|
21
26
|
if (typeof rawTag === 'string') {
|
|
22
27
|
return rawTag
|
|
23
28
|
.split(',')
|
|
24
|
-
.map(s => s.trim())
|
|
29
|
+
.map((s) => s.trim())
|
|
25
30
|
.filter(Boolean);
|
|
26
31
|
}
|
|
27
32
|
return [];
|
|
@@ -33,18 +38,19 @@ function formatMsAsClock(ms) {
|
|
|
33
38
|
return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
|
|
34
39
|
}
|
|
35
40
|
function runCountdown(totalMs) {
|
|
36
|
-
return new Promise(resolve => {
|
|
41
|
+
return new Promise((resolve) => {
|
|
37
42
|
const startTime = Date.now();
|
|
38
43
|
const barWidth = 20;
|
|
39
44
|
const interval = setInterval(() => {
|
|
40
45
|
const elapsed = Date.now() - startTime;
|
|
41
|
-
const remaining = Math.max(0, totalMs - elapsed);
|
|
42
46
|
const progress = Math.min(1, elapsed / totalMs);
|
|
43
47
|
const filled = Math.floor(progress * barWidth);
|
|
44
48
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
45
49
|
const elapsedStr = formatMsAsClock(elapsed);
|
|
46
50
|
const totalStr = formatMsAsClock(totalMs);
|
|
47
|
-
process.stdout.
|
|
51
|
+
if (process.stdout.isTTY && process.env.TERM !== 'dumb') {
|
|
52
|
+
process.stdout.write(`\r [${bar}] ${elapsedStr} / ${totalStr} `);
|
|
53
|
+
}
|
|
48
54
|
if (elapsed >= totalMs) {
|
|
49
55
|
clearInterval(interval);
|
|
50
56
|
process.stdout.write('\n');
|
|
@@ -66,7 +72,7 @@ function runCountdown(totalMs) {
|
|
|
66
72
|
async function cmdPomodoro(task, options) {
|
|
67
73
|
const data = (0, data_1.loadData)();
|
|
68
74
|
if (data.current) {
|
|
69
|
-
console.log(
|
|
75
|
+
console.log((0, terminal_1.paint)(`⚠ ${(0, i18n_1.t)('common.alreadyActive')}`, 'yellow'));
|
|
70
76
|
return;
|
|
71
77
|
}
|
|
72
78
|
const workMin = options.work ?? 25;
|
|
@@ -94,14 +100,15 @@ async function cmdPomodoro(task, options) {
|
|
|
94
100
|
console.log(` ${getRandomEncouragement()}`);
|
|
95
101
|
if ((0, tui_1.isInteractiveTerminal)()) {
|
|
96
102
|
// TUI countdown with mascot
|
|
97
|
-
await
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
label: `${(0, i18n_1.t)('cmd.pomodoro.round', { round: String(round), total: String(rounds) })} [${(0, i18n_1.t)('cmd.pomodoro.workPhase')}]`,
|
|
101
|
-
mascots: [mascot_1.CACE_FOCUSED, mascot_1.CACE_HAPPY],
|
|
102
|
-
onDone: resolve,
|
|
103
|
-
});
|
|
103
|
+
const completed = await (0, countdown_1.showCountdown)({
|
|
104
|
+
totalMs: workMin * 60 * 1000,
|
|
105
|
+
label: `${task} / ${(0, i18n_1.t)('cmd.pomodoro.round', { round: String(round), total: String(rounds) })}`,
|
|
104
106
|
});
|
|
107
|
+
if (!completed) {
|
|
108
|
+
console.log((0, i18n_1.t)('tui.cancelled'));
|
|
109
|
+
process.exitCode = 130;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
else {
|
|
107
114
|
await runCountdown(workMin * 60 * 1000);
|
|
@@ -112,7 +119,8 @@ async function cmdPomodoro(task, options) {
|
|
|
112
119
|
data.current = null;
|
|
113
120
|
totalWorkMs += workMin * 60 * 1000;
|
|
114
121
|
(0, data_1.saveData)(data);
|
|
115
|
-
process.stdout.
|
|
122
|
+
if (process.stdout.isTTY && process.env.TERM !== 'dumb')
|
|
123
|
+
process.stdout.write('\x07');
|
|
116
124
|
(0, mascot_1.showCaceSmall)((0, i18n_1.t)('cmd.pomodoro.workDone'), 'happy');
|
|
117
125
|
console.log();
|
|
118
126
|
if (round < rounds) {
|
|
@@ -120,7 +128,8 @@ async function cmdPomodoro(task, options) {
|
|
|
120
128
|
console.log(` ${(0, i18n_1.t)('cmd.pomodoro.breakTime')}`);
|
|
121
129
|
await runCountdown(breakMin * 60 * 1000);
|
|
122
130
|
totalBreakMs += breakMin * 60 * 1000;
|
|
123
|
-
process.stdout.
|
|
131
|
+
if (process.stdout.isTTY && process.env.TERM !== 'dumb')
|
|
132
|
+
process.stdout.write('\x07');
|
|
124
133
|
(0, mascot_1.showCaceSmall)((0, i18n_1.t)('cmd.pomodoro.breakDone'), 'normal');
|
|
125
134
|
console.log();
|
|
126
135
|
}
|
package/dist/commands/resume.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cmdResume = cmdResume;
|
|
4
|
+
const terminal_1 = require("../terminal");
|
|
4
5
|
const data_1 = require("../data");
|
|
5
6
|
const mascot_1 = require("../mascot");
|
|
6
7
|
const utils_1 = require("../utils");
|
|
@@ -8,7 +9,7 @@ const i18n_1 = require("../i18n");
|
|
|
8
9
|
async function cmdResume(options) {
|
|
9
10
|
const data = (0, data_1.loadData)();
|
|
10
11
|
if (data.current) {
|
|
11
|
-
console.log(
|
|
12
|
+
console.log((0, terminal_1.paint)(`⚠ ${(0, i18n_1.t)('common.alreadyActive')}`, 'yellow'));
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
if (data.history.length === 0) {
|
|
@@ -22,7 +23,7 @@ async function cmdResume(options) {
|
|
|
22
23
|
source = data.history[0];
|
|
23
24
|
}
|
|
24
25
|
else if (options.id) {
|
|
25
|
-
source = data.history.find(s => s.id === options.id);
|
|
26
|
+
source = data.history.find((s) => s.id === options.id);
|
|
26
27
|
}
|
|
27
28
|
if (!source) {
|
|
28
29
|
console.log();
|
package/dist/commands/start.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cmdStart = cmdStart;
|
|
4
|
+
const terminal_1 = require("../terminal");
|
|
4
5
|
const data_1 = require("../data");
|
|
5
6
|
const mascot_1 = require("../mascot");
|
|
6
7
|
const utils_1 = require("../utils");
|
|
@@ -8,19 +9,19 @@ const i18n_1 = require("../i18n");
|
|
|
8
9
|
async function cmdStart(task, options) {
|
|
9
10
|
const data = (0, data_1.loadData)();
|
|
10
11
|
if (data.current) {
|
|
11
|
-
console.log(
|
|
12
|
+
console.log((0, terminal_1.paint)(`⚠ ${(0, i18n_1.t)('common.alreadyActive')}`, 'yellow'));
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
// Normalize tags: support --tag dev --tag api and --tag dev,api
|
|
15
16
|
let tags = [];
|
|
16
17
|
const rawTag = options.tag;
|
|
17
18
|
if (Array.isArray(rawTag)) {
|
|
18
|
-
tags = rawTag.flatMap(tag => tag.split(',').map(s => s.trim())).filter(Boolean);
|
|
19
|
+
tags = rawTag.flatMap((tag) => tag.split(',').map((s) => s.trim())).filter(Boolean);
|
|
19
20
|
}
|
|
20
21
|
else if (typeof rawTag === 'string') {
|
|
21
22
|
tags = rawTag
|
|
22
23
|
.split(',')
|
|
23
|
-
.map(s => s.trim())
|
|
24
|
+
.map((s) => s.trim())
|
|
24
25
|
.filter(Boolean);
|
|
25
26
|
}
|
|
26
27
|
const session = {
|
package/dist/commands/stop.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cmdStop = cmdStop;
|
|
4
|
+
const terminal_1 = require("../terminal");
|
|
4
5
|
const data_1 = require("../data");
|
|
5
6
|
const mascot_1 = require("../mascot");
|
|
6
7
|
const utils_1 = require("../utils");
|
|
@@ -10,7 +11,7 @@ const reflection_1 = require("../tui/reflection");
|
|
|
10
11
|
async function cmdStop(options) {
|
|
11
12
|
const data = (0, data_1.loadData)();
|
|
12
13
|
if (!data.current) {
|
|
13
|
-
console.log(
|
|
14
|
+
console.log((0, terminal_1.paint)(`⚠ ${(0, i18n_1.t)('cmd.stop.noActive')}`, 'yellow'));
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
const session = data.current;
|
|
@@ -68,25 +69,25 @@ async function cmdStop(options) {
|
|
|
68
69
|
// Efficiency display
|
|
69
70
|
if (hasEstimate) {
|
|
70
71
|
let effEmoji = '⭐';
|
|
71
|
-
let effColor = '
|
|
72
|
+
let effColor = 'green';
|
|
72
73
|
if (efficiency < 50) {
|
|
73
74
|
effEmoji = '💀';
|
|
74
|
-
effColor = '
|
|
75
|
+
effColor = 'red';
|
|
75
76
|
}
|
|
76
77
|
else if (efficiency < 80) {
|
|
77
78
|
effEmoji = '💪';
|
|
78
|
-
effColor = '
|
|
79
|
+
effColor = 'yellow';
|
|
79
80
|
}
|
|
80
81
|
else if (efficiency >= 100) {
|
|
81
82
|
effEmoji = '🏆';
|
|
82
83
|
}
|
|
83
|
-
console.log(` ${effEmoji} ${(0, i18n_1.t)('cmd.stop.efficiencyScore')}: ${
|
|
84
|
+
console.log(` ${effEmoji} ${(0, i18n_1.t)('cmd.stop.efficiencyScore')}: ${(0, terminal_1.paint)(`${efficiency}%`, effColor)}`);
|
|
84
85
|
}
|
|
85
86
|
else {
|
|
86
87
|
console.log(` ⏱ ${(0, i18n_1.t)('cmd.stop.noEstimate')}`);
|
|
87
88
|
}
|
|
88
89
|
// Points & Level
|
|
89
|
-
console.log(`
|
|
90
|
+
console.log((0, terminal_1.paint)(` ${(0, i18n_1.t)('score.earned', { points: String(points) })}`, 'yellow'));
|
|
90
91
|
console.log(` ${(0, i18n_1.t)('cmd.status.level', { level: String(level), score: String(data.score) })}`);
|
|
91
92
|
console.log(` ${(0, i18n_1.t)('score.progress', { current: String(levelProgress.current), needed: String(levelProgress.needed) })}`);
|
|
92
93
|
// Streak
|
package/dist/commands/summary.js
CHANGED
|
@@ -14,7 +14,7 @@ function computeDailyHours(sessions, days) {
|
|
|
14
14
|
day.setHours(0, 0, 0, 0);
|
|
15
15
|
const dayEnd = new Date(day);
|
|
16
16
|
dayEnd.setDate(dayEnd.getDate() + 1);
|
|
17
|
-
const daySessions = sessions.filter(s => s.start >= day.toISOString() && s.start < dayEnd.toISOString());
|
|
17
|
+
const daySessions = sessions.filter((s) => s.start >= day.toISOString() && s.start < dayEnd.toISOString());
|
|
18
18
|
const totalMs = daySessions.reduce((acc, s) => acc + (new Date(s.end).getTime() - new Date(s.start).getTime()), 0);
|
|
19
19
|
result.push({
|
|
20
20
|
label: day.toLocaleDateString((0, i18n_2.getLocale)() === 'zh' ? 'zh-CN' : 'en-US', {
|
|
@@ -30,33 +30,33 @@ function computeDailyHours(sessions, days) {
|
|
|
30
30
|
const i18n_2 = require("../i18n");
|
|
31
31
|
function cmdSummary(options) {
|
|
32
32
|
const data = (0, data_1.loadData)();
|
|
33
|
-
const allCompleted = data.history.filter(s => s.end); // all completed, for daily chart
|
|
33
|
+
const allCompleted = data.history.filter((s) => s.end); // all completed, for daily chart
|
|
34
34
|
let sessions = allCompleted;
|
|
35
35
|
// Time filtering
|
|
36
36
|
const now = new Date();
|
|
37
37
|
if (options.today) {
|
|
38
38
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
|
|
39
|
-
sessions = sessions.filter(s => s.start >= todayStart);
|
|
39
|
+
sessions = sessions.filter((s) => s.start >= todayStart);
|
|
40
40
|
}
|
|
41
41
|
else if (options.week) {
|
|
42
42
|
// Start from Monday
|
|
43
43
|
const dayOfWeek = now.getDay(); // 0=Sun, 1=Mon...
|
|
44
44
|
const mondayOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
45
45
|
const weekStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() - mondayOffset);
|
|
46
|
-
sessions = sessions.filter(s => s.start >= weekStart.toISOString());
|
|
46
|
+
sessions = sessions.filter((s) => s.start >= weekStart.toISOString());
|
|
47
47
|
}
|
|
48
48
|
else if (options.month) {
|
|
49
49
|
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
|
|
50
|
-
sessions = sessions.filter(s => s.start >= monthStart);
|
|
50
|
+
sessions = sessions.filter((s) => s.start >= monthStart);
|
|
51
51
|
}
|
|
52
52
|
else {
|
|
53
53
|
// Default to today
|
|
54
54
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
|
|
55
|
-
sessions = sessions.filter(s => s.start >= todayStart);
|
|
55
|
+
sessions = sessions.filter((s) => s.start >= todayStart);
|
|
56
56
|
}
|
|
57
57
|
// Tag filtering
|
|
58
58
|
if (options.tag) {
|
|
59
|
-
sessions = sessions.filter(s => s.tags.includes(options.tag));
|
|
59
|
+
sessions = sessions.filter((s) => s.tags.includes(options.tag));
|
|
60
60
|
}
|
|
61
61
|
console.log();
|
|
62
62
|
if (sessions.length === 0) {
|
|
@@ -66,7 +66,7 @@ function cmdSummary(options) {
|
|
|
66
66
|
}
|
|
67
67
|
// Compute stats
|
|
68
68
|
const totalSessions = sessions.length;
|
|
69
|
-
const durations = sessions.map(s => new Date(s.end).getTime() - new Date(s.start).getTime());
|
|
69
|
+
const durations = sessions.map((s) => new Date(s.end).getTime() - new Date(s.start).getTime());
|
|
70
70
|
const totalDuration = durations.reduce((a, b) => a + b, 0);
|
|
71
71
|
const avgDuration = totalDuration / totalSessions;
|
|
72
72
|
// Tag distribution
|
|
@@ -111,7 +111,7 @@ function cmdSummary(options) {
|
|
|
111
111
|
}
|
|
112
112
|
// Daily bar chart
|
|
113
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);
|
|
114
|
+
const maxHours = Math.max(...dailyHours.map((d) => d.hours), 1);
|
|
115
115
|
for (const day of dailyHours) {
|
|
116
116
|
const barWidth = Math.round((day.hours / maxHours) * 20);
|
|
117
117
|
const bar = '█'.repeat(barWidth);
|
package/dist/commands/sync.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.cmdSync = cmdSync;
|
|
37
|
+
const terminal_1 = require("../terminal");
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const data_1 = require("../data");
|
|
@@ -41,14 +42,14 @@ const mascot_1 = require("../mascot");
|
|
|
41
42
|
const i18n_1 = require("../i18n");
|
|
42
43
|
function cmdSync(filePath) {
|
|
43
44
|
if (!filePath || filePath.trim() === '') {
|
|
44
|
-
console.log(
|
|
45
|
+
console.log((0, terminal_1.paint)(`${(0, i18n_1.t)('cmd.sync.emptyPath')}`, 'yellow'));
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
const data = (0, data_1.loadData)();
|
|
48
49
|
const absPath = path.resolve(filePath);
|
|
49
50
|
const parentDir = path.dirname(absPath);
|
|
50
51
|
if (!fs.existsSync(parentDir)) {
|
|
51
|
-
console.log(
|
|
52
|
+
console.log((0, terminal_1.paint)(`${(0, i18n_1.t)('cmd.sync.dirNotExist', { dir: parentDir })}`, 'yellow'));
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
data.syncPath = absPath;
|
package/dist/data.js
CHANGED
package/dist/i18n.js
CHANGED
|
@@ -29,6 +29,17 @@ function resolveLocale(cliFlag, storedLang) {
|
|
|
29
29
|
// ---- String maps ----
|
|
30
30
|
const strings = {
|
|
31
31
|
zh: {
|
|
32
|
+
'tui.ready': '准备好开始了。',
|
|
33
|
+
'tui.streak': '连续 {days} 天',
|
|
34
|
+
'tui.quit': 'q/Esc 退出 · Ctrl+C 中断',
|
|
35
|
+
'tui.cancel': 'q/Esc/Ctrl+C 取消',
|
|
36
|
+
'tui.oneThing': '一次专注一件事。',
|
|
37
|
+
'tui.cancelled': '计时已取消;当前记录保留,可用 tk stop 结束。',
|
|
38
|
+
'mascot.controls': 'q 退出 · n/p 切换表情',
|
|
39
|
+
'mascot.help': '查看 MINT 的 16 种表情与眨眼动画',
|
|
40
|
+
'mascot.invalidExpression': '表情名称可选:{names}',
|
|
41
|
+
'mascot.invalidSize': '尺寸可选:full、compact、tiny',
|
|
42
|
+
'cmd.help.display': '显示设置(仅本次运行;--ascii 只替换角色字符):',
|
|
32
43
|
// Greetings
|
|
33
44
|
'greeting.lateNight': '夜深了',
|
|
34
45
|
'greeting.morning': '早上好',
|
|
@@ -214,6 +225,17 @@ const strings = {
|
|
|
214
225
|
'error.useHelp': '使用 tk help 查看帮助',
|
|
215
226
|
},
|
|
216
227
|
en: {
|
|
228
|
+
'tui.ready': 'Ready when you are.',
|
|
229
|
+
'tui.streak': '{days} day streak',
|
|
230
|
+
'tui.quit': 'q/Esc quit | Ctrl+C interrupt',
|
|
231
|
+
'tui.cancel': 'q/Esc/Ctrl+C cancel',
|
|
232
|
+
'tui.oneThing': 'One thing at a time.',
|
|
233
|
+
'tui.cancelled': 'Timer cancelled; the active record is kept. Use tk stop to finish it.',
|
|
234
|
+
'mascot.controls': 'q quit | n/p switch expression',
|
|
235
|
+
'mascot.help': 'Preview all 16 MINT expressions and blinking',
|
|
236
|
+
'mascot.invalidExpression': 'Choose an expression: {names}',
|
|
237
|
+
'mascot.invalidSize': 'Choose a size: full, compact, tiny',
|
|
238
|
+
'cmd.help.display': 'Display options (this run only; --ascii changes mascot art):',
|
|
217
239
|
// Greetings
|
|
218
240
|
'greeting.lateNight': 'Late night',
|
|
219
241
|
'greeting.morning': 'Good morning',
|
|
@@ -385,7 +407,7 @@ const strings = {
|
|
|
385
407
|
'mascot.encourage3': 'CACE: Hang in there!',
|
|
386
408
|
'mascot.focused': '😤 Focused...',
|
|
387
409
|
'mascot.celebrating': '🎉 Awesome!',
|
|
388
|
-
'mascot.disappointed':
|
|
410
|
+
'mascot.disappointed': "😴 Don't worry, try again tomorrow!",
|
|
389
411
|
// Status enhancements
|
|
390
412
|
'cmd.status.level': '📊 Lv.{level} | {score} pts',
|
|
391
413
|
'cmd.status.streak': '{fire} {days} day streak',
|