@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.
- package/README.md +10 -0
- package/package.json +1 -1
- 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
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
|
|
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
|
|
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
|
-
|
|
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();
|