@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,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.destroyScreen = destroyScreen;
|
|
4
|
+
exports.installSignalCleanup = installSignalCleanup;
|
|
5
|
+
/**
|
|
6
|
+
* Single source of truth for tearing down a blessed screen without
|
|
7
|
+
* leaving the parent TTY in a broken state.
|
|
8
|
+
*
|
|
9
|
+
* blessed calls `process.stdin.setRawMode(true)` on entry. Depending on
|
|
10
|
+
* the blessed version and the exact exit path, `screen.destroy()` does not
|
|
11
|
+
* always restore the parent TTY — leaving the user's terminal in raw mode
|
|
12
|
+
* after Ctrl+C / quit. This helper runs the documented cleanup chain
|
|
13
|
+
* (`screen.destroy` + raw-mode off + cursor reset) so every exit path
|
|
14
|
+
* (Ctrl+C, q/Esc, timeout, callback throw) behaves the same.
|
|
15
|
+
*
|
|
16
|
+
* Every cleanup step is wrapped in try/catch — screen may already be
|
|
17
|
+
* destroyed (double-fire on race conditions), stdin may not be a TTY,
|
|
18
|
+
* and `screen.program` may have been torn down by blessed itself.
|
|
19
|
+
*/
|
|
20
|
+
function destroyScreen(screen) {
|
|
21
|
+
try {
|
|
22
|
+
screen.destroy();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// already destroyed — ignore
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === 'function') {
|
|
29
|
+
process.stdin.setRawMode(false);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// stdin not writable / already detached — ignore
|
|
34
|
+
}
|
|
35
|
+
// screen.program.showCursor() / normalCursor() exist at runtime but
|
|
36
|
+
// the @types/blessed type def doesn't include them, so call via
|
|
37
|
+
// unknown to keep this file portable across blessed versions.
|
|
38
|
+
const program = screen.program;
|
|
39
|
+
try {
|
|
40
|
+
program.showCursor();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// program may be torn down — ignore
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
program.normalCursor?.();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// ditto
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let activeDispose;
|
|
53
|
+
/**
|
|
54
|
+
* Install SIGINT/SIGTERM handlers that run the standard blessed cleanup
|
|
55
|
+
* and then exit with the conventional signal status (130 / 143).
|
|
56
|
+
*
|
|
57
|
+
* Returns a `dispose()` function so callers can remove the handlers when
|
|
58
|
+
* the screen is closed normally. Installing again while a previous
|
|
59
|
+
* instance is active disposes that instance first (idempotent replace).
|
|
60
|
+
*/
|
|
61
|
+
function installSignalCleanup(screen) {
|
|
62
|
+
activeDispose?.();
|
|
63
|
+
const cleanup = (signal) => {
|
|
64
|
+
dispose();
|
|
65
|
+
destroyScreen(screen);
|
|
66
|
+
const code = signal === 'SIGINT' ? 130 : signal === 'SIGTERM' ? 143 : 1;
|
|
67
|
+
process.exit(code);
|
|
68
|
+
};
|
|
69
|
+
const onSigint = () => cleanup('SIGINT');
|
|
70
|
+
const onSigterm = () => cleanup('SIGTERM');
|
|
71
|
+
const dispose = () => {
|
|
72
|
+
process.removeListener('SIGINT', onSigint);
|
|
73
|
+
process.removeListener('SIGTERM', onSigterm);
|
|
74
|
+
if (activeDispose === dispose) {
|
|
75
|
+
activeDispose = undefined;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
process.on('SIGINT', onSigint);
|
|
79
|
+
process.on('SIGTERM', onSigterm);
|
|
80
|
+
activeDispose = dispose;
|
|
81
|
+
return dispose;
|
|
82
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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.showReflectionInput = showReflectionInput;
|
|
7
|
+
const blessed_1 = __importDefault(require("blessed"));
|
|
8
|
+
const i18n_1 = require("../i18n");
|
|
9
|
+
const lifecycle_1 = require("./lifecycle");
|
|
10
|
+
function showReflectionInput() {
|
|
11
|
+
return new Promise(resolve => {
|
|
12
|
+
const screen = blessed_1.default.screen({
|
|
13
|
+
smartCSR: true,
|
|
14
|
+
title: 'CACE TIMER',
|
|
15
|
+
fullUnicode: true,
|
|
16
|
+
});
|
|
17
|
+
// Title
|
|
18
|
+
blessed_1.default.box({
|
|
19
|
+
parent: screen,
|
|
20
|
+
top: 1,
|
|
21
|
+
left: 'center',
|
|
22
|
+
width: '100%',
|
|
23
|
+
height: 1,
|
|
24
|
+
align: 'center',
|
|
25
|
+
style: { fg: 'cyan', bold: true },
|
|
26
|
+
content: (0, i18n_1.t)('cmd.stop.reflection'),
|
|
27
|
+
});
|
|
28
|
+
// Text input area
|
|
29
|
+
const input = blessed_1.default.textarea({
|
|
30
|
+
parent: screen,
|
|
31
|
+
top: 3,
|
|
32
|
+
left: '10%',
|
|
33
|
+
width: '80%',
|
|
34
|
+
height: 5,
|
|
35
|
+
border: { type: 'line' },
|
|
36
|
+
style: {
|
|
37
|
+
border: { fg: 'cyan' },
|
|
38
|
+
fg: 'white',
|
|
39
|
+
bg: 'black',
|
|
40
|
+
focus: { border: { fg: 'green' } },
|
|
41
|
+
},
|
|
42
|
+
inputOnFocus: true,
|
|
43
|
+
});
|
|
44
|
+
// Hint
|
|
45
|
+
blessed_1.default.box({
|
|
46
|
+
parent: screen,
|
|
47
|
+
top: 9,
|
|
48
|
+
left: 'center',
|
|
49
|
+
width: '100%',
|
|
50
|
+
height: 1,
|
|
51
|
+
align: 'center',
|
|
52
|
+
style: { fg: 'gray' },
|
|
53
|
+
content: 'Enter to confirm | Esc to skip',
|
|
54
|
+
});
|
|
55
|
+
input.focus();
|
|
56
|
+
input.key('enter', () => {
|
|
57
|
+
const text = input.getValue().trim();
|
|
58
|
+
(0, lifecycle_1.destroyScreen)(screen);
|
|
59
|
+
resolve({ text });
|
|
60
|
+
});
|
|
61
|
+
input.key('escape', () => {
|
|
62
|
+
(0, lifecycle_1.destroyScreen)(screen);
|
|
63
|
+
resolve({ text: '' });
|
|
64
|
+
});
|
|
65
|
+
screen.key(['C-c'], () => {
|
|
66
|
+
(0, lifecycle_1.destroyScreen)(screen);
|
|
67
|
+
resolve({ text: '' });
|
|
68
|
+
});
|
|
69
|
+
screen.render();
|
|
70
|
+
});
|
|
71
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Mark {
|
|
2
|
+
time: string;
|
|
3
|
+
note: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Session {
|
|
6
|
+
id: string;
|
|
7
|
+
start: string;
|
|
8
|
+
end?: string;
|
|
9
|
+
task: string;
|
|
10
|
+
tags: string[];
|
|
11
|
+
marks: Mark[];
|
|
12
|
+
estimatedMinutes?: number;
|
|
13
|
+
reflection?: string;
|
|
14
|
+
pointsEarned?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface TimeKeeperData {
|
|
17
|
+
syncPath?: string;
|
|
18
|
+
lang?: string;
|
|
19
|
+
score: number;
|
|
20
|
+
streak: number;
|
|
21
|
+
lastActiveDate?: string;
|
|
22
|
+
current: Session | null;
|
|
23
|
+
history: Session[];
|
|
24
|
+
}
|
package/dist/types.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
2
|
+
export declare function generateId(): string;
|
|
3
|
+
export declare function formatDuration(ms: number): string;
|
|
4
|
+
export declare function formatTime(isoString: string): string;
|
|
5
|
+
export declare function formatDate(isoString: string): string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sleep = sleep;
|
|
4
|
+
exports.generateId = generateId;
|
|
5
|
+
exports.formatDuration = formatDuration;
|
|
6
|
+
exports.formatTime = formatTime;
|
|
7
|
+
exports.formatDate = formatDate;
|
|
8
|
+
// ============ Utilities ============
|
|
9
|
+
const i18n_1 = require("./i18n");
|
|
10
|
+
function sleep(ms) {
|
|
11
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
12
|
+
}
|
|
13
|
+
function generateId() {
|
|
14
|
+
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
15
|
+
}
|
|
16
|
+
function formatDuration(ms) {
|
|
17
|
+
const seconds = Math.floor(ms / 1000);
|
|
18
|
+
const minutes = Math.floor(seconds / 60);
|
|
19
|
+
const hours = Math.floor(minutes / 60);
|
|
20
|
+
if (hours > 0) {
|
|
21
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
22
|
+
}
|
|
23
|
+
else if (minutes > 0) {
|
|
24
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return `${seconds}s`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function formatTime(isoString) {
|
|
31
|
+
const date = new Date(isoString);
|
|
32
|
+
const locale = (0, i18n_1.getLocale)() === 'zh' ? 'zh-CN' : 'en-US';
|
|
33
|
+
return date.toLocaleString(locale, {
|
|
34
|
+
month: '2-digit',
|
|
35
|
+
day: '2-digit',
|
|
36
|
+
hour: '2-digit',
|
|
37
|
+
minute: '2-digit',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function formatDate(isoString) {
|
|
41
|
+
const date = new Date(isoString);
|
|
42
|
+
const locale = (0, i18n_1.getLocale)() === 'zh' ? 'zh-CN' : 'en-US';
|
|
43
|
+
return date.toLocaleDateString(locale);
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cacinie/cace-timer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "A minimal time tracking CLI with cute anime girl mascot",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"dev": "ts-node src/index.ts",
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"lint": "eslint src/",
|
|
17
|
+
"format": "prettier --write src/",
|
|
14
18
|
"prepublishOnly": "npm run build",
|
|
15
19
|
"prepare": "npm run build"
|
|
16
20
|
},
|
|
@@ -39,15 +43,27 @@
|
|
|
39
43
|
},
|
|
40
44
|
"homepage": "https://github.com/CacinieP/cace-timer#readme",
|
|
41
45
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
46
|
+
"node": ">=20"
|
|
43
47
|
},
|
|
44
48
|
"publishConfig": {
|
|
45
49
|
"access": "public"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^9.0.0",
|
|
53
|
+
"@types/blessed": "^0.1.27",
|
|
48
54
|
"@types/node": "^20.10.0",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^8.60.0",
|
|
56
|
+
"@typescript-eslint/parser": "^8.60.0",
|
|
57
|
+
"eslint": "^10.4.1",
|
|
58
|
+
"eslint-config-prettier": "^10.1.8",
|
|
59
|
+
"prettier": "^3.8.3",
|
|
49
60
|
"ts-node": "^10.9.2",
|
|
50
|
-
"typescript": "^5.3.0"
|
|
61
|
+
"typescript": "^5.3.0",
|
|
62
|
+
"typescript-eslint": "^8.60.0",
|
|
63
|
+
"vitest": "^4.1.7"
|
|
51
64
|
},
|
|
52
|
-
"types": "dist/index.d.ts"
|
|
65
|
+
"types": "dist/index.d.ts",
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"blessed": "^0.1.81"
|
|
68
|
+
}
|
|
53
69
|
}
|