SLOPSHOPPER

skill-badge

Show the active skill with the model/effort its turn actually runs on, and flag when the next turn falls back to another model

newguardprompt
★ 9v0.1.0MITupdated 2026-10-09ysoftman/myenv/.claude/skills/skill-badge
A shopper browsing a rack in a slop shop
README

myenv

개인 환경 백업 및 복구 자동화 스크립트~ :smile:

  • myenv 는 디폴트로 ~/workspace/myenv 에 위치(클론)해야 한다. 변경하려면 .zshrc/.bashrc > myenv_path 값을 수정해야 한다.
  • 설치 & 백업
# 설정 및 프로그램 목록등 백업
bash ./backupmysetting.sh

# 전체 설치
bash ./installall.sh

# 필요한 환경/프로그램은 installxxx.sh 사용
  • mac iterm2 사용시
  • iterm2 > general > selection > application in terminal may access clipboard 활성화(tmux 환경에서 클립보드를 사용하기 위해)
  • iterm2 > profiles > colors > color presets 선택
  • iterm2 > profiles > text > font : 18, font-hack-nerd-font(installbrew.sh 에서 설치함)
  • iterm2 에서 tmux 사용시 더블 클릭은 alt + double click(triple click) 로 사용해야 한다.
  • windows alacritty 사용시
  • 설정 파일은 다른 OS 도 같이 사용하니 윈도우 전용으로 수정하지 않는다. 대신 기본 powershell 에서 wsl 실행하면 된다.
  • windows terminal 사용시
  • 폰트 다운로드 받아 설치
  • <https://github.com/ryanoasis/nerd-fonts/releases/download/v3.5.1/Hack.zip>
  • <https://github.com/ryanoasis/nerd-fonts/releases/download/v3.5.1/FiraCode.zip>
  • terminal 에서 ctrl + , 로 설정 파일을 오픈 후 settings.json 내용 복사 & 붙여넣기로 설정 적용
  • 터미널앱 사용(창 선택)시 배경색이 바뀌는 경우, 윈도우 투명도(settings > transparency effects > off)를 비활성화 해야 한다.
  • Ubuntu, sh > dash 쉘로 링크되어 있어 bash 로 쉘스크립트를 실행하려면 상단에 #!/usr/bin/env bash shebang 을 추가하거나, 아래처럼 전역 sh 링크를 bash로 변경한다.
sudo unlink /bin/sh
sudo ln -s /bin/bash /bin/sh
Source 2 files
hooks/register.ts 85 lines
1import type { EngineInterface, Register, TurnCompleteReason } from 'claude-code'
2import { atom, read, update } from 'claude-code'
3
4import type { SkillRun } from '../types'
5
6const runs = atom({ plugin: 'skill-badge', key: 'runs' } as const, [])
7const model = atom({ plugin: 'skill-badge', key: 'model' } as const, '')
8
9const KEEP = 8
10
11const RESULT: Record<TurnCompleteReason, SkillRun['result']> = {
12  answer: 'done',
13  aborted: 'aborted',
14  refusal: 'failed',
15  error: 'failed',
16}
17
18const short = (m: string) => m.replace(/^.*claude-/, '')
19
20const isOpen = (r: SkillRun | undefined) => r !== undefined && r.result === undefined
21
22const patchOpen = async ($: EngineInterface, fn: (r: SkillRun) => SkillRun) => {
23  const list = await read($, runs)
24  const last = list.at(-1)
25  if (last !== undefined && isOpen(last) && JSON.stringify(fn(last)) !== JSON.stringify(last)) {
26    await update($, runs, l => [...l.slice(0, -1), fn(last)])
27  }
28}
29
30export const register: Register = on => {
31  let loaded: string[] = []
32
33  on('prompt.submit', async ($, e, next) => {
34    if (e.turnId === undefined) {
35      const name = /^\/([\w:.-]+)(?=\s|$)/.exec(e.text)?.[1]
36      const isCommand = name !== undefined && (await $.command.list()).some(c => c.name === name)
37      loaded = isCommand ? [name] : []
38    }
39    return next(e)
40  })
41
42  on('tool.call', { tool: 'Skill' }, async (_, e, next) => {
43    const ran = await next(e)
44    if (e.agentId === undefined && ran.deny === undefined && ran.isError !== true) {
45      loaded = [...new Set([...loaded, e.skill])]
46    }
47    return ran
48  })
49
50  on('turn.step', async function* ($, e, next) {
51    if (e.agentId !== undefined) {
52      return yield* next(e)
53    }
54    const effort = e.effort === undefined ? {} : { effort: String(e.effort) }
55    if (loaded.length > 0) {
56      const skills = loaded
57      if (isOpen((await read($, runs)).at(-1))) {
58        await patchOpen($, r => ({ ...r, skills }))
59      } else {
60        const run = { skills, model: short(e.model), ...effort, startedAt: await $.clock.now() }
61        await update($, runs, l => [...l, run].slice(-KEEP))
62      }
63    }
64    const step = yield* next(e)
65    const answered = short(step.usage?.model ?? e.model)
66    if (loaded.length > 0) {
67      await patchOpen($, r => ({ ...r, model: answered }))
68    }
69    const tag = [answered, effort.effort].filter(v => v !== undefined).join(' · ')
70    if ((await read($, model)) !== tag) {
71      await update($, model, () => tag)
72    }
73    return step
74  })
75
76  on('turn.complete', async ($, e, next) => {
77    if (e.agentId === undefined && loaded.length > 0) {
78      const endedAt = await $.clock.now()
79      await patchOpen($, r => ({ ...r, endedAt, result: RESULT[e.reason] }))
80      loaded = []
81    }
82    return next(e)
83  })
84}
85
types/index.d.ts 15 lines
1export type SkillRun = {
2  skills: string[]
3  model: string
4  effort?: string
5  startedAt: number
6  endedAt?: number
7  result?: 'done' | 'failed' | 'aborted'
8}
9
10declare module 'claude-code' {
11  interface PluginState {
12    'skill-badge': { runs: SkillRun[]; model: string }
13  }
14}
15