@timmy6942025/cli-timer 1.1.15 → 1.1.17

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 (3) hide show
  1. package/README.md +10 -0
  2. package/package.json +1 -1
  3. package/src/index.js +187 -5
package/README.md CHANGED
@@ -45,6 +45,16 @@ timer 5 min 2 sec
45
45
 
46
46
  By default, timer and stopwatch output is centered in the terminal.
47
47
 
48
+ ### Update CLI Timer
49
+
50
+ To update globally and verify you are on latest:
51
+
52
+ ```bash
53
+ timer update
54
+ ```
55
+
56
+ `timer update` auto-detects whether your current install came from Bun, npm, or pnpm, runs the matching global update command, then checks your installed version against npm `latest`.
57
+
48
58
  ## Controls
49
59
 
50
60
  - `p` or `Spacebar`: Pause/Resume
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmy6942025/cli-timer",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
4
4
  "description": "Simple customizable terminal timer and stopwatch",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -12,6 +12,7 @@ const SETTINGS_STATE_PATH = path.join(CONFIG_DIR, "settings-state.json");
12
12
  const DEFAULT_FONT = "Standard";
13
13
  const TIMER_SAMPLE_TEXT = "01:23:45";
14
14
  const MIN_FIGLET_WIDTH = 120;
15
+ const PACKAGE_NAME = "@timmy6942025/cli-timer";
15
16
  const PRINTABLE_ASCII_CANDIDATES = Object.freeze(
16
17
  Array.from({ length: 94 }, (_, index) => String.fromCharCode(33 + index))
17
18
  );
@@ -91,6 +92,22 @@ function exitAlternateScreen() {
91
92
  process.stdout.write("\x1b[?1049l");
92
93
  }
93
94
 
95
+ function disableLineWrap() {
96
+ process.stdout.write("\x1b[?7l");
97
+ }
98
+
99
+ function enableLineWrap() {
100
+ process.stdout.write("\x1b[?7h");
101
+ }
102
+
103
+ function enableMouseCapture() {
104
+ process.stdout.write("\x1b[?1000h\x1b[?1006h");
105
+ }
106
+
107
+ function disableMouseCapture() {
108
+ process.stdout.write("\x1b[?1000l\x1b[?1006l");
109
+ }
110
+
94
111
  function formatHms(totalSeconds) {
95
112
  const hours = Math.floor(totalSeconds / 3600);
96
113
  const minutes = Math.floor((totalSeconds % 3600) / 60);
@@ -578,6 +595,13 @@ function toDisplayLines(text) {
578
595
  return lines.length > 0 ? lines : [""];
579
596
  }
580
597
 
598
+ function writeFrameLines(lines) {
599
+ const safeLines = Array.isArray(lines) && lines.length > 0 ? lines : [""];
600
+ const terminalHeight = process.stdout.rows || safeLines.length;
601
+ const visibleLines = safeLines.slice(0, Math.max(1, terminalHeight));
602
+ process.stdout.write(visibleLines.join("\n"));
603
+ }
604
+
581
605
  function writeCenteredBlock(lines) {
582
606
  const safeLines = lines.length > 0 ? lines : [""];
583
607
  const terminalWidth = process.stdout.columns || 120;
@@ -593,8 +617,7 @@ function writeCenteredBlock(lines) {
593
617
  output += "\n".repeat(padTop);
594
618
  }
595
619
  output += safeLines.map((line) => `${leftPrefix}${line}`).join("\n");
596
- output += "\n";
597
- process.stdout.write(output);
620
+ writeFrameLines(toDisplayLines(output));
598
621
  }
599
622
 
600
623
  function writeCenteredBlockWithTop(topLines, centerLines) {
@@ -617,8 +640,7 @@ function writeCenteredBlockWithTop(topLines, centerLines) {
617
640
  output += "\n".repeat(padTop);
618
641
  }
619
642
  output += safeCenter.map((line) => `${leftPrefix}${line}`).join("\n");
620
- output += "\n";
621
- process.stdout.write(output);
643
+ writeFrameLines(toDisplayLines(output));
622
644
  }
623
645
 
624
646
  function drawFrame({ mode, seconds, paused, config, done }) {
@@ -654,7 +676,7 @@ function drawFrame({ mode, seconds, paused, config, done }) {
654
676
  writeCenteredBlockWithTop(topLines, centerLines);
655
677
  } else {
656
678
  const lines = [...topLines, ...centerLines];
657
- process.stdout.write(`${lines.join("\n")}\n`);
679
+ writeFrameLines(lines);
658
680
  }
659
681
  }
660
682
 
@@ -675,6 +697,147 @@ function spawnOk(command, args, options) {
675
697
  }
676
698
  }
677
699
 
700
+ function readCurrentPackageVersion() {
701
+ try {
702
+ const packagePath = path.join(PROJECT_ROOT, "package.json");
703
+ const text = fs.readFileSync(packagePath, "utf8");
704
+ const parsed = JSON.parse(text);
705
+ if (!parsed || typeof parsed.version !== "string") {
706
+ return null;
707
+ }
708
+ return parsed.version.trim() || null;
709
+ } catch (_error) {
710
+ return null;
711
+ }
712
+ }
713
+
714
+ function readLatestPublishedVersion() {
715
+ try {
716
+ const result = spawnSync("npm", ["view", PACKAGE_NAME, "version", "--registry=https://registry.npmjs.org"], {
717
+ encoding: "utf8",
718
+ stdio: ["ignore", "pipe", "pipe"]
719
+ });
720
+ if (result.error || result.status !== 0) {
721
+ return null;
722
+ }
723
+ const version = String(result.stdout || "").trim();
724
+ return version || null;
725
+ } catch (_error) {
726
+ return null;
727
+ }
728
+ }
729
+
730
+ function detectInstallMethod() {
731
+ try {
732
+ const realRoot = fs.realpathSync(PROJECT_ROOT).replace(/\\/g, "/").toLowerCase();
733
+ if (realRoot.includes("/.bun/install/global/node_modules/")) {
734
+ return "bun";
735
+ }
736
+ if (
737
+ realRoot.includes("/.local/share/pnpm/global/") ||
738
+ realRoot.includes("/pnpm/global/") ||
739
+ realRoot.includes("/.pnpm/")
740
+ ) {
741
+ return "pnpm";
742
+ }
743
+ if (realRoot.includes("/node_modules/")) {
744
+ return "npm";
745
+ }
746
+ } catch (_error) {
747
+ }
748
+
749
+ const userAgent = String(process.env.npm_config_user_agent || "").toLowerCase();
750
+ if (userAgent.startsWith("bun/")) {
751
+ return "bun";
752
+ }
753
+ if (userAgent.startsWith("pnpm/")) {
754
+ return "pnpm";
755
+ }
756
+ if (userAgent.startsWith("npm/")) {
757
+ return "npm";
758
+ }
759
+
760
+ return "npm";
761
+ }
762
+
763
+ function getUpdateCommand(method) {
764
+ const packageTarget = `${PACKAGE_NAME}@latest`;
765
+ if (method === "bun") {
766
+ return {
767
+ command: "bun",
768
+ args: ["add", "-g", packageTarget],
769
+ printable: `bun add -g ${packageTarget}`
770
+ };
771
+ }
772
+ if (method === "pnpm") {
773
+ return {
774
+ command: "pnpm",
775
+ args: ["add", "-g", packageTarget],
776
+ printable: `pnpm add -g ${packageTarget}`
777
+ };
778
+ }
779
+ return {
780
+ command: "npm",
781
+ args: ["i", "-g", packageTarget],
782
+ printable: `npm i -g ${packageTarget}`
783
+ };
784
+ }
785
+
786
+ function runSelfUpdate() {
787
+ const method = detectInstallMethod();
788
+ const updateCommand = getUpdateCommand(method);
789
+ const beforeVersion = readCurrentPackageVersion();
790
+ const latestVersion = readLatestPublishedVersion();
791
+
792
+ process.stdout.write(`Detected install method: ${method}\n`);
793
+ if (beforeVersion) {
794
+ process.stdout.write(`Current version: ${beforeVersion}\n`);
795
+ }
796
+ if (latestVersion) {
797
+ process.stdout.write(`Latest version: ${latestVersion}\n`);
798
+ }
799
+ process.stdout.write(`Running: ${updateCommand.printable}\n\n`);
800
+
801
+ let result;
802
+ try {
803
+ result = spawnSync(updateCommand.command, updateCommand.args, { stdio: "inherit" });
804
+ } catch (error) {
805
+ process.stderr.write(`Failed to run updater: ${error.message}\n`);
806
+ process.exitCode = 1;
807
+ return;
808
+ }
809
+
810
+ if (result.error || result.status !== 0) {
811
+ process.stderr.write(`Update command failed: ${updateCommand.printable}\n`);
812
+ process.exitCode = 1;
813
+ return;
814
+ }
815
+
816
+ const installedAfter = readCurrentPackageVersion();
817
+ const latestAfter = readLatestPublishedVersion();
818
+
819
+ process.stdout.write("\nUpdate finished.\n");
820
+ if (installedAfter) {
821
+ process.stdout.write(`Installed version: ${installedAfter}\n`);
822
+ }
823
+ if (latestAfter) {
824
+ process.stdout.write(`Latest version: ${latestAfter}\n`);
825
+ }
826
+
827
+ if (installedAfter && latestAfter && installedAfter === latestAfter) {
828
+ process.stdout.write("You are on the latest version.\n");
829
+ return;
830
+ }
831
+
832
+ if (latestAfter) {
833
+ process.stderr.write("Update completed, but this running install is not on the latest version yet.\n");
834
+ process.exitCode = 1;
835
+ return;
836
+ }
837
+
838
+ process.stdout.write("Could not verify latest version from npm, but update command completed.\n");
839
+ }
840
+
678
841
  function sleepSync(ms) {
679
842
  const durationMs = Math.max(0, Number(ms) || 0);
680
843
  if (durationMs <= 0) {
@@ -967,6 +1130,8 @@ function runClock({ mode, initialSeconds, config }) {
967
1130
  let lastDrawState = "";
968
1131
  let hasExited = false;
969
1132
  let didEnterAlternateScreen = false;
1133
+ let didDisableLineWrap = false;
1134
+ let didEnableMouseCapture = false;
970
1135
 
971
1136
  const stdin = process.stdin;
972
1137
 
@@ -1062,6 +1227,12 @@ function runClock({ mode, initialSeconds, config }) {
1062
1227
  }
1063
1228
  stdin.pause();
1064
1229
  showCursor();
1230
+ if (didEnableMouseCapture) {
1231
+ disableMouseCapture();
1232
+ }
1233
+ if (didDisableLineWrap) {
1234
+ enableLineWrap();
1235
+ }
1065
1236
  if (didEnterAlternateScreen) {
1066
1237
  exitAlternateScreen();
1067
1238
  } else {
@@ -1127,6 +1298,10 @@ function runClock({ mode, initialSeconds, config }) {
1127
1298
  stdin.on("data", onKeypress);
1128
1299
  enterAlternateScreen();
1129
1300
  didEnterAlternateScreen = true;
1301
+ disableLineWrap();
1302
+ didDisableLineWrap = true;
1303
+ enableMouseCapture();
1304
+ didEnableMouseCapture = true;
1130
1305
  hideCursor();
1131
1306
 
1132
1307
  draw(true);
@@ -1255,6 +1430,8 @@ function printUsage() {
1255
1430
  process.stdout.write(" Example: timer 5 min 2 sec\n\n");
1256
1431
  process.stdout.write("Settings\n");
1257
1432
  process.stdout.write(" timer settings\n\n");
1433
+ process.stdout.write("Update\n");
1434
+ process.stdout.write(" timer update\n\n");
1258
1435
  process.stdout.write("Controls\n");
1259
1436
  process.stdout.write(" Defaults: p/Space Pause-Resume | r Restart | f Random Style | q/e/Ctrl+C Exit\n");
1260
1437
  process.stdout.write(" Keybindings are customizable in `timer settings`.\n\n");
@@ -1283,6 +1460,11 @@ function runTimer(args) {
1283
1460
  return;
1284
1461
  }
1285
1462
 
1463
+ if (args[0] === "update") {
1464
+ runSelfUpdate();
1465
+ return;
1466
+ }
1467
+
1286
1468
  if (args[0] === "style") {
1287
1469
  if (args.length === 1 || (args.length === 2 && args[1] === "--all")) {
1288
1470
  const currentFont = getFontFromConfig();