@timmy6942025/cli-timer 1.1.0 → 1.1.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 CHANGED
@@ -102,6 +102,7 @@ This launches a Bubble Tea based screen where you can change:
102
102
  - Tick rate (50-1000 ms)
103
103
  - Completion message
104
104
  - System notification on completion (default On)
105
+ - Completion sound/alarm on completion (default Off)
105
106
  - Pause key / pause alt key
106
107
  - Restart key
107
108
  - Exit key / exit alt key
@@ -113,3 +114,5 @@ Controls in settings UI:
113
114
  - `/`: filter fonts in font picker
114
115
  - `/`: filter keys in key picker
115
116
  - `Esc`/`q`: back/cancel
117
+
118
+ Note for macOS: If system notifications are inconsistent with built-in AppleScript notifications, install `terminal-notifier` (`brew install terminal-notifier`) for improved reliability.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmy6942025/cli-timer",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Simple customizable terminal timer and stopwatch",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -39,14 +39,15 @@ var defaultKeybindings = keybindings{
39
39
  }
40
40
 
41
41
  type config struct {
42
- Font string `json:"font"`
43
- CenterDisplay bool `json:"centerDisplay"`
44
- ShowHeader bool `json:"showHeader"`
45
- ShowControls bool `json:"showControls"`
46
- TickRateMs int `json:"tickRateMs"`
47
- CompletionMessage string `json:"completionMessage"`
48
- NotifyOnComplete bool `json:"notifyOnComplete"`
49
- Keybindings keybindings `json:"keybindings"`
42
+ Font string `json:"font"`
43
+ CenterDisplay bool `json:"centerDisplay"`
44
+ ShowHeader bool `json:"showHeader"`
45
+ ShowControls bool `json:"showControls"`
46
+ TickRateMs int `json:"tickRateMs"`
47
+ CompletionMessage string `json:"completionMessage"`
48
+ NotifyOnComplete bool `json:"notifyOnComplete"`
49
+ PlaySoundOnComplete bool `json:"playSoundOnComplete"`
50
+ Keybindings keybindings `json:"keybindings"`
50
51
  }
51
52
 
52
53
  type statePayload struct {
@@ -151,6 +152,7 @@ func buildMenuItems(cfg config) []list.Item {
151
152
  menuEntry{id: "tickRate", title: "Tick rate", description: fmt.Sprintf("%d ms", cfg.TickRateMs)},
152
153
  menuEntry{id: "message", title: "Completion message", description: summarizeMessage(cfg.CompletionMessage)},
153
154
  menuEntry{id: "notify", title: "System notification", description: boolText(cfg.NotifyOnComplete)},
155
+ menuEntry{id: "sound", title: "Completion sound/alarm", description: boolText(cfg.PlaySoundOnComplete)},
154
156
  menuEntry{id: "pauseKey", title: "Pause key", description: keyTokenLabel(cfg.Keybindings.PauseKey)},
155
157
  menuEntry{id: "pauseAltKey", title: "Pause alt key", description: keyTokenLabel(cfg.Keybindings.PauseAltKey)},
156
158
  menuEntry{id: "restartKey", title: "Restart key", description: keyTokenLabel(cfg.Keybindings.RestartKey)},
@@ -226,14 +228,15 @@ func normalizeKeybindings(cfg keybindings) keybindings {
226
228
 
227
229
  func normalizeConfig(cfg config) config {
228
230
  result := config{
229
- Font: defaultFont,
230
- CenterDisplay: true,
231
- ShowHeader: true,
232
- ShowControls: true,
233
- TickRateMs: defaultTickRateMs,
234
- CompletionMessage: defaultCompletionMessage,
235
- NotifyOnComplete: true,
236
- Keybindings: defaultKeybindings,
231
+ Font: defaultFont,
232
+ CenterDisplay: true,
233
+ ShowHeader: true,
234
+ ShowControls: true,
235
+ TickRateMs: defaultTickRateMs,
236
+ CompletionMessage: defaultCompletionMessage,
237
+ NotifyOnComplete: true,
238
+ PlaySoundOnComplete: false,
239
+ Keybindings: defaultKeybindings,
237
240
  }
238
241
 
239
242
  if strings.TrimSpace(cfg.Font) != "" {
@@ -249,6 +252,7 @@ func normalizeConfig(cfg config) config {
249
252
  result.CompletionMessage = normalizeCompletionMessage(cfg.CompletionMessage)
250
253
  }
251
254
  result.NotifyOnComplete = cfg.NotifyOnComplete
255
+ result.PlaySoundOnComplete = cfg.PlaySoundOnComplete
252
256
  result.Keybindings = normalizeKeybindings(cfg.Keybindings)
253
257
  return result
254
258
  }
@@ -462,6 +466,10 @@ func (m *model) applyMenuAction() tea.Cmd {
462
466
  m.payload.Config.NotifyOnComplete = !m.payload.Config.NotifyOnComplete
463
467
  m.refreshMenu()
464
468
  return nil
469
+ case "sound":
470
+ m.payload.Config.PlaySoundOnComplete = !m.payload.Config.PlaySoundOnComplete
471
+ m.refreshMenu()
472
+ return nil
465
473
  case "pauseKey":
466
474
  m.openKeyPicker("pauseKey", "Select Pause Key")
467
475
  return nil
@@ -10,14 +10,15 @@ func testPayload() statePayload {
10
10
  return statePayload{
11
11
  ConfigPath: "/tmp/cli-timer-test-config.json",
12
12
  Config: config{
13
- Font: "Standard",
14
- CenterDisplay: true,
15
- ShowHeader: true,
16
- ShowControls: true,
17
- TickRateMs: 100,
18
- CompletionMessage: "Time is up!",
19
- NotifyOnComplete: false,
20
- Keybindings: defaultKeybindings,
13
+ Font: "Standard",
14
+ CenterDisplay: true,
15
+ ShowHeader: true,
16
+ ShowControls: true,
17
+ TickRateMs: 100,
18
+ CompletionMessage: "Time is up!",
19
+ NotifyOnComplete: false,
20
+ PlaySoundOnComplete: false,
21
+ Keybindings: defaultKeybindings,
21
22
  },
22
23
  Fonts: []string{"Standard", "Big"},
23
24
  }
package/src/index.js CHANGED
@@ -39,6 +39,7 @@ const DEFAULT_CONFIG = Object.freeze({
39
39
  tickRateMs: 100,
40
40
  completionMessage: "Time is up!",
41
41
  notifyOnComplete: true,
42
+ playSoundOnComplete: false,
42
43
  keybindings: { ...DEFAULT_KEYBINDINGS }
43
44
  });
44
45
 
@@ -203,6 +204,7 @@ function normalizeConfig(raw) {
203
204
  tickRateMs: DEFAULT_CONFIG.tickRateMs,
204
205
  completionMessage: DEFAULT_CONFIG.completionMessage,
205
206
  notifyOnComplete: DEFAULT_CONFIG.notifyOnComplete,
207
+ playSoundOnComplete: DEFAULT_CONFIG.playSoundOnComplete,
206
208
  keybindings: { ...DEFAULT_KEYBINDINGS }
207
209
  };
208
210
 
@@ -225,6 +227,9 @@ function normalizeConfig(raw) {
225
227
  if (typeof raw.notifyOnComplete === "boolean") {
226
228
  next.notifyOnComplete = raw.notifyOnComplete;
227
229
  }
230
+ if (typeof raw.playSoundOnComplete === "boolean") {
231
+ next.playSoundOnComplete = raw.playSoundOnComplete;
232
+ }
228
233
  next.keybindings = normalizeKeybindings(raw.keybindings);
229
234
  if (typeof raw.font === "string") {
230
235
  const normalizedFont = normalizeFontName(raw.font);
@@ -485,11 +490,11 @@ function sendSystemNotification({ title, message }) {
485
490
 
486
491
  try {
487
492
  if (process.platform === "darwin") {
488
- const script = `display notification "${escapeAppleScriptString(safeMessage)}" with title "${escapeAppleScriptString(safeTitle)}"`;
489
- if (spawnOk("osascript", ["-e", script])) {
493
+ if (spawnOk("terminal-notifier", ["-title", safeTitle, "-message", safeMessage])) {
490
494
  return true;
491
495
  }
492
- if (spawnOk("terminal-notifier", ["-title", safeTitle, "-message", safeMessage])) {
496
+ const script = `display notification "${escapeAppleScriptString(safeMessage)}" with title "${escapeAppleScriptString(safeTitle)}"`;
497
+ if (spawnOk("osascript", ["-e", script])) {
493
498
  return true;
494
499
  }
495
500
  return false;
@@ -568,19 +573,28 @@ function sendSystemNotification({ title, message }) {
568
573
  return false;
569
574
  }
570
575
 
576
+ function playCompletionAlarm(config) {
577
+ if (!config || !config.playSoundOnComplete) {
578
+ return;
579
+ }
580
+ try {
581
+ process.stderr.write("\x07\x07\x07");
582
+ } catch (_error) {
583
+ }
584
+ }
585
+
571
586
  function notifyTimerFinished(config, initialSeconds) {
572
- if (!config || !config.notifyOnComplete) {
587
+ if (!config) {
573
588
  return;
574
589
  }
575
- const message = config.completionMessage || "Time is up!";
576
- const title = initialSeconds ? `Timer finished (${formatHms(initialSeconds)})` : "Timer finished";
577
- const notified = sendSystemNotification({ title, message });
578
- if (!notified) {
579
- try {
580
- process.stderr.write("\x07");
581
- } catch (_error) {
582
- }
590
+
591
+ if (config.notifyOnComplete) {
592
+ const message = config.completionMessage || "Time is up!";
593
+ const title = initialSeconds ? `Timer finished (${formatHms(initialSeconds)})` : "Timer finished";
594
+ sendSystemNotification({ title, message });
583
595
  }
596
+
597
+ playCompletionAlarm(config);
584
598
  }
585
599
 
586
600
  function runNonInteractiveTimer(initialSeconds, tickRateMs) {