Every project gets its own prompt-bar colour and session name, set when a session starts. It learns from your own /color and /rename.

Each project folder keeps its own prompt-bar colour and a session name, so you can tell your Claude Code windows apart at a glance.
/color pink or /rename Fairy mocap and that project remembers it from then on.| Command | What it does |
|---|---|
/project-colors | Shows this project's colour and name |
/project-colors reset | Forgets them; the next session picks fresh ones |
/project-colors off | Stops colouring and naming, in every session until you turn it back on; the bar goes back to the default colour |
/project-colors on | Turns it back on, with your saved colours and names |
Each session starts with a short "Session color set to: …" line. That's this mod at work.
hooks/register.ts 163 lines1// project-colors: each project folder keeps its own prompt-bar colour and a
2// session name. A new folder gets a colour picked from its name; type /color
3// or /rename yourself and that project remembers your choice from then on.
4
5import type { EngineInterface, Register } from 'claude-code'
6
7type Project = { color?: string; name?: string }
8type Projects = Record<string, Project>
9
10// what /color took as of Oct 2026; replaced by the list /color prints if it refuses one
11const DEFAULT_COLORS = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'cyan']
12
13function keyOf(root: string): string {
14 return root.replace(/[\\/]+$/, '').toLowerCase()
15}
16
17function folderName(root: string): string {
18 return root.split(/[\\/]/).filter(Boolean).pop() ?? root
19}
20
21/** The same folder name always lands on the same colour. */
22function pickColor(name: string, colors: string[]): string {
23 let hash = 0
24 for (const ch of name.toLowerCase()) hash = (Math.imul(hash, 31) + ch.charCodeAt(0)) >>> 0
25 return colors[hash % colors.length] ?? 'blue'
26}
27
28async function loadProjects($: EngineInterface): Promise<Projects> {
29 const saved = await $.store.get('projects')
30 return saved && typeof saved === 'object' ? (saved as Projects) : {}
31}
32
33async function thisProject($: EngineInterface): Promise<{ key: string; folder: string; projects: Projects }> {
34 const root = await $.session.root()
35 return { key: keyOf(root), folder: folderName(root), projects: await loadProjects($) }
36}
37
38async function remember($: EngineInterface, change: Project) {
39 try {
40 const { key, projects } = await thisProject($)
41 await $.store.set('projects', { ...projects, [key]: { ...projects[key], ...change } })
42 } catch {
43 // forgetting a choice is harmless
44 }
45}
46
47/** Whether `/project-colors off` is in force; read from the store each time, so it holds across sessions. */
48async function isOff($: EngineInterface): Promise<boolean> {
49 return (await $.store.get('isOn')) === false
50}
51
52/** The colours /color takes: the list it last printed, else the known ones. */
53async function knownColors($: EngineInterface): Promise<string[]> {
54 const saved = await $.store.get('colors')
55 return Array.isArray(saved) && saved.length > 0 ? (saved as string[]) : DEFAULT_COLORS
56}
57
58/**
59 * Sets the prompt bar to this project's colour, choosing and saving one for a
60 * new project. A project you set to `default` yourself stays plain.
61 */
62async function applyColor($: EngineInterface) {
63 try {
64 if (await isOff($)) return
65 const { key, folder, projects } = await thisProject($)
66 if (projects[key]?.color === 'default') return
67 const colors = await knownColors($)
68 let color = projects[key]?.color ?? pickColor(folder, colors)
69 const { text } = await $.command.run({ command: 'color', args: color } as never)
70 // a colour /color no longer takes: learn the real list from its answer and pick again
71 const listed = /Available colors:\s*(.+)$/im.exec(text ?? '')
72 if (listed?.[1]) {
73 const valid = listed[1]
74 .split(/[,|\s]+/)
75 .map(c => c.trim())
76 .filter(c => c && c !== 'default')
77 if (valid.length === 0) return
78 await $.store.set('colors', valid)
79 color = pickColor(folder, valid)
80 await $.command.run({ command: 'color', args: color } as never)
81 }
82 if (projects[key]?.color !== color) await remember($, { color })
83 } catch {
84 // no colour this time; the default stays
85 }
86}
87
88/** Names a fresh session after its project: the name you gave it before, else the folder's. */
89async function applyName($: EngineInterface) {
90 try {
91 if (await isOff($)) return
92 const { key, folder, projects } = await thisProject($)
93 await $.command.run({ command: 'rename', args: projects[key]?.name ?? folder } as never)
94 } catch {
95 // unnamed is fine
96 }
97}
98
99export const register: Register = on => {
100 on('session.start', async ($, e, next) => {
101 await $.command.register({
102 name: 'project-colors',
103 description: "Show this project's colour and name, forget them, or switch the mod off and on: /project-colors [reset|off|on]",
104 })
105 const result = await next(e)
106 void applyColor($)
107 return result
108 })
109
110 // the classic start event says whether the session is new and already named
111 on('classic.SessionStart', async ($, e, next) => {
112 const result = await next(e)
113 if (e.source === 'startup' && !e.session_title) void applyName($)
114 return result
115 })
116
117 // learn from what you choose yourself; this mod's own runs come from a plugin
118 on('command.run', { command: 'color' }, async ($, e, next) => {
119 const result = await next(e)
120 // only a colour /color took; a typo it refused (it lists the colours then) is not remembered
121 const color = e.args.trim().toLowerCase()
122 const isRefused = /invalid|cannot|available colors/i.test(result.text ?? '')
123 if (e.origin.kind === 'composer' && color && !isRefused) {
124 void remember($, { color })
125 }
126 return result
127 })
128
129 on('command.run', { command: 'rename' }, async ($, e, next) => {
130 const result = await next(e)
131 const name = e.args.trim()
132 if (e.origin.kind === 'composer' && name) void remember($, { name })
133 return result
134 })
135
136 on('command.run', { command: 'project-colors' }, async ($, e) => {
137 const arg = e.args.trim().toLowerCase()
138 if (arg === 'off' || arg === 'on' || arg === 'toggle') {
139 const isOn = arg === 'on' ? true : arg === 'off' ? false : await isOff($)
140 await $.store.set('isOn', isOn)
141 if (isOn) {
142 void applyColor($)
143 return { text: "Project colours on. This project's colour is back." }
144 }
145 // a command cannot run another from inside itself: reset the colour just after it ends
146 $.clock.after(0, () => void $.command.run({ command: 'color', args: 'default' } as never).catch(() => {}))
147 return { text: 'Project colours off, in every session until /project-colors on. Your saved colours and names are kept.' }
148 }
149
150 const { key, folder, projects } = await thisProject($)
151 if (arg === 'reset') {
152 const { [key]: _forgotten, ...rest } = projects
153 await $.store.set('projects', rest)
154 return { text: `Forgot ${folder}'s colour and name. The next session picks fresh ones.` }
155 }
156 const p = projects[key] ?? {}
157 const state = (await isOff($)) ? ' Project colours are off; /project-colors on brings them back.' : ''
158 return {
159 text: `${folder}: colour ${p.color ?? 'not set yet'}, name ${p.name ?? `${folder} (from the folder)`}. Use /color or /rename to change them; this project remembers.${state}`,
160 }
161 })
162}
163