ClaudeMods
☰
JA
● 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 タブで動作する。小さなファイル4つ、TypeScript は100行未満で、依頼すると Claude Code が1ターンで最初の版を書いてくれた。

問題

複数の VS Code タブで Claude Code を動かしたり、ターミナルのセッションをいくつも開いたりすると、トランスクリプトはどれも同じに見える。ある応答がいつ届いたのかを UI から知る方法はない。タブを開いて最後のメッセージを読んでも、今日の作業なのか先週のものなのか分からない。

このモッドの動作

Claude Code は現在 mods に対応している。これは関数フックとして書かれ、セッション内で実行されるプラグインで、ペインやステータス行、トーストを描画したり、イベントに反応したりできる。イベントの1つが turn.complete で、応答が終わるたびに発火する。このイベントのフックは短いテキスト行を返せ、それが回答の下に表示される。

このモッドは turn.complete にフックし、時計を読み取ってスタンプを返す。ただし、返されたテキストを描画しない画面もある。ターミナルと VS Code は描画するが、デスクトップアプリの Code タブは描画しない。そこでモッドは、ターミナルとデスクトップの Code タブが対応する AbovePrompt レンダリングコンポーネントも使い、プロンプト入力のすぐ上に帯を描画して最新のスタンプを表示する。2つの経路により、どの画面でもスタンプが折りたたまれたり隠れたりせず、目に見える形で表示される。

  • スタンプが付くのはメイン回答だけ。サブエージェントのターンはメインターンの中で終わるためスキップされ、1つの応答からタイムスタンプが大量に出ることはない。
  • ラベルはターンの終了理由で変わる。Completed、Interrupted、Ended with error、Refused のいずれかになる。
  • コードを編集せず /config から変更できる設定が2つある。display(auto、answer、band、status、log)と timeZone だ。
  • トランスクリプト自体は書き換えない。スタンプは表示専用だ。

インストール

Claude Code 2.1.286 以降が必要。mods API は Anthropic によって early access と位置付けられているため、変更される可能性がある。

最短の方法:clone 不要の1ファイル

INSTALL.md は自己完結したインストーラーだ。付録に3つのモッドファイルと、アシスタント向けの手順(バージョン確認、棚卸し、書き込み、検証、そして必要ならバックアップ付きのユーザーレベル設定編集)を収めている。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: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 はユーザーレベルなので、ここを1回編集すれば、このマシンのすべてのプロジェクトと VS Code タブに適用される。タブを見失わないことが目的なら、この設定が必要になる。既存の内容とマージして、次を追加する。

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

macOS と Linux では複数のフォルダを : で、Windows では ; で区切る。Claude Code を再起動すると、新しいセッションにはスタンプが付く。この変数はユーザー設定またはプロセス環境からだけ読み取られ、プロジェクトの .claude/settings.json からは読まれない。そのため、プロジェクト単位の設定はない。1つのプロジェクトだけでスタンプを使うなら、そのプロジェクトを起動するときに選択肢 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 に自動実行させたいことは、今やフック1つで実現できる。フック自体を Claude に書かせることもできる。

INSTALL.md を同期する

INSTALL.md の付録には3つのソースファイルがそのまま埋め込まれている。register.tsx、index.d.ts、hooks.json、plugin.json のいずれかを変更したら、付録も合わせて更新すること。そうしないと、1ファイルインストーラーがフォルダの版より古いモッドを配布してしまう。

クレジット

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.

同名の他の作品

関連作品