@blastin-dev/clocktopus-cli 0.1.2 → 0.1.4
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 +21 -6
- package/dist/src/commands/clock.d.ts +26 -4
- package/dist/src/commands/clock.d.ts.map +1 -1
- package/dist/src/commands/clock.js +99 -6
- package/dist/src/commands/login.d.ts.map +1 -1
- package/dist/src/commands/login.js +5 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +17 -6
- package/dist/src/lib/api.d.ts.map +1 -1
- package/dist/src/lib/api.js +28 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -38,23 +38,38 @@ Clear stored credentials.
|
|
|
38
38
|
|
|
39
39
|
Display the current authenticated user.
|
|
40
40
|
|
|
41
|
-
### `clocktopus clock in`
|
|
41
|
+
### `clocktopus clock (in|out)`
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
```
|
|
44
|
+
clocktopus clock (in|out) [--ago <duration> | --at <time>]
|
|
45
|
+
```
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
Record the start or end of a work session. Defaults to "now" when no
|
|
48
|
+
flag is passed.
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
| Option | Description |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `--ago <duration>` | Backdate by a duration from now (`15m`, `1h`, `1h30m`). Mutually exclusive with `--at`. |
|
|
53
|
+
| `--at <time>` | Backdate to an absolute wall-clock time today (`HH:mm` or `HH:mm:ss`). |
|
|
48
54
|
|
|
49
55
|
### `clocktopus clock status`
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
```
|
|
58
|
+
clocktopus clock status [-d <YYYY-MM-DD>]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Show clock signals for a given date.
|
|
62
|
+
|
|
63
|
+
| Option | Description |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `-d, --date <date>` | Show signals for a specific date in `YYYY-MM-DD` format. Defaults to today. |
|
|
52
66
|
|
|
53
67
|
## Token Storage
|
|
54
68
|
|
|
55
69
|
Credentials are stored in a platform-specific config directory:
|
|
56
70
|
|
|
57
|
-
- **macOS**: `~/Library/Preferences/clocktopus-cli-nodejs/
|
|
71
|
+
- **macOS**: `~/Library/Preferences/clocktopus-cli-nodejs/conf
|
|
72
|
+
ig.json`
|
|
58
73
|
- **Linux**: `~/.config/clocktopus-cli-nodejs/config.json`
|
|
59
74
|
- **Windows**: `%APPDATA%/clocktopus-cli-nodejs/config.json`
|
|
60
75
|
|
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Record a clock-in signal for today
|
|
2
|
+
* Record a clock-in signal for today.
|
|
3
|
+
*
|
|
4
|
+
* Accepts the same backdate flags as `clock out`:
|
|
5
|
+
* --ago <duration> e.g. "45m", "1h", "1h30m"
|
|
6
|
+
* --at <HH:mm> absolute wall-clock time in the user's timezone
|
|
7
|
+
*
|
|
8
|
+
* The server silently clamps the requested time if it would fall outside
|
|
9
|
+
* the allowed window (before start-of-today, before the day's last
|
|
10
|
+
* clock-out, or after the first existing time entry on the day).
|
|
3
11
|
*/
|
|
4
|
-
export declare function clockInCommand(
|
|
12
|
+
export declare function clockInCommand(options?: {
|
|
13
|
+
ago?: string;
|
|
14
|
+
at?: string;
|
|
15
|
+
}): Promise<void>;
|
|
5
16
|
/**
|
|
6
|
-
* Record a clock-out signal for today
|
|
17
|
+
* Record a clock-out signal for today.
|
|
18
|
+
*
|
|
19
|
+
* Accepts optional backdate flags:
|
|
20
|
+
* --ago <duration> e.g. "45m", "1h", "1h30m"
|
|
21
|
+
* --at <HH:mm> absolute wall-clock time in the user's timezone
|
|
22
|
+
*
|
|
23
|
+
* The two flags are mutually exclusive. The server silently clamps the
|
|
24
|
+
* requested time if it would predate the session's clock-in or the
|
|
25
|
+
* session's last time entry.
|
|
7
26
|
*/
|
|
8
|
-
export declare function clockOutCommand(
|
|
27
|
+
export declare function clockOutCommand(options?: {
|
|
28
|
+
ago?: string;
|
|
29
|
+
at?: string;
|
|
30
|
+
}): Promise<void>;
|
|
9
31
|
/**
|
|
10
32
|
* Show clock signals status for a given date (default: today)
|
|
11
33
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/commands/clock.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/commands/clock.ts"],"names":[],"mappings":"AAgFA;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,OAAO,CAAC,IAAI,CAAC,CA0Ef;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,OAAO,CAAC,IAAI,CAAC,CAsEf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB"}
|
|
@@ -16,8 +16,30 @@ const ClockSignalResponseSchema = z.object({
|
|
|
16
16
|
signalTimestamp: z.string(),
|
|
17
17
|
alreadyExists: z.boolean(),
|
|
18
18
|
blockedReason: z.enum(["already_clocked_in", "not_clocked_in"]).nullable(),
|
|
19
|
+
clamped: z.boolean().optional(),
|
|
20
|
+
requestedTime: z.string().nullable().optional(),
|
|
21
|
+
reopened: z.boolean().optional(),
|
|
19
22
|
}),
|
|
20
23
|
});
|
|
24
|
+
/**
|
|
25
|
+
* Parses "15m", "1h", "1h30m" → whole minutes. Mirrors
|
|
26
|
+
* `packages/core/src/utils/duration.ts` (the CLI is a standalone package
|
|
27
|
+
* and doesn't depend on @repo/core).
|
|
28
|
+
*/
|
|
29
|
+
function parseDurationToMinutes(input) {
|
|
30
|
+
const trimmed = input.trim().toLowerCase();
|
|
31
|
+
const match = /^(?:(\d+)h)?(?:(\d+)m)?$/.exec(trimmed);
|
|
32
|
+
if (!match || (!match[1] && !match[2])) {
|
|
33
|
+
throw new Error(`Invalid duration "${input}". Use formats like 15m, 1h, or 1h30m.`);
|
|
34
|
+
}
|
|
35
|
+
const hours = match[1] ? Number(match[1]) : 0;
|
|
36
|
+
const minutes = match[2] ? Number(match[2]) : 0;
|
|
37
|
+
const total = hours * 60 + minutes;
|
|
38
|
+
if (total <= 0) {
|
|
39
|
+
throw new Error(`Duration must be greater than zero: "${input}".`);
|
|
40
|
+
}
|
|
41
|
+
return total;
|
|
42
|
+
}
|
|
21
43
|
/**
|
|
22
44
|
* Schema for clock signals list response
|
|
23
45
|
*/
|
|
@@ -44,14 +66,43 @@ function formatSignalType(type) {
|
|
|
44
66
|
return type === "clock_in" ? "Clock In" : "Clock Out";
|
|
45
67
|
}
|
|
46
68
|
/**
|
|
47
|
-
* Record a clock-in signal for today
|
|
69
|
+
* Record a clock-in signal for today.
|
|
70
|
+
*
|
|
71
|
+
* Accepts the same backdate flags as `clock out`:
|
|
72
|
+
* --ago <duration> e.g. "45m", "1h", "1h30m"
|
|
73
|
+
* --at <HH:mm> absolute wall-clock time in the user's timezone
|
|
74
|
+
*
|
|
75
|
+
* The server silently clamps the requested time if it would fall outside
|
|
76
|
+
* the allowed window (before start-of-today, before the day's last
|
|
77
|
+
* clock-out, or after the first existing time entry on the day).
|
|
48
78
|
*/
|
|
49
|
-
export async function clockInCommand() {
|
|
79
|
+
export async function clockInCommand(options = {}) {
|
|
50
80
|
if (!checkAuth()) {
|
|
51
81
|
process.exit(1);
|
|
52
82
|
}
|
|
83
|
+
if (options.ago && options.at) {
|
|
84
|
+
console.error("Cannot use --ago and --at together. Pick one.");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const body = { signalType: "clock_in" };
|
|
88
|
+
if (options.ago) {
|
|
89
|
+
try {
|
|
90
|
+
body.minutesAgo = parseDurationToMinutes(options.ago);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (options.at) {
|
|
98
|
+
if (!/^\d{2}:\d{2}(:\d{2})?$/.test(options.at)) {
|
|
99
|
+
console.error(`Invalid time "${options.at}". Use HH:mm or HH:mm:ss format.`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
body.effectiveTime = options.at;
|
|
103
|
+
}
|
|
53
104
|
try {
|
|
54
|
-
const data = await post("/api/clock-signal",
|
|
105
|
+
const data = await post("/api/clock-signal", body);
|
|
55
106
|
const response = ClockSignalResponseSchema.parse(data);
|
|
56
107
|
if (response.signal.alreadyExists) {
|
|
57
108
|
console.log(`\nAlready clocked in - must clock out first.`);
|
|
@@ -60,11 +111,21 @@ export async function clockInCommand() {
|
|
|
60
111
|
console.log(` Time: ${response.signal.effectiveTime}`);
|
|
61
112
|
console.log(` Timezone: ${response.signal.timezone}`);
|
|
62
113
|
}
|
|
114
|
+
else if (response.signal.reopened) {
|
|
115
|
+
console.log(`\nPrevious session reopened — clock-out removed.`);
|
|
116
|
+
console.log(` Clocked in since:`);
|
|
117
|
+
console.log(` Date: ${response.signal.workDate}`);
|
|
118
|
+
console.log(` Time: ${response.signal.effectiveTime}`);
|
|
119
|
+
console.log(` Timezone: ${response.signal.timezone}`);
|
|
120
|
+
}
|
|
63
121
|
else {
|
|
64
122
|
console.log(`\nClock In recorded!`);
|
|
65
123
|
console.log(` Date: ${response.signal.workDate}`);
|
|
66
124
|
console.log(` Time: ${response.signal.effectiveTime}`);
|
|
67
125
|
console.log(` Timezone: ${response.signal.timezone}`);
|
|
126
|
+
if (response.signal.clamped && response.signal.requestedTime) {
|
|
127
|
+
console.log(` Adjusted: you asked for ${response.signal.requestedTime}, but that's outside the allowed window for this day.`);
|
|
128
|
+
}
|
|
68
129
|
}
|
|
69
130
|
}
|
|
70
131
|
catch (error) {
|
|
@@ -83,14 +144,43 @@ export async function clockInCommand() {
|
|
|
83
144
|
}
|
|
84
145
|
}
|
|
85
146
|
/**
|
|
86
|
-
* Record a clock-out signal for today
|
|
147
|
+
* Record a clock-out signal for today.
|
|
148
|
+
*
|
|
149
|
+
* Accepts optional backdate flags:
|
|
150
|
+
* --ago <duration> e.g. "45m", "1h", "1h30m"
|
|
151
|
+
* --at <HH:mm> absolute wall-clock time in the user's timezone
|
|
152
|
+
*
|
|
153
|
+
* The two flags are mutually exclusive. The server silently clamps the
|
|
154
|
+
* requested time if it would predate the session's clock-in or the
|
|
155
|
+
* session's last time entry.
|
|
87
156
|
*/
|
|
88
|
-
export async function clockOutCommand() {
|
|
157
|
+
export async function clockOutCommand(options = {}) {
|
|
89
158
|
if (!checkAuth()) {
|
|
90
159
|
process.exit(1);
|
|
91
160
|
}
|
|
161
|
+
if (options.ago && options.at) {
|
|
162
|
+
console.error("Cannot use --ago and --at together. Pick one.");
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
const body = { signalType: "clock_out" };
|
|
166
|
+
if (options.ago) {
|
|
167
|
+
try {
|
|
168
|
+
body.minutesAgo = parseDurationToMinutes(options.ago);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (options.at) {
|
|
176
|
+
if (!/^\d{2}:\d{2}(:\d{2})?$/.test(options.at)) {
|
|
177
|
+
console.error(`Invalid time "${options.at}". Use HH:mm or HH:mm:ss format.`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
body.effectiveTime = options.at;
|
|
181
|
+
}
|
|
92
182
|
try {
|
|
93
|
-
const data = await post("/api/clock-signal",
|
|
183
|
+
const data = await post("/api/clock-signal", body);
|
|
94
184
|
const response = ClockSignalResponseSchema.parse(data);
|
|
95
185
|
if (response.signal.alreadyExists) {
|
|
96
186
|
console.log(`\nNot clocked in - must clock in first.`);
|
|
@@ -106,6 +196,9 @@ export async function clockOutCommand() {
|
|
|
106
196
|
console.log(` Date: ${response.signal.workDate}`);
|
|
107
197
|
console.log(` Time: ${response.signal.effectiveTime}`);
|
|
108
198
|
console.log(` Timezone: ${response.signal.timezone}`);
|
|
199
|
+
if (response.signal.clamped && response.signal.requestedTime) {
|
|
200
|
+
console.log(` Adjusted: you asked for ${response.signal.requestedTime}, but that's before this session's earliest allowed time.`);
|
|
201
|
+
}
|
|
109
202
|
}
|
|
110
203
|
}
|
|
111
204
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/commands/login.ts"],"names":[],"mappings":"AAKA,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/commands/login.ts"],"names":[],"mappings":"AAKA,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAmFlD"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { get } from "../lib/api.js";
|
|
2
2
|
import { pollForToken, requestDeviceCode, sleep } from "../lib/auth.js";
|
|
3
|
-
import {
|
|
3
|
+
import { setToken } from "../lib/config.js";
|
|
4
4
|
import { UserSchema } from "../lib/validators.js";
|
|
5
5
|
export async function loginCommand() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// If the user is already logged in we still start a fresh device flow
|
|
7
|
+
// — `setToken` on success overwrites the existing token. We don't
|
|
8
|
+
// clear the old token up-front, so a failed/cancelled/timed-out login
|
|
9
|
+
// leaves the previously-valid session intact.
|
|
10
10
|
console.log("Starting device authorization...\n");
|
|
11
11
|
try {
|
|
12
12
|
const deviceCode = await requestDeviceCode();
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAgGA,wBAAgB,GAAG,IAAI,IAAI,CAE1B"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { Command } from "commander";
|
|
3
|
+
import { z } from "zod";
|
|
2
4
|
import { clockInCommand, clockOutCommand, clockStatusCommand, } from "./commands/clock.js";
|
|
3
5
|
import { loginCommand } from "./commands/login.js";
|
|
4
6
|
import { logoutCommand } from "./commands/logout.js";
|
|
5
7
|
import { whoamiCommand } from "./commands/whoami.js";
|
|
6
|
-
import { ENVIRONMENTS, setRuntimeEnvironment
|
|
8
|
+
import { ENVIRONMENTS, setRuntimeEnvironment } from "./lib/config.js";
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const parsedJson = z
|
|
11
|
+
.object({ version: z.string() })
|
|
12
|
+
.safeParse(require("../../package.json"));
|
|
13
|
+
const version = parsedJson.success ? parsedJson.data.version : "0.0.0";
|
|
7
14
|
const program = new Command();
|
|
8
15
|
program
|
|
9
16
|
.name("clocktopus")
|
|
10
17
|
.description("CLI for Clocktopus time tracking")
|
|
11
|
-
.version(
|
|
18
|
+
.version(version)
|
|
12
19
|
.option("-e, --env <environment>", "Use environment (dev or prod)")
|
|
13
20
|
.hook("preAction", (thisCommand) => {
|
|
14
21
|
const opts = thisCommand.opts();
|
|
@@ -38,12 +45,16 @@ const clock = program
|
|
|
38
45
|
.description("Record clock-in and clock-out signals");
|
|
39
46
|
clock
|
|
40
47
|
.command("in")
|
|
41
|
-
.description("Record clock-in for today")
|
|
42
|
-
.
|
|
48
|
+
.description("Record clock-in for today (optionally backdated)")
|
|
49
|
+
.option("--ago <duration>", "Backdate by a duration relative to now (e.g. 45m, 1h, 1h30m)")
|
|
50
|
+
.option("--at <time>", "Backdate to an absolute time in your timezone (HH:mm or HH:mm:ss)")
|
|
51
|
+
.action((options) => clockInCommand(options));
|
|
43
52
|
clock
|
|
44
53
|
.command("out")
|
|
45
|
-
.description("Record clock-out for today")
|
|
46
|
-
.
|
|
54
|
+
.description("Record clock-out for today (optionally backdated)")
|
|
55
|
+
.option("--ago <duration>", "Backdate by a duration relative to now (e.g. 45m, 1h, 1h30m)")
|
|
56
|
+
.option("--at <time>", "Backdate to an absolute time in your timezone (HH:mm or HH:mm:ss)")
|
|
57
|
+
.action((options) => clockOutCommand(options));
|
|
47
58
|
clock
|
|
48
59
|
.command("status")
|
|
49
60
|
.description("Show clock signals for a specific date")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAEA,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,qBAAa,QAAS,SAAQ,KAAK;IAExB,MAAM,EAAE,MAAM;IACd,UAAU,EAAE,MAAM;gBADlB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,MAAM;CAKnB;AAED,wBAAsB,OAAO,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAEA,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,qBAAa,QAAS,SAAQ,KAAK;IAExB,MAAM,EAAE,MAAM;IACd,UAAU,EAAE,MAAM;gBADlB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,MAAM;CAKnB;AAED,wBAAsB,OAAO,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAqCZ;AAuBD,wBAAsB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,UAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAE3E;AAED,wBAAsB,IAAI,CAAC,CAAC,EAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,aAAa,UAAO,GACnB,OAAO,CAAC,CAAC,CAAC,CAEZ"}
|
package/dist/src/lib/api.js
CHANGED
|
@@ -29,10 +29,37 @@ export async function request(path, options = {}) {
|
|
|
29
29
|
});
|
|
30
30
|
if (!response.ok) {
|
|
31
31
|
const errorText = await response.text();
|
|
32
|
-
|
|
32
|
+
// The frontend's `handleApiError` serializes errors as
|
|
33
|
+
// `{ error: { message, stack? }, timestamp, path }`. Pull the
|
|
34
|
+
// `message` out so the user sees the actual reason (e.g. "Signal
|
|
35
|
+
// time cannot be in the future…") rather than a raw JSON blob.
|
|
36
|
+
throw new ApiError(response.status, response.statusText, extractErrorMessage(errorText) ?? errorText);
|
|
33
37
|
}
|
|
34
38
|
return response.json();
|
|
35
39
|
}
|
|
40
|
+
function extractErrorMessage(body) {
|
|
41
|
+
if (!body)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(body);
|
|
45
|
+
if (parsed && typeof parsed === "object") {
|
|
46
|
+
const record = parsed;
|
|
47
|
+
const err = record.error;
|
|
48
|
+
if (err && typeof err === "object") {
|
|
49
|
+
const msg = err.message;
|
|
50
|
+
if (typeof msg === "string" && msg.length > 0)
|
|
51
|
+
return msg;
|
|
52
|
+
}
|
|
53
|
+
if (typeof record.message === "string" && record.message.length > 0) {
|
|
54
|
+
return record.message;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Not JSON — fall through and let the caller show the raw text.
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
36
63
|
export async function get(path, authenticated = true) {
|
|
37
64
|
return request(path, { method: "GET", authenticated });
|
|
38
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blastin-dev/clocktopus-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clocktopus": "./dist/bin/clocktopus.js"
|
|
@@ -11,16 +11,17 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"commander": "^13.0.0",
|
|
13
13
|
"conf": "^13.0.0",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
14
|
+
"date-fns": "4.1.0",
|
|
15
|
+
"zod": "4.1.12"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "22.15.3",
|
|
19
|
-
"eslint": "
|
|
19
|
+
"eslint": "9.37.0",
|
|
20
|
+
"eslint-plugin-import-x": "^4.16.1",
|
|
20
21
|
"typescript": "5.9.2",
|
|
21
22
|
"@repo/eslint-config": "0.0.0",
|
|
22
|
-
"@repo/
|
|
23
|
-
"@repo/
|
|
23
|
+
"@repo/prettier-config": "0.1.0",
|
|
24
|
+
"@repo/typescript-config": "0.0.0"
|
|
24
25
|
},
|
|
25
26
|
"prettier": "@repo/prettier-config",
|
|
26
27
|
"scripts": {
|