SLOPSHOPPER

chameleon

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…

newtimer
v0.4.1MITupdated 2026-10-02aksh1618/claude-mods/chameleon
A shopper browsing a rack in a slop shop
README

claude-mods

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

chameleon colouring a session on /rename and /branch, then skill-session-mods naming and colouring it from a skill

ModWhat it does
chameleonGives a session a random colour the first time you /rename it, and every /branch a new one.
skill-session-modsNames and colours a session from the SKILL.md of a skill you type as /<skill> args.

Install

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>.

chameleon

Since 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.

  • A rename is skipped when the mod has already coloured the session, or has seen you run /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.
  • If /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.
  • A rename or branch typed while Claude is working is coloured when that turn ends. A plugin's /color would otherwise wait in the prompt queue as a visible row.
  • Only a typed /rename or /branch triggers it. Renames and branches made in other ways are not seen.

skill-session-mods

When 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.

Keys

KeyMeaning
skill-session-mods-titleTemplate for the session name.
skill-session-mods-title-fallbackTemplate used when the title doesn't apply.
skill-session-mods-title-apply-if-matchRegex tested against the args. If it doesn't match, the title is skipped.
skill-session-mods-colorA /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.

Templates

  • {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.
  • Filters chain with |. 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.
  • A template with an empty or missing placeholder ({} 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.

Limits

  • The skill is found the way Claude Code picks the one /<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.
  • The command must be a plain skill name (letters, digits, _, ., -). Namespaced commands such as /plugin:skill or /apps/web:deploy are ignored.
  • Values must be flat strings on one line, quoted or bare. The mod parses only a metadata: block of key: value lines, not full YAML.
  • Only typed commands are handled. A skill Claude invokes through the Skill tool doesn't pass through prompt submission.
  • Session names are unique while sessions are live. If another live session already has the name, Claude Code appends a suffix to it.

License

MIT

Source 1 files
hooks/register.ts 51 lines
1import 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