SLOPSHOPPER

message-time

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

newrows
v0.2.0no licenseupdated 2026-10-04jonathan-fielding/dotclaude/plugins/message-time
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · message-time
⟨Claude Code's own drawing⟩ sent 01:53 ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Your message
⟨Claude Code's own drawing⟩ sent 01:53
README

message-time

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.

Credit

Inspired by the time plugin in diegorv/claude-functions-hook. This is an independent implementation, not a copy of that code.

Source 1 files
hooks/register.tsx 61 lines
1// 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