SLOPSHOPPER

task-progress

Lets Claude report progress on a long task as a bar in the status line

newguardprompttoolprocess
v0.1.0no licenseupdated 2026-10-02amir20/.dotfiles/claude/.claude/mods/task-progress
A shopper browsing a rack in a slop shop
README

dotfiles

Source 2 files
hooks/register.ts 64 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3import { progressLine } from './line'
4
5const TOOL = 'progress'
6
7let file = ''
8
9// ~/.claude/statusline.sh draws ~/.cache/claude-progress/<session id> as a bar.
10async function progressFile($: EngineInterface) {
11  if (!file) {
12    const home = await $.process.run(['printenv', 'HOME'])
13    file = `${home.stdout.trim()}/.cache/claude-progress/${await $.session.id()}`
14  }
15  return file
16}
17
18// Read every turn, so the bar shows up without the person having to ask for it.
19const GUIDANCE = {
20  id: 'task-progress:guidance',
21  scope: 'session',
22  text:
23    '# Progress bar\n' +
24    `When a request will take several steps (a feature, a refactor, a multi-file fix, a long investigation), ` +
25    `report progress with the mcp__task-progress__${TOOL} tool without being asked: once when you start, ` +
26    'again as each major step finishes, and with percent 100 when done. Keep the percent honest and the step ' +
27    'to a few words. Skip it for quick questions and one-step edits, and never mention it in your replies.',
28} as const
29
30export const register: Register = on => {
31  on('prompt.compose', async ($, e, next) => {
32    const composed = await next(e)
33    return { sections: [...composed.sections, GUIDANCE] }
34  })
35
36  on('session.start', async ($, e, next) => {
37    const r = await next(e)
38    await $.tool.register({
39      name: TOOL,
40      description:
41        'Show the person a progress bar in their status line for the multi-step task you are working on. ' +
42        'Call it when you start a long task and again each time a step finishes, with an honest overall percent ' +
43        'and the step now under way. Call it with percent 100 when the task is done. It returns nothing useful; ' +
44        'do not mention calling it.',
45      inputSchema: {
46        type: 'object',
47        properties: {
48          percent: { type: 'number', minimum: 0, maximum: 100, description: 'Overall completion, 0 to 100.' },
49          step: { type: 'string', description: 'A few words naming the step now under way, or "done".' },
50        },
51        required: ['percent', 'step'],
52      },
53    })
54    return r
55  })
56
57  on('tool.call', { tool: `mcp__task-progress__${TOOL}` }, async ($, e) => {
58    const line = progressLine(e as { percent?: unknown; step?: unknown })
59    if (!line) return { deny: 'percent must be a number from 0 to 100' }
60    await $.fs.write(await progressFile($), `${line}\n`)
61    return { result: 'ok' }
62  })
63}
64
hooks/line.ts 9 lines
1// The file ~/.claude/statusline.sh reads: `<percent>\t<step>`, one line.
2export function progressLine(input: { percent?: unknown; step?: unknown }): string | undefined {
3  const percent = Number(input.percent)
4  if (!Number.isFinite(percent)) return undefined
5  const clamped = Math.round(Math.min(100, Math.max(0, percent)))
6  const step = typeof input.step === 'string' ? input.step.replace(/[\t\r\n]+/g, ' ').trim().slice(0, 80) : ''
7  return `${clamped}\t${step}`
8}
9