@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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +491 -192
  3. package/dist/commands/delete.d.ts +4 -0
  4. package/dist/commands/delete.js +48 -0
  5. package/dist/commands/export.d.ts +4 -0
  6. package/dist/commands/export.js +104 -0
  7. package/dist/commands/help.d.ts +1 -0
  8. package/dist/commands/help.js +73 -0
  9. package/dist/commands/list.d.ts +5 -0
  10. package/dist/commands/list.js +42 -0
  11. package/dist/commands/mark.d.ts +1 -0
  12. package/dist/commands/mark.js +27 -0
  13. package/dist/commands/pomodoro.d.ts +6 -0
  14. package/dist/commands/pomodoro.js +135 -0
  15. package/dist/commands/resume.d.ts +4 -0
  16. package/dist/commands/resume.js +54 -0
  17. package/dist/commands/search.d.ts +1 -0
  18. package/dist/commands/search.js +36 -0
  19. package/dist/commands/start.d.ts +4 -0
  20. package/dist/commands/start.js +47 -0
  21. package/dist/commands/status.d.ts +1 -0
  22. package/dist/commands/status.js +48 -0
  23. package/dist/commands/stop.d.ts +3 -0
  24. package/dist/commands/stop.js +104 -0
  25. package/dist/commands/summary.d.ts +6 -0
  26. package/dist/commands/summary.js +128 -0
  27. package/dist/commands/sync.d.ts +1 -0
  28. package/dist/commands/sync.js +62 -0
  29. package/dist/data.d.ts +18 -0
  30. package/dist/data.js +161 -0
  31. package/dist/i18n.d.ts +7 -0
  32. package/dist/i18n.js +418 -0
  33. package/dist/index.js +142 -498
  34. package/dist/mascot.d.ts +9 -0
  35. package/dist/mascot.js +257 -0
  36. package/dist/parser.d.ts +6 -0
  37. package/dist/parser.js +45 -0
  38. package/dist/tui/countdown.d.ts +8 -0
  39. package/dist/tui/countdown.js +135 -0
  40. package/dist/tui/dashboard.d.ts +2 -0
  41. package/dist/tui/dashboard.js +135 -0
  42. package/dist/tui/index.d.ts +2 -0
  43. package/dist/tui/index.js +8 -0
  44. package/dist/tui/lifecycle.d.ts +28 -0
  45. package/dist/tui/lifecycle.js +82 -0
  46. package/dist/tui/reflection.d.ts +5 -0
  47. package/dist/tui/reflection.js +71 -0
  48. package/dist/types.d.ts +24 -0
  49. package/dist/types.js +2 -0
  50. package/dist/utils.d.ts +5 -0
  51. package/dist/utils.js +44 -0
  52. 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,5 @@
1
+ interface ReflectionResult {
2
+ text: string;
3
+ }
4
+ export declare function showReflectionInput(): Promise<ReflectionResult>;
5
+ export {};
@@ -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
+ }
@@ -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
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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.1.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": ">=18"
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
  }