Applies the theme in ~/.claude/settings.json to running sessions as soon as the file changes

Three Claude Code mods in one marketplace:
theme changes in ~/.claude/settings.json to running sessions.A band above the prompt: a context-window bar split by category, and how many tokens each category holds.
▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 42% 84k / 200k effort high
● system 4k ● tools 20k ● memory 200 ● messages 59.8k
light* one, dark otherwise, and the band switches as soon as the theme changes (/config, or theme-sync)./effort, the settings' effortLevel, or the model's default) follows the token count.At a Claude Code prompt in a terminal:
/plugin install context-bar --marketplace kokko-ng/kokko-claude-mods
Answer y to add the marketplace, then pick the user scope.
Or in ~/.claude/settings.json:
{
"extraKnownMarketplaces": {
"kokko-claude-mods": {
"source": { "source": "github", "repo": "kokko-ng/kokko-claude-mods" }
}
},
"enabledPlugins": { "context-bar@kokko-claude-mods": true }
}
Mods (function-hook plugins) are an early-access Claude Code feature; this one is built and tested against Claude Code 2.1.292.
Claude Code reloads ~/.claude/settings.json when it changes but keeps the running session's theme. theme-sync checks the file once a second and, when its theme differs from the session's, sets it as /config would, with a toast. Edit the file from anywhere (a script that flips light-ansi and dark-ansi alongside the terminal theme, for instance) and every open session follows.
/plugin install theme-sync --marketplace kokko-ng/kokko-claude-mods
or "theme-sync@kokko-claude-mods": true under enabledPlugins.
Keeps the auto memory of every project (~/.claude/projects/<project>/memory/) in a git repo, so each machine sees the others' memories.
~/.claude/projects itself, with the git directory at ~/.claude/memory-sync.git; only each project's memory/ folder is tracked, never transcripts. Memories stay ordinary files where Claude Code reads and writes them, so nothing about recall or permissions changes.MEMORY.md indexes merge line by line (merge=union), so every machine's entries are kept. Any other memory both machines changed gets a normal three-way merge; if that conflicts, the version already pushed stays and this machine's is kept beside it as <name>.from-<host>.md (a git merge driver the mod installs), so nothing is lost or interleaved and the sync carries on. Reconcile those pairs when they appear. Only a memory deleted on one machine and changed on another stops the sync, with a toast saying how to resolve it.The repo defaults to kokko-ng/claude-memory; set CLAUDE_MEMORY_REPO to owner/name (GitHub, using your git credentials) or any git URL. Create it private and empty first:
gh repo create <owner>/claude-memory --private
/plugin install memory-sync --marketplace kokko-ng/kokko-claude-mods
Mods do not run where disableAllHooks is set, and a settings.local.json in ~/.claude sets it for sessions started in your home folder.
Run pre-commit install once per clone. It installs the pre-commit and commit-msg hooks: file hygiene, gitleaks, claude plugin validate --strict and claude plugin test for every mod (on a pinned Claude Code), and Conventional Commits messages checked by commitizen. CI runs the same hooks and checks every commit message.
Each mod is its own plugin under plugins/:
claude plugin validate plugins/context-bar
claude plugin test plugins/context-bar
claude plugin validate plugins/theme-sync
claude plugin test plugins/theme-sync
claude plugin validate plugins/memory-sync
claude plugin test plugins/memory-synchooks/register.ts 66 lines1import type { Register } from 'claude-code'
2
3// How often to look at settings.json. A stat is cheap; the file is only read
4// and parsed when its modification time moves.
5const POLL_MS = 1000
6
7// Claude Code reloads settings.json when it changes but does not re-apply
8// `theme` to a running session; this applies it through the /config row.
9export const register: Register = on => {
10 on('session.start', async ($, e, next) => {
11 const result = await next(e)
12
13 const dir =
14 (await $.env.get('CLAUDE_CONFIG_DIR')) ?? `${await $.env.get('HOME')}/.claude`
15 const path = `${dir}/settings.json`
16 let seenMtime = -1
17 let isBusy = false
18 let lastError = ''
19
20 const readTheme = async (): Promise<unknown> => {
21 try {
22 return (JSON.parse(await $.fs.read(path)) as { theme?: unknown }).theme
23 } catch {
24 return undefined // missing or half-written: the next change retries
25 }
26 }
27
28 const sync = async () => {
29 const { mtimeMs } = await $.fs.stat(path)
30 if (mtimeMs === seenMtime) return
31 seenMtime = mtimeMs
32
33 const theme = await readTheme()
34 if (typeof theme !== 'string') return
35
36 const row = (await $.config.list()).find(r => r.key === 'theme')
37 if (!row || row.isLocked || row.value === theme) return
38
39 const { deny } = await $.config.set({ key: 'theme', value: theme })
40 $.ui.toast(deny ? `theme-sync: ${deny}` : `Theme: ${theme}`)
41 }
42
43 const tick = () => {
44 if (isBusy) return
45 isBusy = true
46 sync()
47 .then(() => {
48 lastError = ''
49 })
50 .catch((err: unknown) => {
51 const text = err instanceof Error ? err.message : String(err)
52 if (text !== lastError) $.ui.toast(`theme-sync: ${text}`)
53 lastError = text
54 })
55 .finally(() => {
56 isBusy = false
57 })
58 }
59
60 tick()
61 $.clock.every(POLL_MS, tick)
62
63 return result
64 })
65}
66