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

My Claude Code configuration: instructions, settings, skills, hooks and statusline.
git, jq, curl, python3~/.claude/secrets/cliproxy-api-key, management settings in ~/.pi/agent/secrets/cliproxy-management.json (statusline quota)glab (GitLab MR skills), herdr (session hook), pngquant, optipng (optimize-images)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).
hooks/register.ts 81 lines1import 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