Gives a session a random /color when it gets a new identity: the first /rename, unless the mod has already coloured it or seen you run /color, and every…

A Claude Code plugin marketplace of mods: plugins whose behaviour is a TypeScript module of event hooks running inside Claude Code.

| Mod | What it does |
|---|---|
chameleon | Gives a session a random colour the first time you /rename it, and every /branch a new one. |
skill-session-mods | Names and colours a session from the SKILL.md of a skill you type as /<skill> args. |
Needs Claude Code 2.1.287 or later, where mods are on by default.
claude plugin marketplace add aksh1618/claude-mods
claude plugin install chameleon@aksh1618-mods
claude plugin install skill-session-mods@aksh1618-mods
To run one from a clone instead, start Claude Code with claude --plugin-dir <clone>/<mod>.
chameleonSince Claude Code 2.1.246, /rename no longer colours the prompt bar. This mod brings that back: the first /rename of a session runs a bare /color, which picks a random colour.
/color in it (/color default included). It can't read the session's actual colour, so a colour set while the mod wasn't loaded doesn't count./color fails, the session isn't marked, and the next rename tries again./branch always gets a new colour, since a branch is a new session and doesn't inherit its parent's colour./color would otherwise wait in the prompt queue as a visible row./rename or /branch triggers it. Renames and branches made in other ways are not seen.skill-session-modsWhen a prompt is typed as /<skill> args, the mod reads that skill's SKILL.md and applies the keys it finds under the frontmatter's metadata:. For example:
---
name: arch-pkg
description: …
metadata:
skill-session-mods-title: "arch-package-mgmt--{2|basename|lower}--{1}-{date}"
skill-session-mods-title-fallback: "arch-package-mgmt--{date}"
skill-session-mods-title-apply-if-match: "^(update|create)\\s+(\\S+)"
skill-session-mods-color: cyan
---
With that skill, typing /arch-pkg update plannotator names the session arch-package-mgmt--plannotator--update-20261002 and turns it cyan.
| Key | Meaning |
|---|---|
skill-session-mods-title | Template for the session name. |
skill-session-mods-title-fallback | Template used when the title doesn't apply. |
skill-session-mods-title-apply-if-match | Regex tested against the args. If it doesn't match, the title is skipped. |
skill-session-mods-color | A /color value, applied when the turn ends. |
Every key is optional, and they apply on every matching command, not just the first: the name changes at once, and the colour when that turn ends. The colour doesn't depend on the title applying. The metadata: block is where the Agent Skills spec puts extra keys, so other tools reading the same skill ignore them.
{1}, {2}, …: the args' whitespace-separated words. With title-apply-if-match, they are the regex's capture groups instead (for the title only; the fallback always uses words).{args}: the args, trimmed. {skill}: the skill's name. {date}: today as YYYYMMDD, local time.|. basename drops a URL's ?query and #fragment and any trailing /, keeps the last path segment, and strips one .git, .deb, .rpm, .AppImage, .tgz, .zip or .tar.<ext> (such as .tar.gz) suffix. lower lowercases.{} included), or an unknown filter, doesn't apply, and neither does the title when title-apply-if-match isn't a valid regex (that one is also logged). The fallback is tried next, and failing that the name is left alone./<skill> runs: first ~/.claude/skills/<skill>/SKILL.md, then .claude/skills/<skill>/SKILL.md in the project, outermost directory first, from the repository root (or / outside a repository) down to the working directory. The first file that exists is used. Skills are found by directory name, so a frontmatter name that differs from it isn't resolved; skills from plugins and managed skills aren't looked up._, ., -). Namespaced commands such as /plugin:skill or /apps/web:deploy are ignored.metadata: block of key: value lines, not full YAML.hooks/register.ts 51 lines1import type { EngineInterface, Register } from 'claude-code'
2
3const inFlight = new Set<string>()
4
5async function colorize($: EngineInterface): Promise<void> {
6 const session = await $.session.id()
7 if (inFlight.has(session)) return
8 inFlight.add(session)
9 $.clock.after(0, () => {
10 $.command.run({ command: 'color' })
11 .then(() => $.store.set(session, true))
12 .catch((err: unknown) => $.ui.log(String(err)))
13 .finally(() => inFlight.delete(session))
14 })
15}
16
17export const register: Register = (on) => {
18 let running = false
19 let pending = false
20
21 on('turn.start', async ($, e, next) => {
22 running = true
23 return next(e)
24 })
25
26 on('turn.complete', async ($, e, next) => {
27 const result = await next(e)
28 if (e.agentId === undefined) {
29 running = false
30 if (pending) {
31 pending = false
32 await colorize($)
33 }
34 }
35 return result
36 })
37
38 on('command.run', { command: ['rename', 'branch', 'color'] }, async ($, e, next) => {
39 const result = await next(e)
40 const session = await $.session.id()
41 if (e.command === 'color') {
42 pending = false
43 await $.store.set(session, true)
44 } else if (e.command === 'branch' || (await $.store.get(session)) === undefined) {
45 if (running) pending = true
46 else await colorize($)
47 }
48 return result
49 })
50}
51