ClaudeMods
☰
KO
● 0 명 접속 중 · 조회 0 회
후원프로젝트 제출
GitHub 저장소 · 작성자 JDWorkdog

turn-timestamp

모든 완료된 응답 아래에 현지 날짜와 시간을 표시하는 작은 Claude Code 모드입니다. 방금 클릭한 탭이 5분 전 것인지 5일 전 것인지 한눈에 알 수 있습니다.

JDWorkdog@JDWorkdog

JDWorkdog/ai-workbench/tree/main/mods/turn-timestamp

번역 완료

이 mod 소개

turn-timestamp: 모든 Claude Code 답변 아래에 날짜와 시간 표시

모든 완료된 응답 아래에 현지 날짜와 시간을 표시하는 작은 Claude Code 모드입니다. 방금 클릭한 탭이 5분 전 것인지 5일 전 것인지 한눈에 알 수 있습니다.

Completed Fri, Oct 2, 2026, 5:41:07 AM EDT

터미널, VS Code 확장, 데스크톱 앱의 Code 탭에서 작동합니다. 작은 파일 네 개로 구성되어 있고 TypeScript는 100줄이 되지 않습니다. 요청을 받은 Claude Code가 한 번의 작업에서 첫 버전을 작성했습니다.

문제

여러 VS Code 탭에서 Claude Code를 실행하거나 터미널 세션을 몇 개 열어 두면 모든 트랜스크립트가 똑같아 보입니다. 특정 응답이 언제 도착했는지 UI에서는 알려 주지 않습니다. 탭을 열고 마지막 메시지를 읽어도 오늘 작업인지 지난주 작업인지 알 수 없습니다.

이 모드가 하는 일

Claude Code는 이제 mods를 지원합니다. 함수 훅으로 작성되어 세션 안에서 실행되는 플러그인으로, 패널·상태 줄·토스트를 그리거나 이벤트에 반응할 수 있습니다. 그중 turn.complete 이벤트는 응답이 끝날 때마다 발생합니다. 이 이벤트의 훅은 짧은 텍스트 한 줄을 반환할 수 있고, 엔진이 답변 아래에 표시합니다.

이 모드는 turn.complete에 훅을 걸고 시계를 읽어 스탬프를 반환합니다. 반환된 텍스트를 그리지 않는 화면도 있습니다. 터미널과 VS Code는 표시하지만 데스크톱 앱의 Code 탭은 표시하지 않습니다. 그래서 터미널과 데스크톱 Code 탭이 지원하는 AbovePrompt 렌더링 컴포넌트로 프롬프트 입력 바로 위에 띠를 그려 최신 스탬프도 보여 줍니다. 두 경로를 함께 사용하므로 어떤 화면에서도 스탬프가 접히거나 숨겨지지 않습니다.

  • 스탬프는 주 답변에만 붙습니다. 서브에이전트 턴은 주 턴 안에서 끝나므로 건너뛰며, 한 응답에서 타임스탬프가 여러 개 쌓이지 않습니다.
  • 턴이 끝난 이유에 따라 라벨이 바뀝니다: Completed, Interrupted, Ended with error, Refused.
  • 코드를 편집하지 않고 /config에서 바꿀 수 있는 설정은 두 가지입니다: display(auto, answer, band, status, log)와 timeZone.
  • 트랜스크립트 자체는 다시 쓰지 않습니다. 스탬프는 표시용일 뿐입니다.

설치

Claude Code 2.1.286 이상이 필요합니다. mods API는 Anthropic이 early access로 표시했으므로 변경될 수 있습니다.

가장 빠른 방법: clone 없이 파일 하나

INSTALL.md는 자체 포함 설치 프로그램입니다. 부록에 모드 파일 세 개와 어시스턴트가 따라 할 단계별 안내(버전 확인, 목록 확인, 작성, 검증, 그리고 원하면 백업을 포함한 사용자 수준 설정 편집)가 들어 있습니다. Claude Code를 열고 다음을 붙여 넣으세요.

Fetch https://raw.githubusercontent.com/JDWorkdog/ai-workbench/main/mods/turn-timestamp/INSTALL.md and follow its section "Agent setup instructions" exactly. Begin by asking me whether I want the mod on for every project or for the current project only.

파일을 로컬에 저장한 뒤 INSTALL.md 파일을 끝까지 읽고 “Agent setup instructions” 절을 정확히 따르세요.라고 붙여 넣어도 됩니다. 아래 선택지는 수동으로 실행하는 방법입니다.

옵션 1: 터미널 세션 하나에서 시험하기

git clone https://github.com/JDWorkdog/ai-workbench.git
claude --plugin-dir ~/path/to/ai-workbench/mods/turn-timestamp

옵션 2: VS Code와 데스크톱 앱을 포함해 어디서나 로드하기

VS Code 확장과 데스크톱 앱은 명령줄 플래그를 받을 수 없으므로 사용자 설정 env 블록의 CLAUDE_CODE_PLUGIN_DIRS 변수에서 플러그인 폴더를 읽습니다. ~/.claude/settings.json은 사용자 수준이므로 여기서 한 번만 수정하면 이 컴퓨터의 모든 프로젝트와 VS Code 탭에 적용됩니다. 탭을 다시 잃어버리지 않으려는 것이 목적이라면 이 설정을 사용하세요. 기존 내용과 합쳐서 다음을 추가합니다.

{
  "env": {
    "CLAUDE_CODE_PLUGIN_DIRS": "~/path/to/ai-workbench/mods/turn-timestamp"
  }
}

macOS와 Linux에서는 여러 폴더를 :로, Windows에서는 ;로 구분합니다. Claude Code를 다시 시작하면 새 세션마다 스탬프가 표시됩니다. 이 변수는 사용자 설정이나 프로세스 환경에서만 읽고 프로젝트의 .claude/settings.json에서는 읽지 않으므로 프로젝트별 설정은 없습니다. 한 프로젝트에서만 사용하려면 그 프로젝트를 시작할 때 옵션 1을 쓰세요.

옵션 3: Claude에게 만들게 하기

Claude Code를 열고 다음과 같이 요청하세요.

plugin-authoring 스킬을 불러와 turn.complete 훅으로 완료된 답변 아래에 현지 날짜와 시간을 출력하는 모드를 만들어 주세요. 서브에이전트 턴은 건너뛰세요.

Claude는 모드를 핫 리로드 개발 폴더에 작성하고, 세션에서 핫 리로드를 켤지 한 번 묻습니다. 그러면 다음 답변부터 스탬프가 표시됩니다. 이 모드도 같은 방식으로 만들었습니다.

확인

claude plugin validate mods/turn-timestamp

출력에서 hooks: turn.complete, ui.render{component=AbovePrompt}, state writes: turn-timestamp.last 줄, Validation passed를 확인할 수 있어야 합니다.

코드

.claude-plugin/plugin.json

{
  "name": "turn-timestamp",
  "version": "0.3.0",
  "description": "Shows the date and time of every completed answer so you can tell how fresh a tab is.",
  "author": {
    "name": "John Workman"
  },
  "types": "./types/index.d.ts",
  "userConfig": {
    "display": {
      "type": "string",
      "title": "Where to show the stamp",
      "description": "auto draws a line beneath each answer (terminal, VS Code) and a band above the prompt with the latest stamp (terminal, desktop Code tab). answer, band, status and log each pick one channel; status pins a plugin status line, which the desktop folds into a collapsed notice.",
      "default": "auto",
      "options": ["auto", "answer", "band", "status", "log"]
    },
    "timeZone": {
      "type": "string",
      "title": "Time zone",
      "description": "An IANA zone such as America/New_York. Empty uses the machine's zone, or TZ when set.",
      "default": ""
    }
  }
}

hooks/hooks.json

{ "modules": ["./register.tsx"] }

types/index.d.ts(띠가 읽는 하나의 값에 대한 상태 계약)

export type TurnStamp = string

declare module 'claude-code' {
  interface PluginState {
    'turn-timestamp': { last: TurnStamp | null }
  }
}

hooks/register.tsx

import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'

import type { TurnStamp } from '../types'

// The latest stamp, kept in session state so the band survives a hot reload.
const last = atom<TurnStamp | null>({ plugin: 'turn-timestamp', key: 'last' } as const, null)

// Formats the wall-clock time as "Fri, Oct 2, 2026, 3:42:07 PM EDT".
// An invalid or empty zone falls back to the sandbox's own zone, then to ISO.
function stamp(ms: number, timeZone: string | undefined): string {
  const d = new Date(ms)
  const opts: Intl.DateTimeFormatOptions = {
    weekday: 'short',
    month: 'short',
    day: 'numeric',
    year: 'numeric',
    hour: 'numeric',
    minute: '2-digit',
    second: '2-digit',
    timeZoneName: 'short',
  }
  try {
    return new Intl.DateTimeFormat('en-US', timeZone ? { ...opts, timeZone } : opts).format(d)
  } catch {
    try {
      return new Intl.DateTimeFormat('en-US', opts).format(d)
    } catch {
      return d.toISOString()
    }
  }
}

export const register: Register = (on, options) => {
  const display = String(options.display ?? 'auto')
  const pinnedZone = String(options.timeZone ?? '').trim()
  const wants = (channel: string) =>
    display === channel || (display === 'auto' && (channel === 'answer' || channel === 'band'))

  on('turn.complete', async ($, e, next) => {
    const result = await next(e)

    // Subagent turns end inside the main turn; only stamp the main answer.
    if (e.agentId !== undefined) return result

    const now = await $.clock.now()
    const zone = pinnedZone || (await $.env.get('TZ')) || undefined
    const label =
      e.reason === 'answer'
        ? 'Completed'
        : e.reason === 'aborted'
          ? 'Interrupted'
          : e.reason === 'error'
            ? 'Ended with error'
            : 'Refused'
    const line = `${label} ${stamp(now, zone)}`

    // The band above the prompt reads this; writing it redraws the band.
    if (wants('band')) await update($, last, () => line)

    // A plugin status line under the prompt. The desktop shows it as a
    // collapsed "Claude Code notice", so it is off unless chosen.
    if (wants('status')) $.ui.status(line)

    // A dim transcript row, for hosts that draw none of the above.
    if (wants('log')) $.ui.log(line)

    // Drawn beneath the answer where the surface supports it (terminal, VS Code).
    return wants('answer') ? { ...result, text: line } : result
  })

  // The band directly above the prompt input. Raised on the terminal and the
  // desktop Code tab; VS Code does not raise it and shows the answer line instead.
  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
    if (!wants('band')) return next(e)
    const line = await read($, last)
    if (e.props.hasSurvey || line === null) return next(e)

    const { Box, Text } = $.ui.resolve(e)
    return (
      <Box>
        <Text dimColor>
          {e.props.isWorking ? `Working. Last answer: ${line}` : `Last answer: ${line}`}
        </Text>
      </Box>
    )
  })
}

읽는 법: register는 on 함수와 매니페스트의 userConfig에서 온 options(기본값이 채워짐)를 받습니다. on('turn.complete', hook)로 훅을 추가합니다. 각 훅에는 $(엔진 인터페이스), e(이벤트 입력), next(나머지 체인)가 전달됩니다. next(e)를 호출하면 엔진이 정상적으로 완료되고 이 이벤트의 결과인 { text }를 돌려줍니다. 다른 text를 반환하면 터미널과 VS Code가 답변 아래에 표시합니다. AbovePrompt의 ui.render 훅은 띠를 그립니다. 세션 상태에서 최신 스탬프를 읽고, 첫 번째 훅의 쓰기가 띠를 다시 그리게 합니다.

맞춤 설정

  • 시간대를 고정합니다. /config의 timeZone(또는 ~/.claude/settings.json의 pluginConfigs."turn-timestamp".options)에 America/New_York 같은 IANA 영역을 지정합니다.
  • 표시 위치를 고릅니다. display를 answer로 설정하면 답변 아래 줄만, band로 설정하면 프롬프트 위 띠만, status로 설정하면 플러그인 상태 줄(데스크톱에서는 접힌 알림으로 표시되므로 기본값이 아님)만, log로 설정하면 흐린 트랜스크립트 줄만 표시합니다. 기본값 auto는 답변 줄과 띠를 모두 그립니다.
  • 스탬프를 짧게 합니다. 형식 옵션에서 weekday와 year를 빼면 Oct 2, 5:41 AM EDT처럼 표시됩니다.
  • 소요 시간을 추가합니다. 이벤트에 e.durationMs가 있습니다. Math.round(e.durationMs / 1000)초를 줄에 덧붙이세요.
  • 토큰 비용을 추가합니다. 턴에 토큰이 있었다면 e.usage에 해당 턴의 토큰 수가 들어 있습니다.

세션 실행 중 파일을 저장하면 훅이 핫 리로드되므로 다음 답변에서 바뀐 결과를 볼 수 있습니다.

타임스탬프 이상의 의미

흥미로운 점은 스탬프가 아니라 필요한 코드가 이렇게 적다는 것입니다. mods API는 프롬프트, 도구 호출, 모델 단계, 턴 경계, UI 렌더링을 위한 타입 이벤트를 제공하고 세션 안에 핫 리로드 루프도 들어 있습니다. 턴 경계에서 Claude Code가 자동으로 하길 바라는 일은 이제 훅 하나로 구현할 수 있고, 그 훅을 Claude에게 작성하게 할 수도 있습니다.

INSTALL.md 동기화

INSTALL.md 부록에는 세 소스 파일이 원문 그대로 들어 있습니다. register.tsx、index.d.ts、hooks.json、plugin.json을 바꾸면 부록도 맞춰 업데이트하세요. 그렇지 않으면 단일 파일 설치 프로그램이 폴더보다 오래된 모드를 전달합니다.

크레딧

Claude Code의 plugin-authoring 스킬로 만들었습니다. AI Workbench starter kit의 일부입니다.

설치

먼저 작성자의 README에서 marketplace와 플러그인 이름을 확인하세요. 저장소 구조에 따라 명령어가 달라질 수 있습니다.

claude plugin marketplace add JDWorkdog/ai-workbench
claude plugin install turn-timestamp
원문 / README

turn-timestamp: a date and time under every Claude Code answer

A tiny Claude Code mod that prints the local date and time beneath every completed response, so you can tell at a glance whether the tab you just clicked on is from five minutes ago or five days ago.

Completed Fri, Oct 2, 2026, 5:41:07 AM EDT

It works in the terminal, the VS Code extension, and the desktop app's Code tab. It is four small files and under a hundred lines of TypeScript, and Claude Code wrote the first version in one turn when asked.

The problem

If you run Claude Code in several VS Code tabs, or keep a handful of terminal sessions open, the transcripts all look the same. Nothing in the UI tells you when a given response landed. You click into a tab, read the last message, and have no idea if you are looking at today's work or something from last week.

What the mod does

Claude Code now supports mods: plugins written as function hooks that run inside the session and can draw panes, status lines, toasts, or react to events. One of those events is turn.complete, which fires every time a response finishes. A hook on that event can return a short line of text, and the engine shows it beneath the answer.

This mod hooks turn.complete, reads the clock, and returns a stamp. Not every surface draws that returned text: the terminal and VS Code do, the desktop app's Code tab does not. So the mod also draws a band directly above the prompt input with the latest stamp, using the AbovePrompt render component, which the terminal and the desktop Code tab support. Between the two channels every surface shows a visible stamp without anything collapsed or hidden.

  • Only the main answer gets a stamp. Subagent turns end inside the main turn and are skipped, so you never see a pile of timestamps from one response.
  • The label changes with how the turn ended: Completed, Interrupted, Ended with error, or Refused.
  • Two settings, changeable from /config without editing code: display (auto, answer, band, status, or log) and timeZone.
  • The transcript itself is never rewritten. The stamp is display-only.

Install

You need Claude Code 2.1.286 or newer. The mods API is marked early access by Anthropic, so expect it to change.

Fastest: one file, no clone

INSTALL.md is a self-contained installer. It carries the three mod files in an appendix plus step-by-step instructions for your assistant: version check, inventory, write, validate, and (if you want it everywhere) one user-level settings edit with a backup. Open Claude Code anywhere and paste:

Fetch https://raw.githubusercontent.com/JDWorkdog/ai-workbench/main/mods/turn-timestamp/INSTALL.md and follow its section "Agent setup instructions" exactly. Begin by asking me whether I want the mod on for every project or for the current project only.

Or save the file locally and paste Read the file INSTALL.md in full, then follow its section "Agent setup instructions" exactly. The options below are the manual equivalents.

Option 1: try it in one terminal session

git clone https://github.com/JDWorkdog/ai-workbench.git
claude --plugin-dir ~/path/to/ai-workbench/mods/turn-timestamp

Option 2: load it everywhere, including VS Code and the desktop app

The VS Code extension and desktop app cannot take a command-line flag, so they read plugin folders from the CLAUDE_CODE_PLUGIN_DIRS variable in the env block of your user settings. Because ~/.claude/settings.json is user-level, one edit here covers every project on your machine and every VS Code tab, which is what you want if the goal is never losing track of a tab again. Add this (merge with what is already there):

{
  "env": {
    "CLAUDE_CODE_PLUGIN_DIRS": "~/path/to/ai-workbench/mods/turn-timestamp"
  }
}

Separate several folders with : on macOS and Linux, ; on Windows. Restart Claude Code and every new session will carry the stamp. This variable is only read from user settings or the process environment, never from a project's .claude/settings.json, so there is no per-project version of this option. If you want the stamp in a single project only, use Option 1 when you start that project.

Option 3: have Claude build it for you

Open Claude Code and ask:

Load the plugin-authoring skill and build me a mod that prints the local date and time beneath every completed answer using the turn.complete hook. Skip subagent turns.

Claude writes the mod into a hot-reloading dev folder, asks once whether to enable hot reloading for the session, and the stamp appears on its next reply. That is how this one was made.

Verify it

claude plugin validate mods/turn-timestamp

You should see hooks: turn.complete, ui.render{component=AbovePrompt} in the output, a state writes: turn-timestamp.last line, and Validation passed.

The code

.claude-plugin/plugin.json

{
  "name": "turn-timestamp",
  "version": "0.3.0",
  "description": "Shows the date and time of every completed answer so you can tell how fresh a tab is.",
  "author": {
    "name": "John Workman"
  },
  "types": "./types/index.d.ts",
  "userConfig": {
    "display": {
      "type": "string",
      "title": "Where to show the stamp",
      "description": "auto draws a line beneath each answer (terminal, VS Code) and a band above the prompt with the latest stamp (terminal, desktop Code tab). answer, band, status and log each pick one channel; status pins a plugin status line, which the desktop folds into a collapsed notice.",
      "default": "auto",
      "options": ["auto", "answer", "band", "status", "log"]
    },
    "timeZone": {
      "type": "string",
      "title": "Time zone",
      "description": "An IANA zone such as America/New_York. Empty uses the machine's zone, or TZ when set.",
      "default": ""
    }
  }
}

hooks/hooks.json

{ "modules": ["./register.tsx"] }

types/index.d.ts (the state contract for the one value the band reads)

export type TurnStamp = string

declare module 'claude-code' {
  interface PluginState {
    'turn-timestamp': { last: TurnStamp | null }
  }
}

hooks/register.tsx

import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'

import type { TurnStamp } from '../types'

// The latest stamp, kept in session state so the band survives a hot reload.
const last = atom<TurnStamp | null>({ plugin: 'turn-timestamp', key: 'last' } as const, null)

// Formats the wall-clock time as "Fri, Oct 2, 2026, 3:42:07 PM EDT".
// An invalid or empty zone falls back to the sandbox's own zone, then to ISO.
function stamp(ms: number, timeZone: string | undefined): string {
  const d = new Date(ms)
  const opts: Intl.DateTimeFormatOptions = {
    weekday: 'short',
    month: 'short',
    day: 'numeric',
    year: 'numeric',
    hour: 'numeric',
    minute: '2-digit',
    second: '2-digit',
    timeZoneName: 'short',
  }
  try {
    return new Intl.DateTimeFormat('en-US', timeZone ? { ...opts, timeZone } : opts).format(d)
  } catch {
    try {
      return new Intl.DateTimeFormat('en-US', opts).format(d)
    } catch {
      return d.toISOString()
    }
  }
}

export const register: Register = (on, options) => {
  const display = String(options.display ?? 'auto')
  const pinnedZone = String(options.timeZone ?? '').trim()
  const wants = (channel: string) =>
    display === channel || (display === 'auto' && (channel === 'answer' || channel === 'band'))

  on('turn.complete', async ($, e, next) => {
    const result = await next(e)

    // Subagent turns end inside the main turn; only stamp the main answer.
    if (e.agentId !== undefined) return result

    const now = await $.clock.now()
    const zone = pinnedZone || (await $.env.get('TZ')) || undefined
    const label =
      e.reason === 'answer'
        ? 'Completed'
        : e.reason === 'aborted'
          ? 'Interrupted'
          : e.reason === 'error'
            ? 'Ended with error'
            : 'Refused'
    const line = `${label} ${stamp(now, zone)}`

    // The band above the prompt reads this; writing it redraws the band.
    if (wants('band')) await update($, last, () => line)

    // A plugin status line under the prompt. The desktop shows it as a
    // collapsed "Claude Code notice", so it is off unless chosen.
    if (wants('status')) $.ui.status(line)

    // A dim transcript row, for hosts that draw none of the above.
    if (wants('log')) $.ui.log(line)

    // Drawn beneath the answer where the surface supports it (terminal, VS Code).
    return wants('answer') ? { ...result, text: line } : result
  })

  // The band directly above the prompt input. Raised on the terminal and the
  // desktop Code tab; VS Code does not raise it and shows the answer line instead.
  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
    if (!wants('band')) return next(e)
    const line = await read($, last)
    if (e.props.hasSurvey || line === null) return next(e)

    const { Box, Text } = $.ui.resolve(e)
    return (
      <Box>
        <Text dimColor>
          {e.props.isWorking ? `Working. Last answer: ${line}` : `Last answer: ${line}`}
        </Text>
      </Box>
    )
  })
}

How to read it: register gets an on function and the options from the manifest's userConfig, defaults filled in. on('turn.complete', hook) adds a hook. Every hook receives $ (the engine interface), e (the event input), and next (the rest of the chain). Calling next(e) lets the engine finish normally and hands back its result, which for this event is { text }. Returning a different text makes the terminal and VS Code show that text beneath the answer. The ui.render hook on AbovePrompt draws the band: it reads the latest stamp from session state, and the write in the first hook redraws it.

Customize it

  • Pin a time zone. Set timeZone in /config (or under pluginConfigs."turn-timestamp".options in ~/.claude/settings.json) to an IANA zone such as America/New_York.
  • Choose where it shows. Set display to answer for the beneath-the-answer line only, band for the above-prompt band only, status for a plugin status line (the desktop folds this into a collapsed notice, which is why it is not in the default), or log for a dim transcript row. The default auto draws the answer line and the band.
  • Shorter stamp. Drop weekday and year from the format options for something like Oct 2, 5:41 AM EDT.
  • Add the duration. e.durationMs is on the event. Append Math.round(e.durationMs / 1000) seconds to the line.
  • Add token cost. e.usage carries the turn's token counts when the turn had any.

Saving the file while a session is running hot-reloads the hook, so you can tweak and see the result on the next reply.

Why this matters beyond timestamps

The interesting part is not the stamp, it is how little it took. The mods API gives you typed events for prompts, tool calls, model steps, turn boundaries, and UI rendering, with a hot-reload loop built into the session. Anything you have wanted Claude Code to do automatically at a turn boundary is now a hook away, and Claude can write the hook for you.

Keeping INSTALL.md in sync

The appendix in INSTALL.md embeds the three source files verbatim. If you change register.tsx, index.d.ts, hooks.json, or plugin.json, update the appendix to match, or the one-file installer will hand out an older mod than the folder does.

Credits

Built with Claude Code's plugin-authoring skill. Part of the AI Workbench starter kit.

동명의 다른 작품

비슷한 프로젝트