SLOPSHOPPER

session-nickname

새 세션에 재밌는 랜덤 이름을 붙여 @이름 으로 SendMessage 할 수 있게 한다

newspinner
v0.2.0MITupdated 2026-10-16wonjangcloud9/wj-mods/session-nickname
A shopper browsing a rack in a slop shop
README

wj-mods

English · 한국어

A set of Claude Code mods that change the terminal UI and notifications. They are plugins with hook modules, installable one by one from this marketplace.

The UI text, notifications and summaries are written in Korean.

ModWhat it does
work-progressCollects multi-step work from every open session into a line above the prompt and a /progress pane. Each task's steps are drawn as a chain of nodes that opens into a vertical flow. It also keeps follow-ups left after a task is done, what a paused task is waiting for, and subagent records that ended without being closed. Each open task shows the time likely left, judged from how long its own steps and your recent tasks' steps took. /progress 요약 writes a summary of the last 24 hours.
plain-koreanA Korean readability bundle. An output style for short, plain Korean answers (conclusion first, no made-up abbreviations, nothing referred to by number alone), a guard that flags answers that slip into English and corrects the next one, a nudge to re-explain when you type "뭔 말" or "짧게", and the claude-warm theme. Installing it also installs plain-summary, turn-divider and compact-tool-rows.
usage-bandShows 5-hour and weekly usage and the Claude status page state in one line above the prompt.
long-turn-notifySends a macOS notification and plays a voice line when a turn ran longer than 2 minutes, or when a question or permission prompt is waiting. macOS only.
session-nicknameGives each new session a random name so other sessions can address it with SendMessage. The name reaches the session list with the first prompt.
turn-dividerDraws a divider above each of your prompts to separate questions from answers.
plain-summaryAdds a two-sentence plain-language summary under long, report-style answers, written by a small model.
compact-tool-rowsShortens long Bash commands in tool rows to the start of the first line.

work-progress pane:

업무 3개 · ● 작업 중 1 · □ 남은 일 1 · ✓ 오늘 완료 1

── ● 작업 중 1 ─────────────────────────────────────────────
▸ Theme builder spec
  ●━●━◉─○─○─○ 2/6 · 약 2시간 남음 · 이 단계 1시간째 · 5분 전 · @quiet-otter
  지금: Write the requirements
  최근: Bash Run the mod's tests · 방금
  └ ● Benchmark AI app builders 1/8 · 최근: WebSearch Lovable docs
  └ ‖ Seed starter audit 3/4 · 보류로 끝남 · only saving the file is left
  └ ✓ 서브에이전트 5개 끝남

── □ 남은 일 1 ─────────────────────────────────────────────
▸ Fix lost attachments · 1일 전
  □ push
── ✓ 오늘 완료 1 ───────────────────────────────────────────
▸ Release notes draft · 2시간 전

Click a task to open its steps as a flow, with the time each step took and its evidence:

▾ Theme builder spec
  ● Read the brief · 40분 — notes in brief.md
  ┃
  ● Benchmark · 1시간 20분 — 3 reports
  ┃
  ◉ Write the requirements  ← 지금
  │
  ○ Design

Install

From the marketplace:

claude plugin marketplace add wonjangcloud9/wj-mods
claude plugin install work-progress@wj-mods

Install other mods the same way with <mod>@wj-mods. They load in sessions started after installing.

The Korean readability bundle installs plain-summary, turn-divider and compact-tool-rows with it:

claude plugin install plain-korean@wj-mods

The output style and the theme are not switched on for you. Choose plain-korean as the output style in /config, and claude-warm in /theme if you want the colors too.

From a local clone, list the mod folders in CLAUDE_CODE_PLUGIN_DIRS in ~/.claude/settings.json, separated by :. Folders listed this way do not pull in dependencies, so list the bundle's three mods as well:

git clone https://github.com/wonjangcloud9/wj-mods.git ~/wj-mods
{
  "env": {
    "CLAUDE_CODE_PLUGIN_DIRS": "/Users/me/wj-mods/work-progress:/Users/me/wj-mods/usage-band"
  }
}

Notes

  • Tested with Claude Code 2.1.301.
  • long-turn-notify records its voice lines with the macOS say Yuna voice on first use and keeps them in ~/.claude/long-turn-notify/sounds/. Without that voice it shows the notification silently.
  • plain-summary calls a small model once per long answer, which counts toward your usage.
  • work-progress keeps its records in ~/.claude/work-progress/ on your machine.
  • Run a mod's tests with claude plugin test <mod folder>.

License

MIT

Source 3 files
hooks/register.ts 49 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { hintTail, pickName, readRecent, remember } from './names'
5
6const RECENT_KEY = 'recent'
7
8const nickname = atom({ plugin: 'session-nickname', key: 'name' } as const, '')
9
10// 기록을 못 읽거나 못 써도 이름은 붙인다. 겹칠 확률만 조금 올라간다.
11async function assignName($: EngineInterface): Promise<string> {
12  const recent = readRecent(await $.store.get(RECENT_KEY).catch(() => undefined))
13  const name = pickName(recent)
14  await $.store.set(RECENT_KEY, remember(recent, name)).catch(() => undefined)
15  return name
16}
17
18export const register: Register = on => {
19  // SessionStart 로 넘긴 제목은 대화 제목에만 남고 세션 목록 이름(@주소)은 폴더 이름 그대로다.
20  // 그래서 시작 때는 이름을 골라 힌트 줄에만 보여 주고, 실제로 붙이는 건 첫 프롬프트 때 한다.
21  on('classic.SessionStart', async ($, e, next) => {
22    const result = await next(e)
23    const name = result.sessionTitle ?? e.session_title ?? (await assignName($))
24    await update($, nickname, () => name)
25    return result
26  })
27
28  on('classic.UserPromptSubmit', async ($, e, next) => {
29    const result = await next(e)
30    // /rename 이나 resume 으로 이미 이름이 있으면 주소가 바뀌지 않게 그대로 둔다.
31    const title = result.sessionTitle ?? e.session_title
32    if (title) {
33      await update($, nickname, () => title)
34      return result
35    }
36
37    const name = (await read($, nickname)) || (await assignName($))
38    await update($, nickname, () => name)
39    return { ...result, sessionTitle: name }
40  })
41
42  // $.ui.status 는 엔진이 mod 이름을 앞에 붙여 그려서, 힌트 줄 끝에 닉네임만 붙인다.
43  on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
44    const name = await read($, nickname)
45    if (!name) return next(e)
46    return next({ ...e, props: { ...e.props, tail: hintTail(name) } })
47  })
48}
49
hooks/names.ts 49 lines
1// @ 뒤에 바로 칠 수 있게 영문 소문자와 - 만 쓴다.
2const ADJECTIVES = [
3  'sleepy', 'grumpy', 'sneaky', 'turbo', 'fluffy', 'spicy', 'crispy', 'jolly',
4  'dizzy', 'brave', 'lazy', 'sassy', 'wobbly', 'cosmic', 'chunky', 'zippy',
5  'salty', 'bouncy', 'cheeky', 'mighty', 'nerdy', 'shy', 'rusty', 'fancy',
6  'hungry', 'lucky', 'noisy', 'quirky', 'smug', 'sparkly', 'tiny', 'wild',
7] as const
8
9const NOUNS = [
10  'otter', 'panda', 'capybara', 'hamster', 'penguin', 'raccoon', 'corgi', 'alpaca',
11  'quokka', 'axolotl', 'narwhal', 'wombat', 'sloth', 'gecko', 'puffin', 'mole',
12  'mandu', 'kimbap', 'hotteok', 'ramyeon', 'dalgona', 'japchae', 'injeolmi', 'odeng',
13  'goguma', 'gamja', 'twigim', 'sikhye', 'bibimbap', 'yakgwa', 'jjajang', 'tteok',
14] as const
15
16// 동시에 열린 세션끼리 이름이 겹치면 SendMessage 가 받는 사람을 고르지 못한다.
17export const RECENT_LIMIT = 64
18
19export type Random = (size: number) => number
20
21export const cryptoRandom: Random = size => {
22  const [value = 0] = crypto.getRandomValues(new Uint32Array(1))
23  return value % size
24}
25
26export function pickName(recent: readonly string[], random: Random = cryptoRandom): string {
27  const used = new Set(recent)
28  const total = ADJECTIVES.length * NOUNS.length
29  const start = random(total)
30  for (let offset = 0; offset < total; offset++) {
31    const index = (start + offset) % total
32    const name = `${ADJECTIVES[Math.floor(index / NOUNS.length)]}-${NOUNS[index % NOUNS.length]}`
33    if (!used.has(name)) return name
34  }
35  return `${ADJECTIVES[0]}-${NOUNS[0]}-${random(1000)}`
36}
37
38export function remember(recent: readonly string[], name: string): string[] {
39  return [name, ...recent.filter(item => item !== name)].slice(0, RECENT_LIMIT)
40}
41
42export function readRecent(value: unknown): string[] {
43  return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
44}
45
46export function hintTail(name: string): string {
47  return `  @${name}`
48}
49
types/index.d.ts 8 lines
1export type Nickname = string
2
3declare module 'claude-code' {
4  interface PluginState {
5    'session-nickname': { name: Nickname }
6  }
7}
8