Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,14 +570,14 @@ npx thermoworks metrics [--host HOST] [--port N] [--device SN] [--interval N]

None.

### `thermoworks events [--device SERIAL] [--type TYPE] [--limit N] [--json]`
### `thermoworks events [--device SERIAL] [--type TYPE] [--limit N] [--since ISO] [--until ISO] [--json]`

Show device event history

**Usage**

```bash
npx thermoworks events [--device SERIAL] [--type TYPE] [--limit N] [--json]
npx thermoworks events [--device SERIAL] [--type TYPE] [--limit N] [--since ISO] [--until ISO] [--json]
```

**Options**
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/command-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,13 @@ export const commandDefinitions: readonly CommandDefinition[] = [
{
name: "events",
summary: "Show device event history",
usage: "events [--device SERIAL] [--type TYPE] [--limit N] [--json]",
usageLines: ["events Show device event history (alarms, status changes)"],
usage:
"events [--device SERIAL] [--type TYPE] [--limit N] [--since ISO] [--until ISO] [--json]",
usageLines: [
"events Show device event history (alarms, status changes)",
" --since ISO Only show events at or after this time",
" --until ISO Only show events at or before this time",
],
supportsJson: true,
handler: ({ args, options }: CommandContext) => events(parseEventsArgs(args.slice(1)), options),
},
Expand Down
28 changes: 27 additions & 1 deletion packages/cli/src/commands/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ export interface EventsCommandOptions {
device?: string;
type?: string;
limit?: number;
since?: Date;
until?: Date;
}

/** Parse an ISO date flag value, exiting with a clear error when invalid. */
function parseDateFlag(flag: string, value: string | undefined): Date {
if (!value) {
console.error(`${flag} requires an ISO date value`);
process.exit(1);
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
console.error(
`Invalid ${flag} value: ${value}. Use an ISO date, for example 2026-06-07T12:00:00Z.`,
);
process.exit(1);
}
return date;
}

/** Map a numeric severity to a labeled color badge. */
Expand All @@ -28,7 +46,7 @@ export function formatSeverityBadge(severity: number): string {

/**
* Parse events-specific flags from remaining CLI args.
* Handles: --device SERIAL, --type TYPE, --limit N
* Handles: --device SERIAL, --type TYPE, --limit N, --since ISO, --until ISO
*/
export function parseEventsArgs(args: string[]): EventsCommandOptions {
const options: EventsCommandOptions = {};
Expand All @@ -49,6 +67,12 @@ export function parseEventsArgs(args: string[]): EventsCommandOptions {
options.limit = parsed;
}
i++;
} else if (arg === "--since") {
options.since = parseDateFlag("--since", next);
i++;
} else if (arg === "--until") {
options.until = parseDateFlag("--until", next);
i++;
}
}

Expand All @@ -71,6 +95,8 @@ export async function events(
const eventList = await client.getEvents({
deviceId: commandOptions.device,
eventType: commandOptions.type,
startTime: commandOptions.since,
endTime: commandOptions.until,
limit: commandOptions.limit,
});

Expand Down
43 changes: 43 additions & 0 deletions packages/cli/tests/events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ describe("parseEventsArgs", () => {
expect(result.limit).toBe(20);
});

it("parses --since and --until flags as dates", () => {
const result = parseEventsArgs([
"--since",
"2026-06-07T12:00:00Z",
"--until",
"2026-06-07T13:00:00Z",
]);
expect(result.since?.toISOString()).toBe("2026-06-07T12:00:00.000Z");
expect(result.until?.toISOString()).toBe("2026-06-07T13:00:00.000Z");
});

it("exits when a date filter is invalid", () => {
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
throw new Error("process.exit");
});
expect(() => parseEventsArgs(["--since", "not-a-date"])).toThrow("process.exit");
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid --since value"));
exitSpy.mockRestore();
});

it("ignores invalid --limit values", () => {
const result = parseEventsArgs(["--limit", "abc"]);
expect(result.limit).toBeUndefined();
Expand Down Expand Up @@ -229,6 +249,8 @@ describe("events", () => {
expect(mockGetEvents).toHaveBeenCalledWith({
deviceId: "NODE5",
eventType: undefined,
startTime: undefined,
endTime: undefined,
limit: undefined,
});
});
Expand All @@ -242,6 +264,8 @@ describe("events", () => {
expect(mockGetEvents).toHaveBeenCalledWith({
deviceId: undefined,
eventType: "alarm",
startTime: undefined,
endTime: undefined,
limit: undefined,
});
});
Expand All @@ -255,10 +279,29 @@ describe("events", () => {
expect(mockGetEvents).toHaveBeenCalledWith({
deviceId: undefined,
eventType: undefined,
startTime: undefined,
endTime: undefined,
limit: 10,
});
});

it("passes date filters to SDK", async () => {
mockGetCredentials.mockResolvedValue({ email: "a@b.com", password: "pw" });
mockGetEvents.mockResolvedValue([]);
const since = new Date("2026-06-07T12:00:00Z");
const until = new Date("2026-06-07T13:00:00Z");

await events({ since, until }, { json: false });

expect(mockGetEvents).toHaveBeenCalledWith({
deviceId: undefined,
eventType: undefined,
startTime: since,
endTime: until,
limit: undefined,
});
});

it("shows plural 'events' for multiple results", async () => {
mockGetCredentials.mockResolvedValue({ email: "a@b.com", password: "pw" });
vi.spyOn(Date, "now").mockReturnValue(new Date("2026-06-07T12:05:00Z").getTime());
Expand Down