ClaudeMods
☰
ZH-TW
● 0 人在線上 · 瀏覽 0 次
贊助提交作品
GitHub 儲存庫 · 發布者 JDWorkdog

turn-timestamp

一個小巧的 Claude Code 外掛,會在每次完成的回覆下方顯示當地日期與時間,讓你一眼看出剛點開的分頁是五分鐘前還是五天前。

JDWorkdog@JDWorkdog

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

已翻譯

關於這個 mod

turn-timestamp:每個 Claude Code 答案下方的日期與時間

一個小巧的 Claude Code 外掛,會在每次完成的回覆下方顯示當地日期與時間,讓你一眼看出剛點開的分頁是五分鐘前還是五天前。

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

它可在終端機、VS Code 擴充功能和桌面應用程式的 Code 分頁中執行。它由四個小檔案組成,TypeScript 不到一百行;作者提出要求後,Claude Code 在一輪對話中寫出了第一版。

問題

如果你在多個 VS Code 分頁中執行 Claude Code,或同時開著幾個終端機工作階段,所有轉錄看起來都一樣。介面不會告訴你某個回覆是什麼時候到達的。你點進一個分頁,讀完最後一則訊息,卻不知道看到的是今天的工作,還是上週的內容。

這個外掛的作用

Claude Code 現在支援 mods:以函式鉤子編寫、在工作階段內部執行的外掛,可以繪製面板、狀態列和 toast,也可以回應事件。其中一個事件是 turn.complete,每次回覆完成時都會觸發。這個事件上的鉤子可以回傳一行短文字,引擎會把它顯示在答案下方。

這個外掛掛接 turn.complete,讀取時鐘並回傳時間戳。不是每個介面都會繪製這段回傳文字:終端機和 VS Code 會,桌面應用程式的 Code 分頁不會。因此,外掛還使用終端機和桌面 Code 分頁都支援的 AbovePrompt render 元件,直接在提示輸入框上方繪製一條帶狀區域,顯示最新時間戳。兩個通道配合後,每個介面都會顯示看得見的時間戳,不會被摺疊或隱藏。

  • 只有主要答案會顯示時間戳。子代理的工作階段在主要工作階段內結束,會被跳過,所以一次回覆不會出現一串時間戳。
  • 標籤會隨工作階段的結束原因變化: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 技能,幫我建置一個 mod,使用 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) 秒附加到這一列。
  • 加入 token 成本。 當工作階段產生過 token 時,e.usage 會帶有該工作階段的 token 數量。

工作階段執行時儲存檔案會熱重載鉤子,因此你可以調整程式,並在下一則回覆中看到結果。

為什麼這不只是時間戳

有意思的不是時間戳,而是實作它所需的程式碼這麼少。mods API 為提示、工具呼叫、模型步驟、工作階段邊界和 UI render 提供帶型別的事件,工作階段內部還內建了熱重載迴圈。凡是你希望 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.

其他同名作品

更多類似作品