SLOPSHOPPER

clock

A small live clock in the footer mode labels, ticking on a timer instead of on activity

newspinnerstatusprocesstimer
★ 8v0.2.0no licenseupdated 2026-10-06ryanthedev/dot-config/claude/mods/clock
A shopper browsing a rack in a slop shop
README

dot-config

.config/i3
.config/nvim

Figuring out what works for me

Used ideas from people like ThePrimeagen and others.

Things needed -nerd fonts -zsh -oh-my-zsh

  • powerline10k installed with zsh

-zoxide -fzf -tmux -tpm -tmux session wizard (should probably roll my own)

Source 2 files
hooks/register.ts 67 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4// 🐇 follow the white rabbit
5export const label = (hhmm: string) => `\u{1F407} ${hhmm}`
6
7const time = atom({ plugin: 'clock', key: 'time' } as const, '')
8
9// The module's environment may not share the host's time zone, so the
10// offset comes from the host's own `date` once per load.
11const offsetMs = async (run: (argv: readonly string[]) => Promise<{ exitCode: number; stdout: string }>) => {
12  try {
13    const { exitCode, stdout } = await run(['date', '+%z'])
14    const m = /^([+-])(\d{2})(\d{2})/.exec(stdout.trim())
15    if (exitCode !== 0 || !m) return 0
16    const sign = m[1] === '-' ? -1 : 1
17    return sign * (Number(m[2]) * 60 + Number(m[3])) * 60_000
18  } catch {
19    return 0
20  }
21}
22
23export const format = (epochMs: number, offset: number) => {
24  const d = new Date(epochMs + offset)
25  const hh = String(d.getUTCHours()).padStart(2, '0')
26  const mm = String(d.getUTCMinutes()).padStart(2, '0')
27  return `${hh}:${mm}`
28}
29
30export const register: Register = on => {
31  on('session.start', async ($, e, next) => {
32    const started = await next(e)
33    if (!e.isInteractive) return started
34
35    // Re-read hourly so a DST change or a new time zone shows up without a reload.
36    let offset = await offsetMs(argv => $.process.run(argv))
37    let offsetReadAt = await $.clock.now()
38    let shown = ''
39    const tick = async () => {
40      const now = await $.clock.now()
41      if (now - offsetReadAt >= 3_600_000) {
42        offset = await offsetMs(argv => $.process.run(argv))
43        offsetReadAt = now
44      }
45      const text = format(now, offset)
46      if (text === shown) return
47      shown = text
48      await update($, time, () => text)
49    }
50
51    // Earlier versions pinned the clock with $.ui.status, which the engine
52    // prefixes with its own icon; clear any such entry a reload left behind.
53    $.ui.status(undefined)
54    await tick()
55    $.clock.every(5_000, () => void tick())
56
57    return started
58  })
59
60  // Drawn as a footer mode label, beside the engine's own (`focus`, ...).
61  on('ui.render', { component: 'SessionMode' }, async ($, e, next) => {
62    const now = await read($, time)
63    if (now === '') return next(e)
64    return next({ ...e, props: { ...e.props, modes: [...e.props.modes, label(now)] } })
65  })
66}
67
types/index.d.ts 9 lines
1/** The time the footer label shows, as HH:MM in local time; '' before the first tick. */
2export type ClockTime = string
3
4declare module 'claude-code' {
5  interface PluginState {
6    clock: { time: ClockTime }
7  }
8}
9