SLOPSHOPPER

effort-cycle

/effort-next raises effort low → medium → high → xhigh → low, skipping max; bind it to a key with command:effort-next

newcommandtoasttimer
v0.1.0no licenseupdated 2026-09-29daanqq/.claude/skills/effort-cycle
A shopper browsing a rack in a slop shop
README

.claude

My Claude Code configuration: instructions, settings, skills, hooks and statusline.

Dependencies

  • git, jq, curl, python3
  • CLIProxyAPI: API key in ~/.claude/secrets/cliproxy-api-key, management settings in ~/.pi/agent/secrets/cliproxy-management.json (statusline quota)
  • Optional: glab (GitLab MR skills), herdr (session hook), pngquant, optipng (optimize-images)

Install

git clone git@github.com:daanqq/.claude.git ~/.claude

If ~/.claude already exists, initialize git there and pull instead:

cd ~/.claude
git init
git remote add origin git@github.com:daanqq/.claude.git
git fetch origin
git checkout -f -b main --track origin/main

Machine-specific overrides go to settings.local.json (ignored).

Source 1 files
hooks/register.ts 81 lines
1import type { EngineInterface, On } from 'claude-code'
2
3/**
4 * The cycle; `max` is left out on purpose, so `max` and anything unknown
5 * step to `low`.
6 */
7export const LEVELS = ['low', 'medium', 'high', 'xhigh'] as const
8
9export const COMMAND = 'effort-next'
10
11export function nextLevelOf(current: unknown): string {
12  const index = LEVELS.findIndex(level => level === current)
13
14  return LEVELS[(index + 1) % LEVELS.length] ?? LEVELS[0]
15}
16
17export function register(on: On): void {
18  // /effort does not report the level back, so the last one set through it or
19  // seen on a model request is the current one; before either, and after
20  // /effort's picker, settings decide.
21  let current: unknown
22
23  on('session.start', async ($, e, next) => {
24    await $.command.register({
25      name: COMMAND,
26      description: 'Raise effort: low → medium → high → xhigh → low',
27      immediate: true,
28    })
29
30    return next(e)
31  })
32
33  on('turn.step', async function* ($, e, next) {
34    if (e.agentId === undefined) {
35      current = e.effort
36    }
37
38    return yield* next(e)
39  })
40
41  on('command.run', { command: 'effort' }, async ($, e, next) => {
42    const result = await next(e)
43    current = e.args.trim() || undefined
44
45    return result
46  })
47
48  on('command.run', { command: COMMAND }, async $ => {
49    current ??= await settingsLevelOf($)
50    const level = nextLevelOf(current)
51    current = level
52    // The engine refuses $.command.run inside a command.run hook, so the
53    // built-in /effort runs once this hook has answered; its output is the
54    // only line the press prints.
55    $.clock.after(0, () => {
56      $.command
57        .run({ command: 'effort', args: level })
58        .catch((error: unknown) => $.ui.toast(`/effort ${level}: ${error}`))
59    })
60
61    return {}
62  })
63}
64
65type EffortSettings = {
66  effortLevel?: unknown
67  modelSettings?: Record<string, { effortLevel?: unknown } | undefined>
68}
69
70// /effort saves the level per model (`claude-opus-5-5`, no `[1m]`), over the
71// top-level one.
72async function settingsLevelOf($: EngineInterface): Promise<unknown> {
73  const [settings, model]: [EffortSettings, string] = await Promise.all([
74    $.settings.read(),
75    $.session.model(),
76  ])
77  const perModel = settings.modelSettings?.[model.replace(/\[.*\]$/, '')]
78
79  return perModel?.effortLevel ?? settings.effortLevel
80}
81