SLOPSHOPPER

usage-line

Plan limits, cost and token totals above the prompt.

newbandstatus
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-line
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ⏳ 5h limit ────────── 31% 💵 Cost $0.42 ⬇️ Input 0 tok ⬆️ Output 0 tok 📖 Cache read 0 tok 💾 Cache write 0 tok ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
⏳ 5h limit ────────── 31% 💵 Cost $0.42 ⬇️ Input 0 tok ⬆️ Output 0 tok 📖 Cache read 0 tok 💾 Cache write 0 tok
README

Claude Code Agents and Skills

English · Українська · Русский

A portable snapshot of the author's local Claude Code setup, synchronized on 2026-10-15. It contains 18 engineering skills, 14 specialist agents, the Brief output style, and 2 mods, plus portable settings.

Skills cover clean and secure code, testing, documentation, architecture, design patterns, implementation plans, HTTP APIs, databases, interfaces, 3D, observability, resilient services, dependencies and CI, releases, commit messages, and LLM security. Agents provide codebase exploration, planning, web research, diagnosis, implementation, review, security audits, database analysis, documentation, test runs, and verification. The Brief output style keeps every final answer to one short, plain paragraph.

The two mods are Claude Code plugins with a hooks module:

  • brief-guard replaces the Claude Code prompt blocks that conflict with the Brief output style. It swaps system prompt sections and reminders for the texts in its instructions folder, adds the author's standing rules, extends tool descriptions, carries the rules through compaction, and adds reminders after tool errors, agent results, shell commands that read or write local files, and changes made before a skill's reference files or any web source were read. In bypassPermissions mode it also allows every permission request that still reaches you, except AskUserQuestion, EnterPlanMode, and ExitPlanMode.
  • usage-line shows plan limits, cost, and token totals above the prompt: the 5-hour and 7-day limits as gauges that change color as they fill, the session cost in US dollars, and the input, output, cache read, and cache write tokens.

This is a personal configuration, not an official Anthropic distribution. Credentials, account state, chat history, projects, sessions, plugins, synced skills, caches, and machine paths are not included.

[!WARNING] When your settings don't set them, the installer adds "permissions": { "defaultMode": "bypassPermissions" } and "skipDangerousModePermissionPrompt": true. Claude Code then runs every tool call without asking and skips the confirmation dialog for that mode, and brief-guard allows the remaining permission requests. The permission modes documentation recommends this mode only for isolated environments, such as containers and virtual machines without internet access. Review the settings before installing, or set your own permissions.defaultMode first, because the installer keeps existing values.

Install or replace an older setup

Download and extract the ZIP archive, or clone the repository:

git clone https://github.com/dayfinggg/anthropic-claude-code-agent-skills.git
cd anthropic-claude-code-agent-skills

Run one installer from the extracted or cloned folder. It uses the included files without downloading another copy.

Windows

Requires Windows PowerShell 5.1 or PowerShell 7.

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1

macOS and Linux

Requires a POSIX shell, python3 for the settings merge, and a SHA-256 utility, such as sha256sum, shasum, or openssl.

sh scripts/install.sh

Restart Claude Code after installation. The mods load through CLAUDE_CODE_PLUGIN_DIRS, which requires Claude Code v2.1.280 or later.

Replace is the default. The installer backs up and replaces all skills except skills/synced, all agents, the output-styles folder, and the mods folder, removing the old set. It keeps synced skills, credentials, sessions, projects, plugins, and unrelated files. Existing settings values are preserved and missing keys are added, except outputStyle, which becomes Brief, and env.CLAUDE_CODE_PLUGIN_DIRS, which lists the installed mod folders.

Update

To download and install the latest GitHub revision without updating your checkout:

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\update.ps1
sh scripts/update.sh

Updates require internet access, with curl and tar additionally required for the shell updater. The last install mode is reused. If you maintain a Git clone instead, run git pull --ff-only and rerun the installer.

Contents and installation details

The payload files are copied byte for byte from the local setup. The settings copy leaves out env.CLAUDE_CODE_PLUGIN_DIRS, which the installer writes for each platform, and each mod's .claude-plugin/types folder is left out because Claude Code generates those type definitions for the version it runs.

Claude Code keeps its configuration in ~/.claude, or in the folder named by CLAUDE_CONFIG_DIR, as described in Environment variables and Explore the .claude directory. Personal subagents load from its agents folder (Create custom subagents), skills from skills/<skill-name>/SKILL.md (Extend Claude with skills), and output styles from output-styles (Output styles). CLAUDE_CODE_PLUGIN_DIRS loads each listed folder the way --plugin-dir does, with absolute paths separated by ; on Windows and : elsewhere (Mods reference).

Verify the installers

Requires Python 3.11 or newer:

python -m unittest discover -s tests -v

Tests use temporary directories, not your real Claude Code setup. Available Windows PowerShell, PowerShell 7, and POSIX shell implementations are tested; unavailable runtimes are reported as skipped.

Report problems through GitHub issues. Repository instructions and scripts use the MIT License.

Source 3 files
hooks/register.tsx 200 lines
1import { atom, read, update } from 'claude-code'
2import type {
3  ElementConstructor,
4  EngineInterface,
5  Register,
6  RenderChildren,
7  SessionCost,
8  SessionRateLimit,
9  TextProps,
10  TurnUsage,
11} from 'claude-code'
12
13import type { LimitGauge, TokenTotals, UsageSummary } from '../types'
14import { CACHE_READ_ICON, CACHE_WRITE_ICON, COST_ICON, iconForLimit, INPUT_ICON, OUTPUT_ICON } from './icons'
15import type { Icon } from './icons'
16
17const SEPARATOR = '   '
18const HIDDEN_LIMITS = new Set(['spend_limit'])
19const ICON_SIZE = 16
20const BAR_CELLS = 10
21const BAR_LINE = '─'
22const FILL_COLORS = [
23  { from: 0.8, color: '#ef4444' },
24  { from: 0.7, color: '#f97316' },
25  { from: 0.4, color: '#eab308' },
26]
27
28const summary = atom({ plugin: 'usage-line', key: 'summary' } as const, null as UsageSummary)
29const NO_TOKENS: TokenTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
30const tokenTotals = atom({ plugin: 'usage-line', key: 'tokenTotals' } as const, NO_TOKENS)
31
32type BandItem = { key: string; icon: Icon; label: string; draw: (Text: ElementConstructor<TextProps>) => RenderChildren }
33
34export const register: Register = on => {
35  on('session.start', async ($, e, next) => {
36    $.ui.status(undefined)
37    await refreshSummary($)
38
39    return next(e)
40  })
41
42  on('turn.step', async function* ($, e, next) {
43    const response = yield* next(e)
44    const { usage } = response
45
46    if (usage !== null) {
47      await update($, tokenTotals, totals => addUsage(totals ?? NO_TOKENS, usage))
48    }
49
50    if (e.agentId === undefined) {
51      await refreshSummary($)
52    }
53
54    return response
55  })
56
57  on('session.measure', async ($, e, next) => {
58    await update($, summary, () => summarizeUsage(e.rateLimits, e.cost))
59
60    return next(e)
61  })
62
63  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
64    const usage = await read($, summary)
65
66    if (e.props.hasSurvey || usage === null) {
67      return next(e)
68    }
69
70    const rows = bandRows(usage, await read($, tokenTotals))
71
72    if (e.surface === 'desktop') {
73      const { Box, Svg, Text } = $.ui.resolve(e)
74
75      return (
76        <Box flexDirection="column">
77          {rows.map((row, rowIndex) => (
78            <Box key={`row-${rowIndex}`} flexDirection="row" alignItems="center" flexWrap="wrap">
79              {row.flatMap((item, index) => [
80                index === 0 ? null : <Text key={`separator-${item.key}`}>{SEPARATOR}</Text>,
81                <Box key={item.key} flexDirection="row" alignItems="center" flexShrink={0}>
82                  <Svg source={item.icon.svg} alt={item.label} width={ICON_SIZE} height={ICON_SIZE} />
83                  <Text>
84                    {' '}
85                    <Text dimColor>{item.label}</Text> {item.draw(Text)}
86                  </Text>
87                </Box>,
88              ])}
89            </Box>
90          ))}
91        </Box>
92      )
93    }
94
95    const { Box, Text } = $.ui.resolve(e)
96
97    return (
98      <Box flexDirection="column">
99        {rows.map((row, rowIndex) => (
100          <Text key={`row-${rowIndex}`}>
101            {row.flatMap((item, index) => [
102              index === 0 ? '' : SEPARATOR,
103              `${item.icon.emoji} `,
104              <Text dimColor>{item.label}</Text>,
105              ' ',
106              item.draw(Text),
107            ])}
108          </Text>
109        ))}
110      </Box>
111    )
112  })
113}
114
115function bandRows(usage: NonNullable<UsageSummary>, tokens: TokenTotals): BandItem[][] {
116  const limits = usage.limits.map(
117    (limit): BandItem => ({
118      key: limit.kind,
119      icon: iconForLimit(limit.kind),
120      label: limit.label,
121      draw: Text => drawGauge(Text, limit),
122    }),
123  )
124  const cost: BandItem[] =
125    usage.costUsd === null ? [] : [valueItem('cost', COST_ICON, 'Cost', formatUsd(usage.costUsd))]
126  const tokenItems = [
127    valueItem('input', INPUT_ICON, 'Input', formatTokenCount(tokens.input)),
128    valueItem('output', OUTPUT_ICON, 'Output', formatTokenCount(tokens.output)),
129    valueItem('cache-read', CACHE_READ_ICON, 'Cache read', formatTokenCount(tokens.cacheRead)),
130    valueItem('cache-write', CACHE_WRITE_ICON, 'Cache write', formatTokenCount(tokens.cacheWrite)),
131  ]
132
133  return [limits, [...cost, ...tokenItems]].filter(row => row.length > 0)
134}
135
136function valueItem(key: string, icon: Icon, label: string, value: string): BandItem {
137  return { key, icon, label, draw: () => value }
138}
139
140function addUsage(totals: TokenTotals, usage: TurnUsage): TokenTotals {
141  return {
142    input: totals.input + usage.input_tokens,
143    output: totals.output + usage.output_tokens,
144    cacheRead: totals.cacheRead + usage.cache_read_input_tokens,
145    cacheWrite: totals.cacheWrite + usage.cache_creation_input_tokens,
146  }
147}
148
149function drawGauge(Text: ElementConstructor<TextProps>, limit: LimitGauge) {
150  const color = colorForFill(limit.fill)
151  const filledCells = Math.min(BAR_CELLS, Math.max(0, Math.round(limit.fill * BAR_CELLS)))
152
153  return [
154    <Text color={color}>{BAR_LINE.repeat(filledCells)}</Text>,
155    <Text dimColor>{BAR_LINE.repeat(BAR_CELLS - filledCells)}</Text>,
156    ' ',
157    <Text color={color}>{limit.value}</Text>,
158  ]
159}
160
161async function refreshSummary($: EngineInterface): Promise<void> {
162  const { rateLimits, cost } = await $.session.usage()
163  await update($, summary, () => summarizeUsage(rateLimits, cost))
164}
165
166function summarizeUsage(rateLimits: SessionRateLimit[], cost: SessionCost | undefined): UsageSummary {
167  return {
168    limits: rateLimits.filter(limit => !HIDDEN_LIMITS.has(limit.kind)).map(describeLimit),
169    costUsd: cost?.usd ?? null,
170  }
171}
172
173function describeLimit(limit: SessionRateLimit): LimitGauge {
174  return {
175    kind: limit.kind,
176    label: iconForLimit(limit.kind).label,
177    value: `${limit.percentUsed}%`,
178    fill: limit.percentUsed / 100,
179  }
180}
181
182function colorForFill(fill: number): string | undefined {
183  return FILL_COLORS.find(level => fill > level.from)?.color
184}
185
186function formatUsd(usd: number): string {
187  return `$${usd.toFixed(2)}`
188}
189
190function formatTokenCount(tokens: number): string {
191  return `${formatTokens(tokens)} tok`
192}
193
194function formatTokens(tokens: number): string {
195  if (tokens >= 1_000_000) return `${Number((tokens / 1_000_000).toFixed(1))}M`
196  if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}K`
197
198  return `${tokens}`
199}
200
hooks/icons.ts 50 lines
1export type Icon = { svg: string; emoji: string }
2
3export type LimitIcon = Icon & { label: string }
4
5function lucide(body: string): string {
6  return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-2 1 28 28"><g fill="none" stroke="#e8e8e8" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">${body}</g></svg>`
7}
8
9const HOURGLASS: Icon = {
10  emoji: '⏳',
11  svg: lucide(
12    '<path d="M5 22h14M5 2h14m-2 20v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/>',
13  ),
14}
15
16const CALENDAR: Icon = {
17  emoji: '📅',
18  svg: lucide(
19    '<path d="M8 2v3m8-3v3"/><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18M8 13h.01M12 13h.01M16 13h.01M8 17h.01M12 17h.01M16 17h.01"/>',
20  ),
21}
22
23export const COST_ICON: Icon = {
24  emoji: '💵',
25  svg: lucide('<path d="M12 2v20m5-17H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>'),
26}
27
28export const INPUT_ICON: Icon = { emoji: '⬇️', svg: lucide('<path d="M12 17V3m-6 8l6 6l6-6m1 10H5"/>') }
29
30export const OUTPUT_ICON: Icon = { emoji: '⬆️', svg: lucide('<path d="m18 9l-6-6l-6 6m6-6v14m-7 4h14"/>') }
31
32export const CACHE_READ_ICON: Icon = {
33  emoji: '📖',
34  svg: lucide('<path d="M12 2v8m4-4l-4 4l-4-4"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 18h.01M10 18h.01"/>'),
35}
36
37export const CACHE_WRITE_ICON: Icon = {
38  emoji: '💾',
39  svg: lucide('<path d="m16 6l-4-4l-4 4m4-4v8"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 18h.01M10 18h.01"/>'),
40}
41
42const LIMIT_ICONS: Record<string, LimitIcon> = {
43  five_hour: { ...HOURGLASS, label: '5h limit' },
44  seven_day: { ...CALENDAR, label: '7d limit' },
45}
46
47export function iconForLimit(kind: string): LimitIcon {
48  return LIMIT_ICONS[kind] ?? { ...HOURGLASS, label: kind }
49}
50
types/index.d.ts 12 lines
1export type LimitGauge = { kind: string; label: string; value: string; fill: number }
2
3export type UsageSummary = { limits: LimitGauge[]; costUsd: number | null } | null
4
5export type TokenTotals = { input: number; output: number; cacheRead: number; cacheWrite: number }
6
7declare module 'claude-code' {
8  interface PluginState {
9    'usage-line': { summary: UsageSummary; tokenTotals: TokenTotals }
10  }
11}
12