SLOPSHOPPER

ascii-spinner

Replaces the working line with a one-row WEAVEworm rethreading synthsilk, in Sekiguchi colors

newspinner
v0.1.0MITupdated 2026-10-09binbandit/bin-stack/mods/ascii-spinner
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · ascii-spinner
› 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 ▣ client module ./weaveworm.ts ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Spinner
▣ client module ./weaveworm.ts
README

bin-stack

A harness-agnostic agent skill stack.

Skills follow the Agent Skills format (SKILL.md + optional scripts/references/assets). One source of truth under skills/. Works with Cursor, Claude Code, Codex, and any other agent that discovers skills the standard way.

Inspired by stacks like pstack, but not locked to a single product.

Install

Via skills CLI

npx skills add binbandit/bin-stack

Pick specific skills:

npx skills add binbandit/bin-stack --skill file-pr --skill babysit-pr --skill arena --skill bro

Symlink a clone (one copy, every harness)

git clone https://github.com/binbandit/bin-stack.git ~/Developer/ai/bin-stack
cd ~/Developer/ai/bin-stack
./scripts/link-skills.sh

That links each skill directory into the harness roots you already have (~/.agents/skills, ~/.cursor/skills, ~/.claude/skills, ~/.codex/skills, …). Edit here; every harness sees the same files.

Skills

SkillWhen to use
file-prFile or update a concise pull request
babysit-prMonitor a PR through review bots and CI
arenaRun competing attempts, then graft the strongest ideas into one result
broRestate the last message in plain language
verifyProve a behavior change on the user surface with revision-bound evidence
reviewFilter adversarial findings by reachable failure and concrete evidence

Mods

Claude Code mods: plugins of function hooks that change how Claude Code looks and behaves. They are Claude Code only.

ModWhat it does
glamour-darkDraws replies as rich markdown in glamour's dark style, one sentence per line
marathon-footerRestyles the hints after the mode label under the prompt to match the SLOPOTHON banner: ⏵⏵ auto mode on · ⇧⇥ CYCLE // ← 1 AGENT
redact-secretsSwaps secrets for placeholders before Claude reads them or the session logs them; Write and Edit put the real values back
tool-linesDraws each tool call as one compact line and hides its result block
ascii-spinnerSwaps the working line (verb, timer, tokens, effort, tip) for a one-row WEAVEworm rethreading synthsilk, in Sekiguchi colors
slopothonPaints a SLOPOTHON lockup over the Claude logo in Marathon's design language: block wordmark, a dot-matrix card naming the model and branch, and a livery strip with a tagline and barcode per session. Fullscreen ("tui": "fullscreen") only; it starts each session with a /slopothon row and the logo shows for ~130ms first. Type /slopothon to redraw it inline

Install every mod at user scope:

./scripts/install-mods.sh

The script adds mods/ as the bin-stack marketplace and installs each mod from it. Claude Code reads the mods in place from this clone, so an edit here applies on the next session or /reload-plugins. Re-run it after adding a mod.

Layout

skills/
  file-pr/SKILL.md
  babysit-pr/SKILL.md
  arena/SKILL.md
  bro/SKILL.md
  verify/SKILL.md
  review/SKILL.md
mods/
  .claude-plugin/marketplace.json
  glamour-dark/
  marathon-footer/
  redact-secrets/
  tool-lines/
  ascii-spinner/
  slopothon/
scripts/
  link-skills.sh   # optional: symlink into local harness skill roots
  install-mods.sh  # install the Claude Code mods

Canonical skills live in skills/. Harness-specific paths are adapters (symlinks or CLI installs), not forks of the content.

Authoring

  1. Add skills/<name>/SKILL.md with required name and description frontmatter.
  2. Keep name matching the directory. Lowercase, hyphens only.
  3. Put long reference material in references/; keep SKILL.md short enough to load on activation.
  4. Prefer harness-neutral wording. Call out Cursor / Claude / Codex only when a step truly differs.

See agentskills.io/specification.

License

MIT

The verify and review skills adapt selected pstack guidance by Lauren Tan at revision 9f451cf875ad1239912762f67741e8e5ba6ac0f1. Each skill bundles its provenance and the original MIT notice in LICENSE-pstack so attribution travels with individual skill installs.

Source 2 files
hooks/register.ts 13 lines
1import type { Register } from 'claude-code'
2
3const UNMEASURED_TERMINAL_COLUMNS = 120
4
5export const register: Register = on => {
6  on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
7    if (e.surface !== 'terminal') return next(e)
8    const { Client } = $.ui.resolve(e)
9    const columns = e.viewport?.columns ?? UNMEASURED_TERMINAL_COLUMNS
10    return Client({ key: 'weaveworm', module: './weaveworm.ts', props: { columns } })
11  })
12}
13
hooks/weaveworm.ts 81 lines
1import type { ClientModule, RenderElement } from 'claude-code'
2
3type WeavewormProps = { columns: number }
4type WeavewormState = { frame: number }
5type Cell = { glyph: string; color?: string; backgroundColor?: string }
6type Rgb = readonly [number, number, number]
7
8const SEKIGUCHI_MINT = '#7BCCAB'
9const SEKIGUCHI_MINT_SHADE = '#4F9C80'
10const NONA_RED = '#FF3B30'
11const FRESH_SILK: Rgb = [244, 241, 232]
12const COOLED_SILK: Rgb = [74, 122, 106]
13const SPENT_SILK = '#33524A'
14
15const FRAME_MS = 80
16const WORM_CELLS = 8
17const ARCH_CELLS = 2
18const ARCH_POSITIONS = WORM_CELLS - ARCH_CELLS - 1
19const GLOW_CELLS = 10
20const CORNER_CELLS = 4
21const MIN_TAPE_CELLS = 14
22const MAX_TAPE_CELLS = 24
23const WEAVES = '╳┼╪'
24
25const hexOf = (rgb: Rgb) => `#${rgb.map(channel => Math.round(channel).toString(16).padStart(2, '0')).join('')}`
26
27const lerp = (from: number, to: number, mix: number) => from + (to - from) * mix
28
29const blend = ([r1, g1, b1]: Rgb, [r2, g2, b2]: Rgb, mix: number): Rgb => [
30  lerp(r1, r2, mix),
31  lerp(g1, g2, mix),
32  lerp(b1, b2, mix),
33]
34
35const cooled = (cellsBehind: number) => hexOf(blend(FRESH_SILK, COOLED_SILK, Math.min(1, cellsBehind / GLOW_CELLS) ** 0.7))
36
37function wormCell(segment: number, arch: number): Cell {
38  if (segment === WORM_CELLS - 1) return { glyph: '▀', color: NONA_RED, backgroundColor: SEKIGUCHI_MINT }
39  const color = Math.floor((WORM_CELLS - 2 - segment) / 2) % 2 === 0 ? SEKIGUCHI_MINT : SEKIGUCHI_MINT_SHADE
40  if (segment === 0) return { glyph: '▗', color }
41  return { glyph: segment >= arch && segment < arch + ARCH_CELLS ? '▀' : '▄', color }
42}
43
44const Weaveworm: ClientModule<WeavewormProps, WeavewormState> = (props, surface) => {
45  if (surface.state === undefined) {
46    let frame = 0
47    surface.every(FRAME_MS, () => surface.setState({ frame: ++frame }))
48    surface.setState({ frame })
49  }
50  const { Text } = surface.elements
51  const frame = surface.state?.frame ?? 0
52  const tapeCells = Math.max(MIN_TAPE_CELLS, Math.min(MAX_TAPE_CELLS, props.columns - CORNER_CELLS))
53  const step = Math.floor(frame / ARCH_POSITIONS)
54  const travel = tapeCells + WORM_CELLS
55  const pass = Math.floor(step / travel)
56  const tail = (step % travel) - WORM_CELLS + 1
57  const arch = 1 + (frame % ARCH_POSITIONS)
58
59  const cellAt = (x: number): Cell => {
60    if (x >= tail + WORM_CELLS) return { glyph: WEAVES.charAt(pass % WEAVES.length), color: SPENT_SILK }
61    if (x >= tail) return wormCell(x - tail, arch)
62    return { glyph: WEAVES.charAt((pass + 1) % WEAVES.length), color: cooled(tail - x - 1) }
63  }
64
65  const cells: RenderElement[] = Array.from({ length: tapeCells }, (_, x) => {
66    const { glyph, ...style } = cellAt(x)
67    return Text({ ...style, children: [glyph] })
68  })
69
70  return Text({
71    wrap: 'truncate-end',
72    children: [
73      Text({ color: SEKIGUCHI_MINT, children: ['◤ '] }),
74      ...cells,
75      Text({ color: SEKIGUCHI_MINT, children: [' ◢'] }),
76    ],
77  })
78}
79
80export default Weaveworm
81