SLOPSHOPPER

image-peek

입력창에 붙여 넣은 이미지를 보내기 전에 입력창 위 목록으로 보여주고, 누르면 사진 뷰어로 크게 엽니다

newbandtoastpromptprocess
★ 1v0.2.0no licenseupdated 2026-10-07glglekdy/claude-mods/image-peek
A shopper browsing a rack in a slop shop
README

claude-mods

Claude Code 터미널의 입력창 위에 한 줄짜리 정보를 더해 주는 모드 두 개입니다.

모드하는 일지원 환경
total-stats모델·effort·fast, 남은 컨텍스트와 사용 한도, 직전 대화의 토큰과 걸린 시간, 세션 전체 턴·토큰·스킬 횟수를 한 줄로 보여주고, 마우스로 자세히 보거나 바로 바꿀 수 있습니다모든 OS
image-peek입력창에 붙여 넣은 이미지([Image #1])를 보내기 전에 목록으로 보여주고, 누르면 원본을 사진 뷰어로 엽니다Windows
📎 [ Image 1 ] [ Image 2 ]
🧠 Opus 5.5 · medium · ⚡fast · ctx ███████░░░ 68% · 남은 5h 77% 주 60% · 직전 4.0k (1m23s) · 세션 12턴 244.0k · 스킬 3
> 이거 봐줘 [Image #1] [Image #2]

설치

Claude Code 터미널의 입력창에 입력합니다. 필요한 것만 설치해도 됩니다.

/plugin install total-stats --marketplace glglekdy/claude-mods
/plugin install image-peek --marketplace glglekdy/claude-mods

처음 설치할 때 마켓플레이스를 추가할지 물으면 y, 설치 범위는 user(모든 세션)를 고르면 됩니다. 설치한 세션에서 바로 켜집니다.

새 버전이 나오면 터미널에서 마켓플레이스 목록을 새로 받은 뒤 플러그인을 업데이트합니다. Claude Code를 다시 켜거나 /reload-plugins를 입력하면 적용됩니다.

claude plugin marketplace update glglekdy-mods
claude plugin update total-stats@glglekdy-mods
claude plugin update image-peek@glglekdy-mods

Claude Code 안에서 /plugin 메뉴로 업데이트해도 됩니다.

끄기와 지우기:

claude plugin disable total-stats
claude plugin uninstall total-stats

total-stats

항목내용색누르면
🧠 모델지금 대화에 쓰이는 모델마우스를 올리면 Claude 색모델·effort·fast 선택 창
effort실제 요청에 적용된 effort마우스를 올리면 강조색모델·effort·fast 선택 창
fastfast 모드 상태켜짐은 노랑, 꺼짐은 흐리게, 켜지지 않는 환경이면 빨강 ⚡fast 불가
ctx남은 컨텍스트, 10칸 게이지50% 넘게 남으면 초록, 20~50% 노랑, 20% 밑 빨강ctx 글자: /context 실행
남은5시간·주간 한도가 남은 비율(구독 요금제일 때만)같은 기준남은 글자: /usage 실행
직전바로 전 대화 한 번(턴)에 쓴 토큰과 (걸린 시간)시간은 강조색
세션이번 세션의 턴 수와 전체 토큰(서브에이전트 포함)
스킬이번 세션에서 스킬을 부른 횟수(/이름, Skill 도구, 서브에이전트 미리 불러오기 포함)

컨텍스트가 20% 이하로 남으면 ctx 옆에 빨간 경고와 버튼이 생깁니다. 버튼을 누르면 /compact가 실행됩니다.

ctx ██░░░░░░░░ 15% ⚠ 곧 가득 참 [ /compact ]

마우스를 올리면

줄 바로 위에 카드가 펼쳐지고, 마우스를 치우면 사라집니다.

╭──────────────────────────────────────────╮   ╭──────────────────────────────────────────────╮
│ 컨텍스트 64.0k / 200.0k                  │   │ 5시간 15% 남음 (85% 사용) · 리셋까지 2h13m   │
│ Messages      ███░░░░░░░  50.0k 25%      │   │ 주간  60% 남음 (40% 사용) · 리셋까지 3d4h    │
│ MCP tools     █░░░░░░░░░  10.0k 5%       │   │ 이번 세션 API 환산 비용 $0.31                │
│ System prompt █░░░░░░░░░   4.0k 2%       │   ╰──────────────────────────────────────────────╯
╰──────────────────────────────────────────╯    남은 위에 올렸을 때
 ctx 위에 올렸을 때

╭──────────────────────────────────────────────────────────╮
│ 모델   [ Opus ] [ Sonnet ] [ Haiku ] [ Fable ] [ 더 보기 ] │
│ effort [ low ] [ medium ] [ high ] [ xhigh ] [ max ]      │
│ fast   [ 켜짐 → 끄기 ]  크레딧(추가 사용량)을 씁니다       │
╰──────────────────────────────────────────────────────────╯
 모델·effort·fast 위에 올렸을 때
  • ctx 카드: /context처럼 컨텍스트를 무엇이 얼마나 차지하는지 많은 순서대로 최대 8개 보여줍니다. 대화가 한 번 끝날 때마다 새로 계산하고, 로컬에서 어림하므로 API를 따로 부르지 않습니다.
  • 남은 카드: 5시간·주간 한도가 얼마나 남았고 리셋까지 얼마나 걸리는지, 그리고 이번 세션 비용을 API 가격으로 환산해 보여줍니다. 구독 요금제에서는 실제로 내는 돈이 아니라서 줄에는 넣지 않았습니다.
  • 모델·effort·fast 메뉴: 누르면 /model, /effort, /fast를 직접 입력한 것처럼 실행합니다. 지금 쓰는 값은 강조색으로 보입니다. 모델이나 effort를 클릭하면 같은 내용의 선택 창이 열리고, 그 창에서는 숫자 키(모델 1~4, effort 5~9, fast 0)와 Esc도 쓸 수 있습니다.
  • fast 불가: fast는 크레딧(추가 사용량)을 씁니다. 켜려 했는데 꺼진 채로 남으면, 메뉴에서 눌렀든 /fast를 직접 쳤든 빨간색으로 표시하고 이유를 메뉴에 적습니다.
  • 마우스 올리기와 클릭은 마우스를 지원하는 터미널에서만 동작합니다. 키보드로는 ctrl+x 다음 tab으로 줄에 들어가서 Tab으로 옮기고 Enter로 누릅니다.

알아 둘 것

  • /model, /effort, /fast를 직접 쳐도 늦어도 2초 안에 반영됩니다.
  • 토큰·턴·스킬 횟수는 모드가 켜진 뒤부터 셉니다. 세션마다 따로 저장해서 여러 창을 열어도 섞이지 않습니다.
  • 창이 좁으면 줄을 바꾸지 않고 오른쪽 끝을 자릅니다.
  • 계정이나 요금제에서 막힌 모델은 메뉴에서 골라도 바뀌지 않고, Claude Code가 알려준 문구가 알림으로 뜹니다.

image-peek

  • 이미지를 붙여 넣어 입력창에 [Image #1]이 생기면 바로 위에 [ Image 1 ] 버튼이 생깁니다.
  • 버튼을 누르면 원본 파일이 Windows 기본 사진 뷰어로 열립니다.
  • 입력창에서 [Image #1]을 지우면 목록에서도 빠지고, 메시지를 보내면 목록이 비워집니다.

원본 파일은 이렇게 찾습니다.

  1. 붙여 넣은 글에 이미지 파일 경로가 있으면 그 경로를 씁니다. 예를 들어 Orca 터미널은 %TEMP%\orca-paste-….png로 저장합니다.
  2. 경로 없이 [Image #N]만 들어오면 %TEMP%에서 15초 안에 새로 생긴 이미지 파일을 씁니다.
  3. 둘 다 없으면 버튼은 나오지만, 누르면 "원본 파일을 찾지 못했어요"라고 알려줍니다.

Windows의 Orca 터미널에서 확인했습니다. 다른 터미널은 이미지를 붙여 넣는 방식이 달라 원본을 못 찾을 수 있고, macOS와 Linux에서는 이미지가 열리지 않습니다.

직접 고쳐 쓰기

git clone https://github.com/glglekdy/claude-mods
claude --plugin-dir ./claude-mods/total-stats
claude plugin validate ./claude-mods/total-stats
claude plugin test ./claude-mods/total-stats
Source 2 files
hooks/register.tsx 102 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Peek } from '../types'
5
6const images = atom({ plugin: 'image-peek', key: 'images' } as const, [])
7
8// 붙여 넣은 글에 들어 있는 이미지 파일 경로 (따옴표로 감싸였어도)
9const IMAGE_PATH = /[A-Za-z]:\\[^"'\r\n]*?\.(?:png|jpe?g|gif|webp|bmp)/i
10// Claude Code가 입력창에 넣는 자리표시자: [Image #1]
11const PLACEHOLDER = /\[Image #(\d+)\]/g
12// 자리표시자가 생긴 순간에 만들어진 파일로 볼 시간 범위
13const FRESH_MS = 15_000
14
15// PowerShell 작은따옴표 문자열 안에 넣을 수 있게 합니다.
16const quote = (s: string) => `'${s.replace(/'/g, "''")}'`
17
18const basename = (path: string) => path.slice(Math.max(path.lastIndexOf('\\'), path.lastIndexOf('/')) + 1)
19
20// 자리표시자 번호 → 원본 파일 경로 (못 찾으면 '')
21let sources = new Map<number, string>()
22
23// 붙여 넣은 직후 임시 폴더에 새로 생긴 이미지 파일을 찾습니다.
24async function freshTempImage($: EngineInterface) {
25  const temp = (await $.env.get('TEMP')) ?? (await $.env.get('TMP'))
26  if (!temp) return ''
27  const now = await $.clock.now()
28  const entries = await $.fs.list(temp).catch(() => [])
29  const newest = entries
30    .filter(f => f.kind === 'file' && /\.(?:png|jpe?g|gif|webp|bmp)$/i.test(f.name) && now - f.mtimeMs < FRESH_MS)
31    .sort((a, b) => b.mtimeMs - a.mtimeMs)[0]
32  return newest ? `${temp}\\${newest.name}` : ''
33}
34
35async function track($: EngineInterface, inputText: string, draft: string) {
36  const pasted = IMAGE_PATH.exec(inputText)?.[0] ?? ''
37
38  const list: Peek[] = []
39  for (const [, n] of draft.matchAll(PLACEHOLDER)) {
40    const num = Number(n)
41    if (!sources.has(num)) sources.set(num, pasted || (await freshTempImage($)))
42    list.push({ label: `Image ${num}`, path: sources.get(num) ?? '' })
43  }
44  // 터미널이 경로를 글자 그대로 넣은 경우도 목록에 올립니다.
45  const raw = IMAGE_PATH.exec(draft)?.[0]
46  if (raw) list.push({ label: basename(raw), path: raw })
47
48  const current = await read($, images)
49  if (JSON.stringify(current) !== JSON.stringify(list)) await update($, images, () => list)
50}
51
52async function openImage($: EngineInterface, path: string) {
53  if (!path) {
54    $.ui.toast('이 이미지의 원본 파일을 찾지 못했어요')
55    return
56  }
57  const { exitCode } = await $.process.run([
58    'powershell.exe', '-NoProfile', '-NonInteractive', '-Command', `Start-Process -FilePath ${quote(path)}`,
59  ])
60  if (exitCode !== 0) $.ui.toast('이미지를 열지 못했어요')
61}
62
63export const register: Register = on => {
64  sources = new Map()
65
66  // 입력창이 바뀔 때마다, 지금 들어 있는 이미지 자리표시자로 목록을 맞춥니다.
67  on('prompt.edit', async ($, e, next) => {
68    const ran = await next(e)
69    const isImageEdit = IMAGE_PATH.test(e.inputText) || e.inputText.includes('[Image') ||
70      e.text.includes('[Image') || ran.text.includes('[Image') || IMAGE_PATH.test(ran.text)
71    if (isImageEdit) await track($, e.inputText, ran.text)
72    return ran
73  }).catch(($, e, next) => next(e))
74
75  // 보내고 나면 입력창이 비므로 목록도 비웁니다.
76  on('prompt.submit', async ($, e, next) => {
77    const ran = await next(e)
78    await update($, images, () => [])
79    return ran
80  }).catch(($, e, next) => next(e))
81
82  // 아래 플러그인(예: total-stats)이 그린 줄을 감싸서 함께 보여줍니다.
83  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
84    const list = await read($, images)
85    if (e.props.hasSurvey || list.length === 0) return next(e)
86
87    const below = await next(e)
88    const { Box, Button, Text } = $.ui.resolve(e)
89    return (
90      <Box flexDirection="column">
91        <Box gap={1}>
92          <Text dimColor>📎</Text>
93          {list.map(img => (
94            <Button key={`${img.label}:${img.path}`} label={img.label} onPress={() => openImage($, img.path)} />
95          ))}
96        </Box>
97        {below}
98      </Box>
99    )
100  })
101}
102
types/index.d.ts 11 lines
1export type Peek = {
2  label: string
3  path: string
4}
5
6declare module 'claude-code' {
7  interface PluginState {
8    'image-peek': { images: Peek[] }
9  }
10}
11