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

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.
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
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.
| Skill | When to use |
|---|---|
file-pr | File or update a concise pull request |
babysit-pr | Monitor a PR through review bots and CI |
arena | Run competing attempts, then graft the strongest ideas into one result |
bro | Restate the last message in plain language |
verify | Prove a behavior change on the user surface with revision-bound evidence |
review | Filter adversarial findings by reachable failure and concrete evidence |
Claude Code mods: plugins of function hooks that change how Claude Code looks and behaves. They are Claude Code only.
| Mod | What it does |
|---|---|
glamour-dark | Draws replies as rich markdown in glamour's dark style, one sentence per line |
marathon-footer | Restyles the hints after the mode label under the prompt to match the SLOPOTHON banner: ⏵⏵ auto mode on · ⇧⇥ CYCLE // ← 1 AGENT |
redact-secrets | Swaps secrets for placeholders before Claude reads them or the session logs them; Write and Edit put the real values back |
tool-lines | Draws each tool call as one compact line and hides its result block |
ascii-spinner | Swaps the working line (verb, timer, tokens, effort, tip) for a one-row WEAVEworm rethreading synthsilk, in Sekiguchi colors |
slopothon | Paints 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.
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.
skills/<name>/SKILL.md with required name and description frontmatter.name matching the directory. Lowercase, hyphens only.references/; keep SKILL.md short enough to load on activation.See agentskills.io/specification.
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.
hooks/register.ts 13 lines1import 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}
13hooks/weaveworm.ts 81 lines1import 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