Shows when each prompt and reply in the transcript was stored, dim under its row.

depends on chezmoi
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b $HOME/.local/binwinget install twpayne.chezmoiSet-ExecutionPolicy -ExecutionPolicy ByPass -Scope Processchezmoi init --apply berlysia~/.local/bin will be added to $PATH~/.local/.bin will be added to $PATH , and overwrite this directory with symlinkTailscale経由のSSHと、公開リポジトリに端末情報を置かない鍵管理はSSH設定の手順を参照。
Local workflow verification uses actrun, managed by this project's mise config.
mise install
pnpm ci:local:typescript
pnpm ci:local:codex
pnpm ci:local:shell
If your shell is not activated by mise, run commands through mise exec -- ... instead.
Claude workflows use environment-scoped secrets instead of repository-wide secrets.
claude-manual: used by .github/workflows/claude.ymlclaude-autofix: used by .github/workflows/auto-fix-dependencies.ymlAdd CLAUDE_CODE_OAUTH_TOKEN to both environments. If you want unattended automation, leave approvals and wait timers disabled.
This dotfiles project includes tools for analyzing Claude Code hook decision logs:
home/dot_claude/scripts/update-auto-approve.tsAutomated permission pattern analysis and management tool:
# Interactive review of all patterns
bun home/dot_claude/scripts/update-auto-approve.ts
# Preview changes without applying
bun home/dot_claude/scripts/update-auto-approve.ts --dry-run
# Analyze last 7 days only
bun home/dot_claude/scripts/update-auto-approve.ts --since 7d
# Auto-approve safe patterns without interaction
bun home/dot_claude/scripts/update-auto-approve.ts --auto-approve-safe
# Show detailed analysis information
bun home/dot_claude/scripts/update-auto-approve.ts --verbose
Features:
~/.claude/logs/decisions.jsonl for permission patternsThis repository includes comprehensive Claude Code configuration with:
.skills/ directory)~/.claude/settings.json is managed by splitting it into multiple files:
.settings.base.json.tmpl: Core settings (model, language, statusLine, alwaysThinkingEnabled, etc.).settings.permissions.json: Permission settings (allow/deny).settings.hooks.json.tmpl: Hooks configuration (dynamically generated with chezmoi variables)These files are automatically merged by run_onchange_update-settings-json.sh.tmpl during chezmoi apply.
Important notes:
.settings.hooks.json.tmpl and run chezmoi applyenabledPlugins is managed by the claude plugin CLI; existing values are preserved on merge (see Plugin Management)~/.claude.json (MCP servers configuration) is automatically managed by run_onchange_update-claude-json.sh.tmpl:
How it works:
package.json dependencies~/.claude.jsonmcpServers, preferredNotifChannel, and defaultModeThe managed MCP servers are defined in the template of run_onchange_update-claude-json.sh.tmpl.
To update versions: Edit package.json dependencies and run chezmoi apply
This project uses a two-layer approach for managing Claude Code skills:
.skills/)${projectRoot}/.skills/ for unified management (at repo root, outside home/)~/.claude/skills/ and ~/.codex/skills/ via run_after_sync-skills.sh.tmplhome/dot_apm/apm.yml)apm.yml)apm install -g from GitHub repositoriesapm.lock.yamlBoth layers coexist in ~/.claude/skills/ directory.
Plugins and marketplaces are declared in home/.chezmoidata/claude_plugins.yaml. chezmoi apply installs and removes them to match the declaration (run_onchange_install-claude-plugins-8.sh.tmpl).
Auditing and syncing:
# Compare declared plugins (YAML) with the actual state of the claude CLI
~/.claude/scripts/show-missing-plugins.sh
# Print YAML generated from the installed plugins and diff it against the declaration
~/.claude/scripts/sync-enabled-plugins.sh
To use these skills in another project:
cd /path/to/your/project
curl -fsSL https://raw.githubusercontent.com/berlysia/dotfiles/master/scripts/setup-claude-skills.sh | bash
This creates a simple .claude/settings.json with inline SessionStart command - no hook files needed!
Skills are installed to ~/.claude/ and auto-update on Claude Code startup.
See docs/external-usage.md for detailed documentation.
See .skills/ directory for available skills.
hooks/register.tsx 69 lines1import { atom, memberOf, read, update } from "claude-code";
2import type {
3 EngineInterface,
4 Register,
5 RenderElement,
6 RenderInput,
7} from "claude-code";
8
9// A family keyed by the stored row's uuid, so a write redraws that row alone.
10const storedAt = atom(
11 { plugin: "message-times", key: "storedAt" } as const,
12 null,
13);
14
15export function formatDateTime(ms: number): string {
16 const date = new Date(ms);
17 const pad = (n: number) => String(n).padStart(2, "0");
18 const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
19 const time = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
20 // An offset, not a zone name like JST: a name needs Intl's time zone data,
21 // which the mod's runtime is not promised to carry.
22 const offsetMinutes = -date.getTimezoneOffset();
23 const sign = offsetMinutes < 0 ? "-" : "+";
24 const abs = Math.abs(offsetMinutes);
25 const offset = `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
26 return `${day} ${time} ${offset}`;
27}
28
29async function drawWithTime(
30 $: EngineInterface,
31 e: RenderInput<"UserMessage" | "AssistantMessage">,
32 beneath: RenderElement,
33): Promise<RenderElement> {
34 const at = await read($, memberOf(storedAt, e));
35 // A row stored before the mod loaded (a resumed session's history) has no
36 // time here; the transcript file has one, which this mod does not read.
37 if (at === null) return beneath;
38 const { Box, Text } = $.ui.resolve(e);
39 return (
40 <Box flexDirection="column">
41 {beneath}
42 <Text dimColor>{` ${formatDateTime(at)}`}</Text>
43 </Box>
44 );
45}
46
47export const register: Register = (on) => {
48 on("session.append", async ($, e, next) => {
49 const result = await next(e);
50 if (result.deny !== undefined) return result;
51 const { type } = result.message;
52 if (type === "user" || type === "assistant") {
53 const at = await $.clock.now();
54 // A message row's requestId is the uuid its row was stored under.
55 await update($, memberOf(storedAt, { requestId: result.uuid }), () => at);
56 }
57 return result;
58 });
59
60 on("ui.render", { component: "UserMessage" }, async ($, e, next) =>
61 drawWithTime($, e, await next(e)),
62 );
63 // Every block of a reply: the blocks either side of a tool call were
64 // written at different times.
65 on("ui.render", { component: "AssistantMessage" }, async ($, e, next) =>
66 drawWithTime($, e, await next(e)),
67 );
68};
69types/index.d.ts 13 lines1declare module "claude-code" {
2 interface PluginState {
3 "message-times": {
4 /**
5 * When `session.append` stored each user or assistant row, in
6 * `$.clock.now()`'s milliseconds, by the row's uuid (a message row's
7 * `requestId`); null for a row stored before the mod loaded.
8 */
9 storedAt: StateFamily<number | null>;
10 };
11 }
12}
13