Pane listing the files changed this session with line counts and their diffs

English · Türkçe
Small, focused mods for Claude Code, built on its function-hook plugin API. Each folder is one plugin; the repo root is a plugin marketplace, so you install only the mods you want.
AskUserQuestion, an idle prompt).size: large in /config brings back the 32×2 half-block band (preview). The large band animates with $.ui.blit, so the rest of the band is not redrawn every frame.%34; 34% 336k came out garbled in the status line.git status with core.fsmonitor off, so a cloned repo's config cannot start a program every 15 s.todo-pane, agents-pane, git-band, time-tracker, session-journal.| Mod | What it does |
|---|---|
usage-meter | Status line: context fill bar with tokens/window, a live turn timer, the last turn's tokens, session cost and 5-hour rate-limit usage. |
clear-reminder | Status line counting prompts since the last /clear (🟢 mesaj ▰▰▱ 7/20); turns yellow at 17 and red at 20, with a toast. |
sys-meter | Status line with CPU load, RAM usage and temperature (🟢 cpu ▰▱▱ %14 · ram ▰▰▰ %78 24.6/31.6G · 🌡 69°C), refreshed every 5 s. Windows only; the temperature is the ACPI thermal zone, readable without admin rights, not the CPU package sensor. |
git-band | One line above the prompt: branch, changed-file count, ahead/behind. Refreshes every 15 s and after each turn. |
todo-pane | Live pane of Claude's todo list (TodoWrite or TaskCreate/TaskUpdate) with a progress bar. Opens by itself; /todos reopens it. |
agents-pane | Live pane of subagents with status, elapsed time and a Stop button for each running one. /subagents reopens it. |
session-diff | /session-diff lists every file changed this session with +/− counts; press a file to read its diff (b back, k/j page). |
| Mod | What it does |
|---|---|
prompt-score | Toasts a quality score for every prompt you type (clarity, context, specificity, verifiability) with one hint for the weakest part. Local rules, no API calls. Hints are in Turkish. |
time-tracker | Adds up how long Claude works per project per day; /zaman shows the last 7 days. |
session-journal | Appends one line per turn to ~/.claude/journal/<date>.md; /gunluk adds a Haiku-written summary of the session. |
| Mod | What it does |
|---|---|
clawd-spinner | A tiny 16×1 band under the spinner (block octants; set size to large in /config for a 32×2 half-block band if your font lacks octants) where pixel-art Clawd plays one of 17 scenes, picked at random for every command (UFO, cooking, fishing, rain, music, laptop, rocket, growing plant, falling blocks, sleeping…). Time-of-day backdrop: morning coffee, a night sky, Friday-evening confetti. |
| Mod | What it does |
|---|---|
ask-alert | When Claude waits for you (a permission prompt from an ask rule, an AskUserQuestion, an idle prompt, an MCP input request) it raises a Windows notification, a toast in the terminal and plays Windows Notify System Generic.wav. mode in /plugin configure: both (default), toast or sound. Windows only. |
done-sound | Plays a random mp3 from its sounds/ folder when Claude finishes answering, never the same twice in a row (Windows, through PowerShell's MediaPlayer). |
At a Claude Code prompt:
/plugin install usage-meter --marketplace kad1r/claude-mods
Answer y to add the marketplace, then pick a scope. Repeat with any mod name from the tables above. /reload-plugins picks up updates.
done-sound: no sound files are in the repo. Put one or more mp3s in done-sound/sounds/ inside the installed plugin folder; each finished answer plays a random one, never the same twice in a row.
ask-alert on 2.1.300.session-journal writes the first line of your prompts to disk in plain text. Keep that in mind if you paste secrets into prompts./gunluk makes one Haiku request each time you run it.ask-alert does not fire in bypassPermissions mode for permission prompts, since none are shown. Where an organization manages Claude Code, its Notification hook runs only if the organization lists the plugin; the AskUserQuestion alert still works.scene.ts, the status-line and pane text from each mod's functions), not cropped from a live session. The Windows notification in ask-alert.svg is drawn to look like the real one.Each mod is three files: .claude-plugin/plugin.json, hooks/hooks.json and a hooks module in hooks/. Mods that keep values in $.state also have types/index.d.ts.
claude plugin validate ./<mod>
claude plugin test ./<mod>
To run from a local clone, add it as a marketplace (claude plugin marketplace add <path>), install from it, and run /reload-plugins after edits; the plugin is read straight from the folder.
<a id="türkçe"></a>
English · Türkçe
Claude Code için küçük, tek işe odaklı mod'lar. Hepsi Claude Code'un function-hook plugin API'si ile yazıldı. Her klasör bir plugin; repo kökü bir plugin marketplace'i, yani sadece istediğin mod'ları kurarsın.
AskUserQuestion, boşta kalan prompt) Windows bildirimi ve ses./config içinde size: large seçilirse 32×2'lik yarım-blok bant geri gelir (önizleme). Büyük bant $.ui.blit ile oynuyor, yani her karede bandın geri kalanı yeniden çizilmiyor.%34 biçiminde yazıyor; 34% 336k durum satırında bozuk görünüyordu.git status'u core.fsmonitor kapalı çalıştırıyor; klonlanan bir reponun ayarı 15 saniyede bir program başlatamaz.todo-pane, agents-pane, git-band, time-tracker, session-journal.| Mod | Ne yapar |
|---|---|
usage-meter | Durum satırı: context doluluk çubuğu ve token/pencere, canlı turn sayacı, son turn'ün token'ı, oturum maliyeti, 5 saatlik limit kullanımı. |
clear-reminder | Son /clear'dan beri yazdığın mesajları sayan durum satırı (🟢 mesaj ▰▰▱ 7/20); 17'de sarı, 20'de kırmızı olur ve toast gösterir. |
sys-meter | CPU yükü, RAM kullanımı ve sıcaklığı gösteren durum satırı (🟢 cpu ▰▱▱ %14 · ram ▰▰▰ %78 24.6/31.6G · 🌡 69°C), 5 saniyede bir yenilenir. Sadece Windows; sıcaklık admin yetkisi gerektirmeyen ACPI thermal zone değeridir, CPU paket sensörü değil. |
git-band | Prompt'un üstünde tek satır: branch, değişen dosya sayısı, ahead/behind. 15 saniyede bir ve her turn sonunda yenilenir. |
todo-pane | Claude'un yapılacaklar listesini (TodoWrite veya TaskCreate/TaskUpdate) ilerleme çubuğuyla gösteren canlı panel. Kendiliğinden açılır; /todos tekrar açar. |
agents-pane | Subagent'ları durum ve süreyle gösteren canlı panel; çalışan her birinin yanında Durdur butonu. /subagents tekrar açar. |
session-diff | /session-diff bu oturumda değişen dosyaları +/− sayılarıyla listeler; bir dosyaya basınca diff'ini gösterir (b geri, k/j sayfa). |
| Mod | Ne yapar |
|---|---|
prompt-score | Yazdığın her prompt'a kalite puanı verir (netlik, bağlam, kapsam, doğrulanabilirlik) ve en zayıf kısım için tek bir ipucu gösterir. Yerel kurallar, API çağrısı yok. |
time-tracker | Claude'un proje bazında günlük çalıştığı süreyi toplar; /zaman son 7 günü gösterir. |
session-journal | Her turn için ~/.claude/journal/<tarih>.md dosyasına bir satır ekler; /gunluk oturumun Haiku ile yazılmış özetini ekler. |
| Mod | Ne yapar |
|---|---|
clawd-spinner | Spinner'ın altında 16×1'lik minik bir bant (blok octant karakterleri; fontun bunları çizmiyorsa /config'ten size ayarını large yap, 32×2 yarım-blok bant gelir): piksel Clawd her komutta rastgele seçilen 17 sahneden birini oynar (UFO, yemek, balık tutma, yağmur, müzik, laptop, roket, büyüyen çiçek, düşen bloklar, uyku…). Arka plan saate göre değişir: sabah kahve, gece gökyüzü, cuma akşamı konfeti. |
| Mod | Ne yapar |
|---|---|
ask-alert | Claude seni beklediğinde (ask kuralından gelen izin isteği, AskUserQuestion, boşta kalan prompt, MCP'nin bilgi isteği) Windows bildirimi ve terminal içi toast gösterir, Windows Notify System Generic.wav çalar. /plugin configure içinde mode: both (varsayılan), toast veya sound. Sadece Windows. |
done-sound | Claude cevabı bitirince sounds/ klasöründen rastgele bir mp3 çalar, aynısı art arda gelmez (Windows, PowerShell MediaPlayer ile). |
Claude Code prompt'unda:
/plugin install usage-meter --marketplace kad1r/claude-mods
Marketplace'i eklemek için y de, sonra scope seç. Tablolardaki diğer mod adlarıyla tekrarla. Güncellemeler için /reload-plugins.
done-sound: Ses dosyaları repoda yok. Kurulan plugin klasöründe done-sound/sounds/ altına bir veya daha fazla mp3 koy; her cevap bitiminde rastgele biri çalar, aynısı art arda gelmez.
ask-alert 2.1.300 üzerinde.session-journal, prompt'larının ilk satırını diske düz metin olarak yazar. Prompt'a secret yapıştırıyorsan bunu göz önünde tut./gunluk her çalıştığında bir Haiku isteği yapar.ask-alert, bypassPermissions modunda izin uyarısı vermez, çünkü izin ekranı çıkmaz. Claude Code'u organizasyon yönetiyorsa Notification hook'u ancak organizasyon plugin'i listelerse çalışır; AskUserQuestion uyarısı yine çalışır.scene.ts'den, durum satırı ve panel metinleri mod fonksiyonlarından); canlı oturumdan kırpılmadı. ask-alert.svg içindeki Windows bildirimi gerçeğine benzetilerek çizildi.Her mod üç dosyadan oluşur: .claude-plugin/plugin.json, hooks/hooks.json ve hooks/ altında bir hooks modülü. $.state kullananlarda ayrıca types/index.d.ts vardır.
claude plugin validate ./<mod>
claude plugin test ./<mod>
Yerel bir klondan çalıştırmak için klonu marketplace olarak ekle (claude plugin marketplace add <yol>), oradan kur ve değişikliklerden sonra /reload-plugins çalıştır; plugin doğrudan klasörden okunur.
hooks/register.tsx 122 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import { fit, record, shortPath, unified } from './diff'
5
6const PANE = 'session-diff'
7const TITLE = 'Oturum değişiklikleri'
8
9const files = atom({ plugin: 'session-diff', key: 'files' } as const, [])
10const selected = atom({ plugin: 'session-diff', key: 'selected' } as const, null)
11const page = atom({ plugin: 'session-diff', key: 'page' } as const, 0)
12
13export const register: Register = on => {
14 let cwd = ''
15
16 on('session.start', async ($, e, next) => {
17 cwd = e.cwd
18 await $.command.register({
19 name: 'session-diff',
20 description: 'Bu oturumda değişen dosyaları ve diff’lerini panelde göster',
21 })
22 return next(e)
23 })
24
25 on('command.run', { command: 'session-diff' }, async $ => {
26 await update($, selected, () => null)
27 await $.ui.open({ id: PANE, title: TITLE })
28 return { text: 'Değişiklik paneli açıldı.' }
29 })
30
31 on('tool.call', { tool: 'Edit' }, async ($, e, next) => {
32 const ran = await next(e)
33 if (!('deny' in ran && ran.deny !== undefined) && ran.isError !== true) {
34 await update($, files, list =>
35 record(list, shortPath(e.file_path, cwd), { before: e.old_string, after: e.new_string }, false),
36 )
37 }
38 return ran
39 }).catch(($, e, next) => next(e)) // tracking must never block an edit
40
41 on('tool.call', { tool: 'Write' }, async ($, e, next) => {
42 const path = e.file_path
43 // Read what the file held before, so an overwrite shows what it replaced.
44 const existed = await $.fs.exists(path).catch(() => false)
45 const before = existed ? await $.fs.read(path).catch(() => '') : ''
46 const ran = await next(e)
47 if (!('deny' in ran && ran.deny !== undefined) && ran.isError !== true) {
48 await update($, files, list => record(list, shortPath(path, cwd), { before, after: e.content }, !existed))
49 }
50 return ran
51 }).catch(($, e, next) => next(e))
52
53 on('session.end', async ($, e, next) => {
54 if (e.reason === 'clear') {
55 await update($, files, () => [])
56 await update($, selected, () => null)
57 }
58 return next(e)
59 })
60
61 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
62 const { Box, Button, Code, Text } = $.ui.resolve(e)
63 const list = await read($, files)
64 const current = await read($, selected)
65 const width = Math.max(20, e.props.bodyColumns)
66 const room = Math.max(4, (e.viewport?.rows ?? 30) - 6)
67 const file = list.find(f => f.path === current)
68
69 if (file !== undefined) {
70 const lines = unified(file).split('\n')
71 const pages = Math.max(1, Math.ceil(lines.length / room))
72 const at = Math.min(await read($, page), pages - 1)
73 const turn = (delta: number) => update($, page, p => Math.max(0, Math.min(pages - 1, p + delta)))
74
75 return (
76 <Box flexDirection="column">
77 <Box>
78 <Button key="back" hotkey="b" label="← Liste" onPress={() => update($, selected, () => null)} />
79 <Button key="prev" hotkey="k" label="↑" onPress={() => turn(-1)} />
80 <Button key="next" hotkey="j" label="↓" onPress={() => turn(1)} />
81 <Text dimColor>
82 {' '}
83 {at + 1}/{pages} · <Text color="green">+{file.added}</Text> <Text color="red">−{file.removed}</Text> · {file.edits} düzenleme
84 </Text>
85 </Box>
86 <Code format="diff" path={file.path} source={lines.slice(at * room, (at + 1) * room).join('\n')} />
87 </Box>
88 )
89 }
90
91 const added = list.reduce((n, f) => n + f.added, 0)
92 const removed = list.reduce((n, f) => n + f.removed, 0)
93
94 return (
95 <Box flexDirection="column">
96 {list.length === 0 ? (
97 <Text dimColor>Bu oturumda henüz Edit/Write ile değişen dosya yok.</Text>
98 ) : (
99 <Text dimColor>
100 {list.length} dosya · <Text color="green">+{added}</Text> <Text color="red">−{removed}</Text>
101 </Text>
102 )}
103 {list.slice(0, room).map(f => (
104 <Button
105 key={`file:${f.path}`}
106 plain
107 onPress={async () => {
108 await update($, page, () => 0)
109 await update($, selected, () => f.path)
110 }}
111 >
112 <Text color="green">{`+${f.added}`.padStart(5)}</Text> <Text color="red">{`−${f.removed}`.padStart(5)}</Text>{' '}
113 {fit(f.path, width - 14)}
114 {f.isNew ? <Text dimColor> (yeni)</Text> : ''}
115 </Button>
116 ))}
117 {list.length > room ? <Text dimColor>… {list.length - room} dosya daha</Text> : null}
118 </Box>
119 )
120 })
121}
122hooks/diff.ts 52 lines1import type { FileChange, Hunk } from '../types'
2
3// Per file, keep the latest hunks and cap each side so state stays small.
4export const MAX_HUNKS = 20
5export const MAX_SIDE_CHARS = 4000
6
7export const lineCount = (text: string): number => (text === '' ? 0 : text.replace(/\n$/, '').split('\n').length)
8
9const cap = (text: string) => (text.length > MAX_SIDE_CHARS ? text.slice(0, MAX_SIDE_CHARS) + '\n…' : text)
10
11export const record = (files: readonly FileChange[], path: string, hunk: Hunk, isNew: boolean): FileChange[] => {
12 const found = files.find(f => f.path === path)
13 const base: FileChange = found ?? { path, added: 0, removed: 0, edits: 0, isNew, hunks: [] }
14 const next: FileChange = {
15 ...base,
16 added: base.added + lineCount(hunk.after),
17 removed: base.removed + lineCount(hunk.before),
18 edits: base.edits + 1,
19 hunks: [...base.hunks, { before: cap(hunk.before), after: cap(hunk.after) }].slice(-MAX_HUNKS),
20 }
21 // Most recently touched file first.
22 return [next, ...files.filter(f => f.path !== path)]
23}
24
25const prefixed = (text: string, mark: string) =>
26 text === '' ? [] : text.replace(/\n$/, '').split('\n').map(l => mark + l)
27
28// A unified diff the Code element can draw; line numbers are not known, so each hunk counts from 1.
29export const unified = (file: FileChange): string => {
30 const head = [`--- a/${file.path}`, `+++ b/${file.path}`]
31 const hunks = file.hunks.flatMap(h => {
32 const before = lineCount(h.before)
33 const after = lineCount(h.after)
34 return [
35 `@@ -${before === 0 ? 0 : 1},${before} +${after === 0 ? 0 : 1},${after} @@`,
36 ...prefixed(h.before, '-'),
37 ...prefixed(h.after, '+'),
38 ]
39 })
40 return [...head, ...hunks].join('\n')
41}
42
43// Path relative to the working directory when inside it, forward slashes either way.
44export const shortPath = (path: string, cwd: string): string => {
45 const p = path.replace(/\\/g, '/')
46 const root = cwd.replace(/\\/g, '/').replace(/\/$/, '') + '/'
47 return p.toLowerCase().startsWith(root.toLowerCase()) ? p.slice(root.length) : p
48}
49
50export const fit = (text: string, width: number): string =>
51 text.length <= width ? text : '…' + text.slice(text.length - Math.max(1, width - 1))
52types/index.d.ts 17 lines1export type Hunk = { before: string; after: string }
2
3export type FileChange = {
4 path: string
5 added: number
6 removed: number
7 edits: number
8 isNew: boolean
9 hunks: Hunk[]
10}
11
12declare module 'claude-code' {
13 interface PluginState {
14 'session-diff': { files: FileChange[]; selected: string | null; page: number }
15 }
16}
17