From 950fd1913fbb7a4e702fccd832a0075517551a34 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 25 Feb 2026 15:26:00 -0500 Subject: [PATCH] matrix-js: show local-time verification timestamps via shared formatter --- extensions/matrix-js/src/cli.test.ts | 66 ++++++++++++++++++++++++++++ extensions/matrix-js/src/cli.ts | 26 +++++++++-- src/plugin-sdk/index.ts | 5 +++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/extensions/matrix-js/src/cli.test.ts b/extensions/matrix-js/src/cli.test.ts index 580b48be8de..60bc8e1dbe7 100644 --- a/extensions/matrix-js/src/cli.test.ts +++ b/extensions/matrix-js/src/cli.test.ts @@ -1,4 +1,5 @@ import { Command } from "commander"; +import { formatZonedTimestamp } from "openclaw/plugin-sdk"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const bootstrapMatrixVerificationMock = vi.fn(); @@ -19,6 +20,10 @@ function buildProgram(): Command { return program; } +function formatExpectedLocalTimestamp(value: string): string { + return formatZonedTimestamp(new Date(value), { displaySeconds: true }) ?? value; +} + describe("matrix-js CLI verification commands", () => { beforeEach(async () => { vi.resetModules(); @@ -79,4 +84,65 @@ describe("matrix-js CLI verification commands", () => { expect(process.exitCode).toBe(0); }); + + it("prints local timezone timestamps for verify status output", async () => { + const recoveryCreatedAt = "2026-02-25T20:10:11.000Z"; + getMatrixVerificationStatusMock.mockResolvedValue({ + verified: true, + userId: "@bot:example.org", + deviceId: "DEVICE123", + backupVersion: "1", + recoveryKeyStored: true, + recoveryKeyCreatedAt: recoveryCreatedAt, + pendingVerifications: 0, + }); + const program = buildProgram(); + + await program.parseAsync(["matrix-js", "verify", "status"], { from: "user" }); + + expect(console.log).toHaveBeenCalledWith( + `Recovery key created at: ${formatExpectedLocalTimestamp(recoveryCreatedAt)}`, + ); + }); + + it("prints local timezone timestamps for verify bootstrap and device output", async () => { + const recoveryCreatedAt = "2026-02-25T20:10:11.000Z"; + const verifiedAt = "2026-02-25T20:14:00.000Z"; + bootstrapMatrixVerificationMock.mockResolvedValue({ + success: true, + verification: { + verified: true, + userId: "@bot:example.org", + deviceId: "DEVICE123", + recoveryKeyCreatedAt: recoveryCreatedAt, + }, + crossSigning: { + published: true, + masterKeyPublished: true, + selfSigningKeyPublished: true, + userSigningKeyPublished: true, + }, + pendingVerifications: 0, + cryptoBootstrap: {}, + }); + verifyMatrixRecoveryKeyMock.mockResolvedValue({ + success: true, + userId: "@bot:example.org", + deviceId: "DEVICE123", + backupVersion: "1", + recoveryKeyCreatedAt: recoveryCreatedAt, + verifiedAt, + }); + const program = buildProgram(); + + await program.parseAsync(["matrix-js", "verify", "bootstrap"], { from: "user" }); + await program.parseAsync(["matrix-js", "verify", "device", "valid-key"], { from: "user" }); + + expect(console.log).toHaveBeenCalledWith( + `Recovery key created at: ${formatExpectedLocalTimestamp(recoveryCreatedAt)}`, + ); + expect(console.log).toHaveBeenCalledWith( + `Verified at: ${formatExpectedLocalTimestamp(verifiedAt)}`, + ); + }); }); diff --git a/extensions/matrix-js/src/cli.ts b/extensions/matrix-js/src/cli.ts index a5094d1442c..e2544f7ecd8 100644 --- a/extensions/matrix-js/src/cli.ts +++ b/extensions/matrix-js/src/cli.ts @@ -1,4 +1,5 @@ import type { Command } from "commander"; +import { formatZonedTimestamp } from "openclaw/plugin-sdk"; import { bootstrapMatrixVerification, getMatrixVerificationStatus, @@ -22,6 +23,24 @@ function markCliFailure(): void { process.exitCode = 1; } +function formatLocalTimestamp(value: string | null | undefined): string | null { + if (!value) { + return null; + } + const parsed = new Date(value); + if (!Number.isFinite(parsed.getTime())) { + return value; + } + return formatZonedTimestamp(parsed, { displaySeconds: true }) ?? value; +} + +function printTimestamp(label: string, value: string | null | undefined): void { + const formatted = formatLocalTimestamp(value); + if (formatted) { + console.log(`${label}: ${formatted}`); + } +} + function printVerificationStatus(status: { verified: boolean; userId: string | null; @@ -45,9 +64,7 @@ function printVerificationStatus(status: { console.log("Run 'openclaw matrix-js verify device ' to verify this device."); } console.log(`Recovery key stored: ${status.recoveryKeyStored ? "yes" : "no"}`); - if (status.recoveryKeyCreatedAt) { - console.log(`Recovery key created at: ${status.recoveryKeyCreatedAt}`); - } + printTimestamp("Recovery key created at", status.recoveryKeyCreatedAt); console.log(`Pending verifications: ${status.pendingVerifications}`); } @@ -126,6 +143,7 @@ export function registerMatrixJsCli(params: { program: Command }): void { console.log( `Cross-signing published: ${result.crossSigning.published ? "yes" : "no"} (master=${result.crossSigning.masterKeyPublished ? "yes" : "no"}, self=${result.crossSigning.selfSigningKeyPublished ? "yes" : "no"}, user=${result.crossSigning.userSigningKeyPublished ? "yes" : "no"})`, ); + printTimestamp("Recovery key created at", result.verification.recoveryKeyCreatedAt); console.log(`Pending verifications: ${result.pendingVerifications}`); if (!result.success) { markCliFailure(); @@ -164,6 +182,8 @@ export function registerMatrixJsCli(params: { program: Command }): void { if (result.backupVersion) { console.log(`Backup version: ${result.backupVersion}`); } + printTimestamp("Recovery key created at", result.recoveryKeyCreatedAt); + printTimestamp("Verified at", result.verifiedAt); } else { console.error(`Verification failed: ${result.error ?? "unknown error"}`); markCliFailure(); diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 7faa2341dc0..337f02a3cc5 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -244,6 +244,11 @@ export type { PersistentDedupeOptions, } from "./persistent-dedupe.js"; export { formatErrorMessage } from "../infra/errors.js"; +export { + formatUtcTimestamp, + formatZonedTimestamp, + resolveTimezone, +} from "../infra/format-time/format-datetime.js"; export { DEFAULT_WEBHOOK_BODY_TIMEOUT_MS, DEFAULT_WEBHOOK_MAX_BODY_BYTES,