Pushes the dashboard decision log hard: a standing instruction, a nudge after commits and edit-heavy turns, and a running count.

Claude Code plugins, published from Sanduhr.
Add the marketplace once, then install any plugin in it by name:
claude plugin marketplace add estevanhernandez-stack-ed/sanduhr-mods
claude plugin install <plugin>@sanduhr-mods
<!-- sanduhr:plugins -->
claude plugin install now-playing --marketplace estevanhernandez-stack-ed/sanduhr-mods
claude plugin install finish-line --marketplace estevanhernandez-stack-ed/sanduhr-mods
claude plugin install agent-comms --marketplace estevanhernandez-stack-ed/sanduhr-mods
claude plugin install decision-logger --marketplace estevanhernandez-stack-ed/sanduhr-mods
claude plugin install page-pane --marketplace estevanhernandez-stack-ed/sanduhr-mods
claude plugin install desk-setup --marketplace estevanhernandez-stack-ed/sanduhr-mods
Then ask Claude Code to set up your Desk ("set up my Desk on the second screen"). Every change previews on the desktop and waits for your Apply.
<!-- /sanduhr:plugins -->
Some mods are tied to one person's machines: a home server, a media box, a dashboard. Those aren't published here as code, because the code would carry someone else's hosts. What travels instead is the idea and a prompt. Paste a prompt into Claude Code (the plugin-authoring skill ships with it), fill in the parts in angle brackets, and Claude builds the mod for your setup; Sanduhr's New Mod page then keeps it, checks it and turns it on per folder.
A status-line glyph and a toast when something at home goes down or comes back, read from whatever already knows (an Uptime Kuma instance, a Tailscale tailnet, a Home Assistant), polled slowly over SSH or HTTP. /lab lists every monitor and peer on demand.
Build me a Claude Code mod called homelab-pulse with the plugin-authoring skill.
It polls my home lab every 60 seconds and shows a short status-line entry: a
check mark when everything is up, the count of what is down otherwise, and
"lab ?" when the source can't be reached. It toasts once when a monitor drops
and once when it recovers, never on every poll. A /lab command prints every
monitor and peer with its state.
Source of truth: <how to read your monitors, e.g. `ssh <host> …` printing JSON,
an HTTP endpoint, or `tailscale status --json`>. Run it with $.process.run with
a 20 s timeout, or $.http.fetch; parse only the fields you show.
Keep all state in an atom; no files, no network beyond that one source; no
credentials in the mod (use an SSH host alias or a local-network URL).
Add register.test.ts covering: all up, one down, source unreachable, recovery.
Two characters in the status line while a media server answers, nothing otherwise. No alerts, slow polling, no token.
Build me a Claude Code mod called plex-pip with the plugin-authoring skill.
Every <5> minutes it fetches <your server's unauthenticated health URL, e.g.
http://<host>:32400/identity for Plex, or /System/Ping for Jellyfin> with
$.http.fetch. While the response is ok it shows "🎬🎵" in the status line via
$.ui.status; on any error or non-2xx it shows nothing. Read the URL and the
interval from plugin options (plexUrl, pollMinutes) so the mod ships without my
address in it; do nothing when the URL option is empty. No toasts, no commands,
no state beyond the last result. Add register.test.ts for ok, non-2xx, a thrown
fetch and an empty option.
If you build one, publish it to your own marketplace from Sanduhr (New Mod, a kept mod's menu, Publish to Marketplace…).
hooks/register.ts 89 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4const logged = atom({ plugin: 'decision-logger', key: 'logged' } as const, 0)
5
6// The claude.ai connector name is the live one; the local-server names stay for older setups.
7export const DECISIONS_TOOL = /^mcp__(claude_ai_626Labs_Dash|626labs(-cloud)?)__manage_decisions$/
8const EDIT_TOOLS = new Set(['Edit', 'Write', 'NotebookEdit'])
9const EDITS_PER_NUDGE = 6
10
11const STANDING_ORDER = [
12 'Decision log, turned up. Este wants the 626 dashboard decision log fed generously, not sparingly.',
13 'Log with manage_decisions (action: log), tagged with the bound project id, whenever the work crosses any fork:',
14 'a choice of X over Y, a scope cut or add, a tradeoff, a gotcha discovered, a wall pushed through, a deadline or commitment.',
15 'Lean toward logging when unsure. A short entry (what, why, what would revisit it) beats a missing one.',
16 'Skip only the truly routine: ran tests, fixed a typo, renamed a variable.',
17 'Log at the moment of the fork, in the same turn, without asking first.',
18].join(' ')
19
20export const register: Register = on => {
21 let edits = 0
22 let loggedThisTurn = false
23
24 on('prompt.compose', async ($, e, next) => {
25 const out = await next(e)
26
27 return {
28 sections: [...out.sections, { id: 'decision-logger:standing-order', text: STANDING_ORDER, scope: 'session' }],
29 }
30 })
31
32 on('turn.start', ($, e, next) => {
33 edits = 0
34 loggedThisTurn = false
35
36 return next(e)
37 })
38
39 on('tool.call', async ($, e, next) => {
40 if (EDIT_TOOLS.has(e.tool)) {
41 edits += 1
42
43 return next(e)
44 }
45
46 if (DECISIONS_TOOL.test(e.tool)) {
47 const ran = await next(e)
48 const action = (e as { action?: unknown }).action
49
50 if (ran.deny === undefined && ran.isError !== true && action === 'log') {
51 loggedThisTurn = true
52 const total = await update($, logged, n => n + 1)
53 $.ui.toast(`Decision logged (${total} this session).`)
54 }
55
56 return ran
57 }
58
59 if (e.tool === 'Bash' && /\bgit\s+commit\b/.test(e.command)) {
60 const ran = await next(e)
61
62 if (ran.deny === undefined && ran.isError !== true) {
63 $.ui.toast('Commit landed. Decision in it worth logging?')
64
65 return {
66 ...ran,
67 context: [
68 ...(ran.context ?? []),
69 'A commit just landed. If it carried a decision, tradeoff, scope change or gotcha, log it with manage_decisions now, in this turn.',
70 ],
71 }
72 }
73
74 return ran
75 }
76
77 return next(e)
78 })
79
80 on('turn.complete', async ($, e, next) => {
81 if (edits >= EDITS_PER_NUDGE && !loggedThisTurn) {
82 const total = await read($, logged)
83 $.ui.toast(`${edits} edits this turn, no decision logged (${total} so far).`, { timeoutMs: 6000 })
84 }
85
86 return next(e)
87 })
88}
89types/index.d.ts 8 lines1export type Count = number
2
3declare module 'claude-code' {
4 interface PluginState {
5 'decision-logger': { logged: Count }
6 }
7}
8