Shows when you sent each message, under it in the transcript.

Shows when you sent each message, as a dim line under it in the transcript:
> can you add a test for this
sent 19:04
The first message of a new day carries the date too (sent Mon 5 Oct, 09:12). Only messages you send get a stamp, typed or from Remote Control; task notifications and messages from other agents don't.
It's a function hook, which is early access, so start Claude Code with:
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude
Messages are stamped when they're first drawn, so in a resumed session the older messages show the time the session resumed, not when they were sent.
Run the tests with claude plugin test plugins/message-time.
Inspired by the time plugin in diegorv/claude-functions-hook. This is an independent implementation, not a copy of that code.
hooks/register.tsx 61 lines1// Puts a dim "sent HH:MM" line under each message you send. Under, not
2// above: the engine draws a blank line above a message row, so a stamp above
3// it would sit against the previous reply instead of the message.
4//
5// A message is stamped the first time it is drawn, which is when you send it.
6// The stamp is remembered by the row's requestId, so redraws and resizes keep
7// it. Rows from before the module loaded (a resumed session, a hot reload) get
8// stamped with the load time, since the engine doesn't hand us the real one.
9import type { Register } from "claude-code";
10
11// Origins that are you: typed at the prompt, or sent from Remote Control.
12// Task notifications, peers, channels and the rest aren't "sent" by you.
13const YOURS = new Set(["composer", "bridge"]);
14
15// Lines the stamp up with the message text, past the engine's "> " marker.
16const INDENT = 2;
17
18const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
19const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
20
21const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`);
22
23const dayOf = (d: Date) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
24
25// "19:04", or "Mon 5 Oct, 09:12" when `withDate`.
26export const stampText = (ms: number, withDate: boolean) => {
27 const d = new Date(ms);
28 const time = `${pad(d.getHours())}:${pad(d.getMinutes())}`;
29 return withDate ? `${DAYS[d.getDay()]} ${d.getDate()} ${MONTHS[d.getMonth()]}, ${time}` : time;
30};
31
32export const register: Register = (on) => {
33 const sentAt = new Map<string, string>();
34 let lastDay: string | undefined; // the day of the last message stamped
35
36 on("ui.render", { component: "UserMessage" }, async ($, e, next) => {
37 if (!YOURS.has(e.props.origin.kind)) return next(e);
38
39 let stamp = sentAt.get(e.requestId);
40 if (stamp === undefined) {
41 const now = await $.clock.now(); // resolves a Promise<number>
42 const day = dayOf(new Date(now));
43 // The date shows on the first message of a new day, not the session's first.
44 stamp = stampText(now, lastDay !== undefined && day !== lastDay);
45 lastDay = day;
46 sentAt.set(e.requestId, stamp);
47 }
48
49 const row = await next(e);
50 const { Box, Text } = $.ui.resolve(e);
51 return (
52 <Box flexDirection="column">
53 {row}
54 <Box marginLeft={INDENT}>
55 <Text dimColor>{`sent ${stamp}`}</Text>
56 </Box>
57 </Box>
58 );
59 });
60};
61