flowingkhaosSign in
Build a Claude Code mod that fails closedstep 3 of 8 · 0 of 18 carriedSign in to keep it
Step 3 of 8 · weight 2 · about 25 minutes

Read it, then pass the check at the end to open step 4.

Count turns in a band above the prompt

Updated

What this step is for

Your first useful mod only watches: it counts the turns of a session and shows the count in a dim line above the prompt. Watching is the safest move a mod has, because it never changes what Claude does. Our own version of this band shows the context size and the minutes left on the prompt cache; the next spoke article explains why that number matters.

You will meet the two parts every drawn mod needs: a value that lives in the session (atom), and a ui.render hook that draws from it.

Do it

mkdir -p ~/claude-mods/turn-band/.claude-plugin ~/claude-mods/turn-band/hooks ~/claude-mods/turn-band/types
cd ~/claude-mods/turn-band

.claude-plugin/plugin.json. The types line names the contract for the values the mod keeps:

{
  "name": "turn-band",
  "version": "0.1.0",
  "description": "Counts this session's turns above the prompt.",
  "author": { "name": "you" },
  "types": "./types/index.d.ts"
}

types/index.d.ts:

export type Turns = number

declare module 'claude-code' {
  interface PluginState {
    'turn-band': { turns: Turns }
  }
}

hooks/hooks.json:

{ "modules": ["./register.tsx"] }

hooks/register.tsx. JSX compiles against the engine’s h, and the elements come from the surface that draws, so the same code draws in a terminal and in the Desktop app:

import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'

const turns = atom({ plugin: 'turn-band', key: 'turns' } as const, 0)

export const register: Register = on => {
  // Main conversation only: a subagent's turn has an agentId.
  on('turn.complete', async ($, e, next) => {
    if (!e.agentId) await update($, turns, n => n + 1)
    return next(e)
  })

  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
    const n = await read($, turns)
    if (n === 0) return next(e) // nothing to say yet: let the engine draw its own
    const { Text } = $.ui.resolve(e)
    return <Text dimColor>turns {n}</Text>
  })
}

Load it:

claude --plugin-dir ~/claude-mods/turn-band

Check it

  1. claude plugin validate ~/claude-mods/turn-band passes and lists turn.complete and ui.render{component=AbovePrompt}.
  2. In the session, send any prompt. When the reply ends, a dim turns 1 appears above the prompt; the next reply makes it turns 2.
  3. Edit the text to turn {n}, then run /reload-plugins. The line changes, and the count does not reset: the value lives in the session, not in your module.
The gate · a reading check closes this stepweight 2 of Σw 15

Check what you read in “Count turns in a band above the prompt”

Three questions drawn from a larger pool, so no two papers are quite the same. Two right passes it and opens the next step. The questions are public; scoring them needs an account, and the key never leaves the server.

Loading the check…

All 8 stepsTake the check

The columns, fixes and workshops continue in the community: SEO, AI, and making money with code.

Join on Whop

Luke Sidney writes and builds everything here, alone, in the open. lukesidney.me