Five setting profiles in a side pane (Token saver, Fast flow, Max quality, Guarded, Autopilot): press one to see exactly what it sets, then Activate; every…

Five setting profiles in a side pane. Each one sets Claude Code up for one way of working: press a profile to see exactly what it sets, then press Activate. Every activation can be undone.
⚙ ULTRA CONFIG default · default effort · default mode
──────────────────────────────────────────────────────────────
▾ ◔ Token saver ■■■□□□□□□□ 3/11
Sonnet, low effort, short answers, Haiku subagents
╭────────────────────────────────────────────────────────────╮
│ ● Model Default (recommended) → sonnet │
│ cheaper per token than Opus │
│ ● Effort medium → low │
│ less thinking per request │
│ ✓ Auto-compact on │
│ … │
│ [ Activate Token saver ] │
╰────────────────────────────────────────────────────────────╯
▸ » Fast flow ■■■□□□□□□□ 3/8
Edits auto-accepted, medium effort, fewer interruptions
▸ ◆ Max quality ■■■■■■□□□□ 6/9
▸ ▣ Guarded ■■■□□□□□□□ 2/7
▸ ➤ Autopilot ■■■■■□□□□□ 4/8
| Command | What it does |
|---|---|
/ultra-config | Opens the pane |
/ultra-config <profile> | Opens the pane on that profile |
/ultra-config apply <profile> | Activates a profile and lists what it changed and skipped |
/ultra-config undo | Reverts the last activated profile (newest first) |
/ultra-config close | Closes the pane |
Profile ids: saver, flow, quality, guarded, autopilot.
In the pane everything is a press: a profile's name opens its detail (one is always open, the first to start), and Activate applies it (a while the pane has focus). There are no tabs or arrow-key menus, so nothing competes with Claude Code's own ← for the agents view.
| Profile | For | Sets |
|---|---|---|
| ◔ Token saver | Spending fewer tokens | Model sonnet, effort low, subagents on haiku, Concise output, no prompt suggestions or recap, auto-compact on, small workflows, no ultracode keyword, MCP output capped at 10,000 tokens, fast mode off |
| » Fast flow | Less waiting | Model sonnet, effort medium, edits auto-accepted, Concise output, prompt suggestions on, turn duration and progress bar on, medium workflows |
| ◆ Max quality | The best result | Model best, effort xhigh, subagents keep their own model, default output, workflows on with large fan-outs and the ultracode keyword, checkpoints and auto-compact on |
| ▣ Guarded | Control and safety | Every edit and command asks first, plan mode never acts on its own, bypassPermissions disabled, Read deny rules for .env, secrets/, ~/.ssh and ~/.aws (they stop the Read tool, not a Bash cat), checkpoints on, verbose output, no ultracode keyword |
| ➤ Autopilot | Long unattended runs (fewer approval prompts) | Auto permission mode (also during plan), effort high, workflows on, recap on, push notifications when Claude decides or needs you, auto-compact on |
Each profile shows how much of it is already in force (3/11), and ✓ active when all of it is. In the detail, ● is a change (old value struck through, then the new one and why), ✓ is already set and ⊘ is skipped.
/config rows (model, permission mode, output style, auto-compact…) are written with $.config.set, exactly as if you changed them in /config. Claude Code picks the file: most go to ~/.claude/settings.json or ~/.claude.json, and Output style goes to the project's .claude/settings.local.json, the same as the menu./effort saves it: modelSettings["<model id>"].effortLevel in ~/.claude/settings.json, for the model your next message runs on (the mod learns its id from that request and sends the request at the new effort too). Since Claude Code 2.1.251 a top-level effortLevel in user settings is ignored by Opus 5.5 and later models, so the mod never writes it. A model id with a dot (Bedrock, Vertex) can't be saved this way; then the effort applies to that one request only./config row (env.CLAUDE_CODE_SUBAGENT_MODEL, env.MAX_MCP_OUTPUT_TOKENS, permissions.deny, permissions.disableBypassPermissionsMode) are written to ~/.claude/settings.json (or $CLAUDE_CONFIG_DIR/settings.json). Before each write the file is copied to settings.json.ultra-config.bak (only the latest copy is kept; it holds whatever the file held, env values included). A file that is not valid JSON is never overwritten: those steps are skipped and the pane says why.Every activated profile is kept (the last 20) in the plugin's store, so undo works after a restart and puts back exactly the values it changed. A deny rule you already had stays when you undo. A profile never touches a row your organization locked, a value this Claude Code version does not offer, or a row Claude Code reserves for its own panel (it says so, for example "can only be turned on from the /config panel"); those show as ⊘ in the detail.
The /config rows apply right away and the effort from your next message. The env variables (CLAUDE_CODE_SUBAGENT_MODEL, MAX_MCP_OUTPUT_TOKENS) are read when Claude Code starts, so count on them from the next session.
In the fullscreen layout the pane docks beside the transcript (/tui fullscreen, at least 110 columns). On the classic layout it sits above the prompt. In Claude Desktop it draws in the session's side panel; padded cells use non-breaking spaces so the columns keep their width.
Set them in user or managed settings under pluginConfigs["ultra-config@skills-dir"].options ("ultra-config" when loaded with --plugin-dir):
| Option | Default | What it does |
|---|---|---|
columns | 62 | Width asked for the docked pane (44–100); a width you drag it to wins |
npx claude-code-templates@latest --mod productivity/ultra-config
Needs Claude Code 2.1.287 or later and a trusted project folder (or install it under ~/.claude/skills/).
Claude Code writes /config rows from the settings it holds in memory, and it does not reload a settings.json write that lands right after one of its own. So the mod writes settings.json first, waits until $.settings.read shows the new values (writing the file once more if the first write was not picked up), and only then sets /config rows. If the engine never reloads the file, the row changes are skipped and the pane says to restart the session. Without this ordering, the next /config change would silently put back the old values.
Tests: claude plugin test productivity/ultra-config (from cli-tool/components/mods).
hooks/register.tsx 560 lines1/**
2 * ultra-config — Claude Mod
3 *
4 * A side pane with five profiles (Token saver, Fast flow, Max quality,
5 * Guarded, Autopilot) that set Claude Code up for one way of working in one
6 * press. Each profile opens to show exactly what it sets, with one button to
7 * activate it.
8 *
9 * `/ultra-config` opens it · `/ultra-config <profile>` opens that profile ·
10 * `/ultra-config apply <profile>` activates it · `/ultra-config undo`
11 * reverts the last one · `/ultra-config close` closes the pane
12 *
13 * `/config` rows are written with `$.config.set`, the way the menu writes
14 * them; effort, the subagent model, env caps and deny rules (keys with no
15 * `/config` row) are written to ~/.claude/settings.json, after a copy of the
16 * file is saved beside it as settings.json.ultra-config.bak. Each activated
17 * profile is kept in the plugin's store so `undo` puts back exactly what it
18 * changed.
19 *
20 * Needs Claude Code >= 2.1.287 (mods on by default); every UI prop it uses
21 * is in that version's API, so the Desktop app's bundled engine draws it too.
22 *
23 * Options (pluginConfigs["ultra-config@skills-dir"].options):
24 * columns: number width asked for the docked pane (default 62)
25 */
26import { atom, read, update } from 'claude-code'
27import type { ConfigRow, EngineInterface, Register } from 'claude-code'
28
29import type {
30 UltraConfigBackup as Backup,
31 UltraConfigEntry,
32 UltraConfigLive,
33 UltraConfigPending,
34 UltraConfigSnapshot as Snapshot,
35 UltraConfigUi,
36} from '../types'
37import {
38 PROFILES,
39 applySettings,
40 currentEffort,
41 effortPath,
42 inSync,
43 matchOf,
44 nb,
45 pad,
46 paneColumns,
47 planFor,
48 profileById,
49 restorable,
50 revertSettings,
51 show,
52 userSettingsPath,
53} from './profiles.ts'
54import type { Effort, Json, ListStep, Live, PlanItem, Profile, SettingsStep } from './profiles.ts'
55
56const PANE = 'ultra-config'
57const COMMAND = 'ultra-config'
58const STORE_HISTORY = 'history'
59const KEEP = 20
60/** How long a write waits for the engine to reload it, twice at most: 20 × 150 ms. */
61const RELOAD_TRIES = 20
62const RELOAD_STEP_MS = 150
63const INK = '#141414'
64const ACCENT = '#d97757'
65
66type Host = EngineInterface
67
68const ui = atom({ plugin: 'ultra-config', key: 'ui' } as const, {
69 selected: null,
70 notice: null,
71 isBusy: false,
72} satisfies UltraConfigUi)
73const snapshot = atom({ plugin: 'ultra-config', key: 'snapshot' } as const, {
74 rows: [],
75 user: {},
76 path: null,
77 error: null,
78} satisfies Snapshot)
79const history = atom({ plugin: 'ultra-config', key: 'history' } as const, [] as Backup[])
80/** The main loop's model id and effort, as the last request carried them. */
81const live = atom({ plugin: 'ultra-config', key: 'live' } as const, {} as UltraConfigLive)
82/** An activated effort waiting for the next request to learn the model id it is saved under. */
83const pending = atom({ plugin: 'ultra-config', key: 'pending' } as const, null as UltraConfigPending | null)
84const STORE_PENDING = 'pendingEffort'
85
86const message = (err: unknown): string => (err instanceof Error ? err.message : String(err))
87
88// This module holds every call on `$`; profiles.ts is pure.
89
90/** Reads the settings file, or says why it can't be trusted for a write. */
91async function readUser($: Host, path: string | null): Promise<{ user: Json; text: string | null; error: string | null }> {
92 if (path === null) return { user: {}, text: null, error: 'no home directory to find settings.json in' }
93 let text: string
94 try {
95 text = await $.fs.read(path)
96 } catch {
97 // no file yet is fine; a file the engine reads but we can't is not
98 const loaded = await $.settings.read({ source: 'user' }).catch(() => ({}))
99 return Object.keys(loaded).length === 0
100 ? { user: {}, text: null, error: null }
101 : { user: { ...loaded }, text: null, error: `cannot read ${path}` }
102 }
103 if (text.trim() === '') return { user: {}, text, error: null }
104 try {
105 const parsed: unknown = JSON.parse(text)
106 if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
107 return { user: {}, text, error: 'settings.json is not a JSON object' }
108 return { user: parsed as Json, text, error: null }
109 } catch (err) {
110 return { user: {}, text, error: `settings.json is not valid JSON (${message(err)})` }
111 }
112}
113
114async function settingsPath($: Host): Promise<string | null> {
115 const dir = await $.env.get('CLAUDE_CONFIG_DIR')
116 const home = (await $.env.get('HOME')) ?? (await $.env.get('USERPROFILE'))
117 return userSettingsPath(dir, home) ?? null
118}
119
120async function refresh($: Host): Promise<Snapshot> {
121 const rows = await $.config.list().catch(() => [] as ConfigRow[])
122 const path = await settingsPath($)
123 const { user, error } = await readUser($, path)
124 const next: Snapshot = { rows: [...rows], user, path, error }
125 await update($, snapshot, () => next)
126 return next
127}
128
129async function notify($: Host, text: string, tone: 'success' | 'warning' | 'error'): Promise<void> {
130 await update($, ui, s => ({ ...s, notice: { text, tone } }))
131}
132
133async function saveHistory($: Host, list: Backup[]): Promise<void> {
134 await update($, history, () => list)
135 await $.store.set(STORE_HISTORY, list).catch(err => $.ui.log(`ultra-config: history not saved: ${message(err)}`))
136}
137
138/**
139 * Writes the settings object, keeping a copy of the file as it was, and
140 * resolves once the engine has loaded it (false if it never did).
141 *
142 * The engine writes /config rows from the settings it holds in memory, so a
143 * `$.config.set` before it reloads would put back what this write changed.
144 * And it does not reload a write that lands just after one of its own: then
145 * the file is written again, once, which it does pick up.
146 */
147async function writeUser($: Host, path: string, before: string | null, next: Json, paths: string[]): Promise<boolean> {
148 if (before !== null) await $.fs.write(`${path}.ultra-config.bak`, before)
149 const text = `${JSON.stringify(next, null, 2)}\n`
150 for (const body of [text, text.trimEnd()]) {
151 await $.fs.write(path, body)
152 for (let i = 0; i < RELOAD_TRIES; i++) {
153 const loaded = await $.settings.read({ source: 'user' }).catch(() => undefined)
154 if (loaded && inSync(loaded, next, paths)) return true
155 await $.clock.sleep(RELOAD_STEP_MS)
156 }
157 }
158 $.ui.log(`ultra-config: Claude Code did not reload ${path}`)
159 return false
160}
161
162const NOT_RELOADED = 'Claude Code did not reload settings.json; restart the session, then apply again'
163
164/** The model and effort in use, with an effort waiting for the next request. */
165async function liveNow($: Host): Promise<Live> {
166 const want = await read($, pending)
167 return { ...(await read($, live)), ...(want ? { pending: want.value } : {}) }
168}
169
170async function setPending($: Host, value: UltraConfigPending | null): Promise<void> {
171 await update($, pending, () => value)
172 await $.store.set(STORE_PENDING, value).catch(err => $.ui.log(`ultra-config: pending effort not saved: ${message(err)}`))
173}
174
175/**
176 * Saves an activated effort under the model id this request names, and adds
177 * that write to the activation's history entry so undo puts it back. Returns
178 * false when it could not be written (the request then still gets the effort).
179 */
180async function saveEffort($: Host, model: string, want: UltraConfigPending): Promise<boolean> {
181 const path = await settingsPath($)
182 const at = effortPath(model)
183 if (path === null || at === undefined) {
184 $.ui.log(`ultra-config: effort not saved for ${model} (no settings.json, or a model id with a dot)`)
185 return false
186 }
187 const { user, text, error } = await readUser($, path)
188 if (error !== null) {
189 $.ui.log(`ultra-config: effort not saved: ${error}`)
190 return false
191 }
192 const { next, entries } = applySettings(user, [{ layer: 'settings', path: at, label: 'Effort', value: want.value, why: '' }])
193 if (entries.length === 0) return true
194 const list = await read($, history)
195 await saveHistory($, list.map(b => (b.id === want.backupId ? { ...b, entries: [...b.entries, ...entries] } : b)))
196 return writeUser($, path, text, next, [at])
197}
198
199async function applyProfile($: Host, profile: Profile): Promise<Backup> {
200 const snap = await refresh($)
201 const plan = planFor(profile, snap.rows, snap.user, await liveNow($))
202 const entries: UltraConfigEntry[] = []
203 const refused: { label: string; reason: string }[] = []
204 let isSynced = true
205
206 // settings.json first: the /config writes below then start from it
207 const fileSteps = profile.steps.filter((s): s is SettingsStep | ListStep => s.layer === 'settings' || s.layer === 'list')
208 if (fileSteps.length > 0) {
209 const { user, text, error } = await readUser($, snap.path)
210 if (error !== null || snap.path === null) {
211 for (const s of fileSteps) refused.push({ label: s.label, reason: error ?? 'settings.json not found' })
212 } else {
213 const { next, entries: written } = applySettings(user, fileSteps)
214 if (written.length > 0) {
215 // recorded before the write: a write that fails halfway can still be undone
216 entries.push(...written)
217 try {
218 isSynced = await writeUser($, snap.path, text, next, written.flatMap(w => (w.layer === 'config' ? [] : [w.path])))
219 } catch (err) {
220 isSynced = false
221 for (const w of written) refused.push({ label: w.label, reason: `may not be written: ${message(err)}` })
222 }
223 }
224 }
225 }
226
227 for (const step of profile.steps) {
228 if (step.layer !== 'config') continue
229 const item = plan.find(p => p.id === `config:${step.key}`)!
230 if (item.status === 'same') continue
231 if (item.status !== 'change') {
232 refused.push({ label: item.label, reason: SKIP[item.status] })
233 continue
234 }
235 if (!isSynced) {
236 refused.push({ label: item.label, reason: NOT_RELOADED })
237 continue
238 }
239 const row = snap.rows.find(r => r.key === step.key)!
240 const r = await $.config.set({ key: step.key, value: step.value }).catch(err => ({ deny: message(err) }))
241 if (r.deny !== undefined) refused.push({ label: item.label, reason: r.deny })
242 else entries.push({ layer: 'config', key: step.key, label: row.label, previous: restorable(row) })
243 }
244
245 // effort is saved per model, under the id the next request names
246 const effortStep = profile.steps.find(s => s.layer === 'effort')
247 const effortItem = plan.find(p => p.id === 'effort')
248 const effort: Effort | undefined = effortStep && effortItem?.status === 'change' ? effortStep.value : undefined
249
250 const backup: Backup = {
251 id: `${Date.now()}`,
252 profile: profile.id,
253 name: profile.name,
254 at: new Date().toISOString(),
255 entries,
256 refused,
257 ...(effort ? { effort } : {}),
258 }
259 if (entries.length > 0 || effort) await saveHistory($, [backup, ...(await read($, history))].slice(0, KEEP))
260 if (effort) await setPending($, { value: effort, backupId: backup.id })
261 await refresh($)
262 return backup
263}
264
265async function undoLast($: Host): Promise<{ text: string; tone: 'success' | 'warning' | 'error' }> {
266 const [last, ...rest] = await read($, history)
267 if (!last) return { text: 'Nothing to undo', tone: 'warning' }
268 // an effort not yet saved for any model is simply dropped
269 if ((await read($, pending))?.backupId === last.id) await setPending($, null)
270 const failed: string[] = []
271 let isSynced = true
272 // settings.json first, as when applying
273 const fileEntries = last.entries.filter(e => e.layer !== 'config')
274 if (fileEntries.length > 0) {
275 const path = await settingsPath($)
276 const { user, text, error } = await readUser($, path)
277 if (error !== null || path === null) failed.push(`settings.json (${error ?? 'not found'})`)
278 else
279 isSynced = await writeUser($, path, text, revertSettings(user, fileEntries), fileEntries.map(f => f.path)).catch(err => {
280 failed.push(`settings.json (${message(err)})`)
281 return true
282 })
283 }
284 for (const entry of [...last.entries].reverse()) {
285 if (entry.layer !== 'config') continue
286 if (!isSynced) {
287 failed.push(`${entry.label} (${NOT_RELOADED})`)
288 continue
289 }
290 const r = await $.config.set({ key: entry.key, value: entry.previous }).catch(err => ({ deny: message(err) }))
291 if (r.deny !== undefined) failed.push(`${entry.label} (${r.deny})`)
292 }
293 // a settings.json that could not be restored keeps the entry, so undo can be tried again
294 const fileFailed = failed.some(f => f.startsWith('settings.json'))
295 if (!fileFailed) await saveHistory($, rest)
296 await refresh($)
297 if (fileFailed) return { text: `Not undone: ${failed.join(', ')}. Fix it and run /ultra-config undo again`, tone: 'error' }
298 return failed.length === 0
299 ? { text: `Undid ${last.name}: ${last.entries.length} setting${last.entries.length === 1 ? '' : 's'} restored`, tone: 'success' }
300 : { text: `Undid ${last.name}, except ${failed.join(', ')}`, tone: 'warning' }
301}
302
303const SKIP: Record<'locked' | 'missing' | 'invalid', string> = {
304 locked: 'locked by your organization',
305 missing: 'not in this Claude Code version',
306 invalid: 'value not offered here',
307}
308
309const summary = (b: Backup): string => {
310 const n = b.entries.length + (b.effort ? 1 : 0)
311 const head = n === 0 ? `${b.name}: already in place` : `${b.name} activated: ${n} setting${n === 1 ? '' : 's'} changed`
312 const tail = b.effort ? ` (effort ${b.effort} from your next message)` : ''
313 return b.refused.length === 0 ? `${head}${tail}` : `${head}${tail}, ${b.refused.length} skipped`
314}
315
316function planText(profile: Profile, plan: PlanItem[]): string {
317 const lines = [`${profile.icon} ${profile.name}: ${profile.tagline}`]
318 for (const p of plan) {
319 const mark = p.status === 'change' ? '●' : p.status === 'same' ? '✓' : '⊘'
320 const tail = p.status === 'change' ? `${p.from} → ${p.to} (${p.why})` : p.status === 'same' ? p.to : SKIP[p.status]
321 lines.push(` ${mark} ${p.label}: ${tail}`)
322 }
323 lines.push(`/ultra-config apply ${profile.id} to apply`)
324 return lines.join('\n')
325}
326
327export const register: Register = (on, options) => {
328 const columns = paneColumns(options.columns)
329
330 on('session.start', async ($, e, next) => {
331 const r = await next(e)
332 await $.command
333 .register({
334 name: COMMAND,
335 description: 'Setting profiles: token saver, fast flow, max quality, guarded, autopilot',
336 argumentHint: '[profile | apply <profile> | undo | close]',
337 })
338 .catch(err => $.ui.log(`ultra-config: /${COMMAND} not registered: ${message(err)}`))
339 const stored = await $.store.get(STORE_HISTORY).catch(() => undefined)
340 if (Array.isArray(stored)) await update($, history, () => stored as Backup[])
341 const want = await $.store.get(STORE_PENDING).catch(() => undefined)
342 if (want && typeof want === 'object') await update($, pending, () => want as UltraConfigPending)
343 return r
344 })
345
346 // The main loop's requests: learn the model id and effort in use, and save
347 // an activated effort under that id (and send it with this request).
348 on('turn.step', async function* ($, e, next) {
349 if (e.agentId) return yield* next(e)
350 const want = await read($, pending)
351 if (!want) {
352 await update($, live, () => ({ model: e.model, effort: e.effort }))
353 return yield* next(e)
354 }
355 await setPending($, null)
356 await saveEffort($, e.model, want).catch(err => {
357 $.ui.log(`ultra-config: effort not saved: ${message(err)}`)
358 return false
359 })
360 await update($, live, () => ({ model: e.model, effort: want.value }))
361 await refresh($).catch(() => undefined)
362 return yield* next({ ...e, effort: want.value })
363 })
364
365 // keep the cards in step with changes made in /config itself
366 on('config.set', async ($, e, next) => {
367 const r = await next(e)
368 if (r.deny === undefined) await refresh($).catch(() => undefined)
369 return r
370 }).catch(($, e, next) => next(e))
371
372 on('command.run', { command: COMMAND }, async ($, e) => {
373 const [verb = '', arg = ''] = e.args.trim().split(/\s+/)
374 const word = verb.toLowerCase()
375 if (word === 'close' || word === 'stop') {
376 await $.ui.close({ id: PANE }).catch(() => undefined)
377 return { text: 'ultra-config closed' }
378 }
379 if (word === 'undo') {
380 const { text, tone } = await undoLast($)
381 await notify($, text, tone)
382 return { text }
383 }
384 if (word === 'apply') {
385 const profile = profileById(arg)
386 if (!profile) return { text: `Unknown profile "${arg}". Profiles: ${PROFILES.map(p => p.id).join(', ')}` }
387 const b = await applyProfile($, profile)
388 const text = summary(b)
389 await notify($, text, b.refused.length === 0 ? 'success' : 'warning')
390 const skipped = b.refused.map(x => `\n ⊘ ${x.label}: ${x.reason}`).join('')
391 return { text: `${text}${skipped}\n/ultra-config undo reverts it` }
392 }
393 const profile = word === '' ? undefined : profileById(word)
394 if (word !== '' && !profile)
395 return { text: `Unknown argument "${word}". Try a profile (${PROFILES.map(p => p.id).join(', ')}), apply, undo or close.` }
396 const snap = await refresh($)
397 await update($, ui, s => ({
398 ...s,
399 selected: profile ? profile.id : s.selected,
400 notice: null,
401 }))
402 let placed = true
403 try {
404 placed = (await $.ui.open({ id: PANE, title: 'ultra config', focus: true, columns })).isPlaced
405 } catch (err) {
406 placed = false
407 $.ui.log(`ultra-config: pane not opened: ${message(err)}`)
408 }
409 if (profile) {
410 const plan = planFor(profile, snap.rows, snap.user, await liveNow($))
411 if (!placed) return { text: planText(profile, plan) }
412 const n = plan.filter(p => p.status === 'change').length
413 return { text: `${profile.icon} ${profile.name}: ${n} change${n === 1 ? '' : 's'}, shown in the pane · /ultra-config apply ${profile.id}` }
414 }
415 if (!placed) return { text: PROFILES.map(p => `${p.icon} ${p.id}: ${p.tagline}`).join('\n') }
416 // a Desktop session reports isFullscreen false too, so the reply names no layout
417 return { text: 'ultra-config opened · press a profile to see what it sets · /ultra-config close' }
418 })
419
420 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
421 const { Box, Text, Button } = $.ui.resolve(e)
422 const W = Math.max(40, (e.props.bodyColumns ?? columns) - 1)
423 const state = await read($, ui)
424 const snap = await read($, snapshot)
425 const now = await liveNow($)
426 const set = (patch: Partial<UltraConfigUi>) => update($, ui, s => ({ ...s, ...patch }))
427
428 const effort = currentEffort(snap.user, now)
429 const model = snap.rows.find(r => r.key === 'model')
430 const mode = snap.rows.find(r => r.key === 'permissionMode')
431 const facts = [show(model?.value).replace(/ \(.*\)$/, '').toLowerCase(), `${effort === undefined ? 'default' : show(effort)} effort`, `${show(mode?.value)} mode`]
432
433 // one profile is always open; the first until another is pressed
434 const open = (state.selected ? profileById(state.selected) : undefined) ?? PROFILES[0]!
435 const labelW = Math.min(27, Math.max(14, Math.floor(W * 0.44)))
436
437 const activate = async (p: (typeof PROFILES)[number]) => {
438 if ((await read($, ui)).isBusy) return
439 await set({ isBusy: true })
440 try {
441 const b = await applyProfile($, p)
442 await set({ notice: { text: `${summary(b)} · /ultra-config undo reverts it`, tone: b.refused.length === 0 ? 'success' : 'warning' } })
443 $.ui.toast(`${p.icon} ${summary(b)}`)
444 } catch (err) {
445 await set({ notice: { text: `Not activated: ${message(err)}`, tone: 'error' } })
446 } finally {
447 await set({ isBusy: false })
448 }
449 }
450
451 const detail = (p: (typeof PROFILES)[number]) => {
452 const plan = planFor(p, snap.rows, snap.user, now)
453 const changes = plan.filter(x => x.status === 'change').length
454 // every step, so Activate never applies one that was not shown; the pane scrolls
455 const lines = []
456 for (const x of plan) {
457 if (x.status === 'change')
458 lines.push(
459 <Box key={`chg:${x.id}`} flexDirection="column">
460 <Text wrap="truncate-end">
461 <Text color={p.color}>● </Text>
462 <Text>{pad(x.label, labelW)}</Text>
463 {/* drop the menu hint, e.g. "Opus (default)" -> "Opus", so the old -> new row fits a narrow pane */}
464 <Text dimColor strikethrough>{x.from.replace(/ \(.*\)$/, '')}</Text>
465 <Text dimColor>{nb(' → ')}</Text>
466 <Text color={p.color} bold>{x.to}</Text>
467 </Text>
468 <Text color="#8a8a8a" wrap="truncate-end">{nb(` ${x.why}`)}</Text>
469 </Box>,
470 )
471 else if (x.status === 'same')
472 lines.push(
473 <Text key={`same:${x.id}`} wrap="truncate-end">
474 <Text color="success">✓ </Text>
475 <Text dimColor>{pad(x.label, labelW)}{x.to}</Text>
476 </Text>,
477 )
478 else
479 lines.push(
480 <Text key={`skip:${x.id}`} color="warning" wrap="truncate-end">
481 ⊘ <Text dimColor>{`${x.label}: ${SKIP[x.status]}`}</Text>
482 </Text>,
483 )
484 }
485 return (
486 <Box key={`detail:${p.id}`} flexDirection="column" borderStyle="round" borderColor={p.color} paddingX={1} width={W}>
487 {lines}
488 <Box flexDirection="row" marginTop={1}>
489 {changes > 0 ? (
490 <Button
491 key={`activate:${p.id}`}
492 variant="primary"
493 hotkey="a"
494 autoFocus
495 label={state.isBusy ? 'Activating…' : `Activate ${p.name}`}
496 onPress={() => activate(p)}
497 />
498 ) : (
499 <Text color="success" bold>✓ Active: nothing to change</Text>
500 )}
501 </Box>
502 </Box>
503 )
504 }
505
506 const cards = PROFILES.map(p => {
507 const plan = planFor(p, snap.rows, snap.user, now)
508 const { same, of } = matchOf(plan)
509 const isActive = of > 0 && same === of
510 const isOpen = p.id === open.id
511 const meter = 10
512 const lit = of === 0 ? 0 : Math.round((same / of) * meter)
513 return (
514 <Box key={`card:${p.id}`} flexDirection="column" width={W}>
515 <Box flexDirection="row" justifyContent="space-between" width={W}>
516 <Box flexDirection="row">
517 <Text>
518 <Text color={isOpen ? p.color : '#5a5a5a'}>{isOpen ? '▾ ' : '▸ '}</Text>
519 <Text backgroundColor={p.color} color={INK} bold>{nb(` ${p.icon} `)}</Text>
520 <Text>{'\u00a0'}</Text>
521 </Text>
522 {/* a label string, not rich children: Claude Code 2.1.287 (and the Desktop build) skips the whole render on those */}
523 <Button key={`pick:${p.id}`} plain dimColor={!isOpen} label={p.name} onPress={() => set({ selected: p.id, notice: null })} />
524 </Box>
525 <Text>
526 <Text color={p.color}>{'■'.repeat(lit)}</Text>
527 <Text color="#3a3a3a">{'■'.repeat(meter - lit)}</Text>
528 <Text color={isActive ? 'success' : undefined} dimColor={!isActive}>
529 {nb(isActive ? ' ✓ active' : ` ${String(same).padStart(2)}/${of}`.padEnd(10))}
530 </Text>
531 </Text>
532 </Box>
533 <Text dimColor wrap="truncate-end">{nb(` ${p.tagline}`)}</Text>
534 {isOpen && detail(p)}
535 </Box>
536 )
537 })
538
539 return (
540 <Box flexDirection="column" width={W} gap={1}>
541 <Box key="head" flexDirection="column" width={W}>
542 <Box flexDirection="row" justifyContent="space-between" width={W}>
543 <Text>
544 <Text backgroundColor={ACCENT} color={INK} bold>{nb(' ⚙ ULTRA ')}</Text>
545 <Text backgroundColor="#3a2a24" color="#f4ece6" bold>{nb(' CONFIG ')}</Text>
546 </Text>
547 <Text dimColor>{nb(facts.join(' · '))}</Text>
548 </Box>
549 <Text color="#3a3a3a">{'─'.repeat(W)}</Text>
550 {state.notice && (
551 <Text color={state.notice.tone}>{state.notice.tone === 'success' ? '✓ ' : '! '}{state.notice.text}</Text>
552 )}
553 {snap.error && <Text color="warning">! {snap.error}: effort, env and deny rules are left alone</Text>}
554 </Box>
555 {cards}
556 </Box>
557 )
558 })
559}
560hooks/profiles.ts 372 lines1/**
2 * ultra-config: the profiles and the pure logic that turns a profile into a
3 * plan, applies it to a settings object and undoes it.
4 * Nothing here calls `$`, so every rule is unit-tested in tests/.
5 *
6 * A profile changes two layers:
7 * - `config`: a row of the `/config` menu, written through `$.config.set`
8 * exactly as if the person changed it there (the engine picks the file).
9 * - `settings` / `list`: keys of ~/.claude/settings.json that have no
10 * `/config` row (effortLevel, env, permissions), read-modified-written.
11 */
12import type { ConfigRow, ConfigValue } from 'claude-code'
13
14/** The part of a `/config` row the plan reads. */
15export type Row = Pick<ConfigRow, 'key' | 'label' | 'kind' | 'value' | 'options' | 'isLocked'>
16
17export type ConfigStep = { layer: 'config'; key: string; value: ConfigValue; why: string }
18/** `value: null` removes the key. `path` is dotted: `env.MAX_MCP_OUTPUT_TOKENS`. */
19export type SettingsStep = { layer: 'settings'; path: string; label: string; value: string | null; why: string }
20/** Adds entries to a list (`permissions.deny`), leaving what is there. */
21export type ListStep = { layer: 'list'; path: string; label: string; add: string[]; why: string }
22export type Effort = 'low' | 'medium' | 'high' | 'xhigh'
23/**
24 * The effort for the model in use. Since Claude Code 2.1.251 effort is saved
25 * per model (`modelSettings[<model id>].effortLevel`): a top-level
26 * `effortLevel` in user settings is ignored by Opus 5.5 and later models. The
27 * model id is only known at the next request, so this step is written then.
28 */
29export type EffortStep = { layer: 'effort'; value: Effort; why: string }
30export type Step = ConfigStep | SettingsStep | ListStep | EffortStep
31
32export type ProfileId = 'saver' | 'flow' | 'quality' | 'guarded' | 'autopilot'
33
34export type Profile = {
35 id: ProfileId
36 name: string
37 icon: string
38 color: string
39 tagline: string
40 steps: Step[]
41}
42
43const cfg = (key: string, value: ConfigValue, why: string): ConfigStep => ({ layer: 'config', key, value, why })
44const set = (path: string, label: string, value: string | null, why: string): SettingsStep => ({
45 layer: 'settings',
46 path,
47 label,
48 value,
49 why,
50})
51
52const effort = (value: Effort, why: string): EffortStep => ({ layer: 'effort', value, why })
53export const SUBAGENT = { path: 'env.CLAUDE_CODE_SUBAGENT_MODEL', label: 'Subagent model' } as const
54
55export const PROFILES: Profile[] = [
56 {
57 id: 'saver',
58 name: 'Token saver',
59 icon: '◔',
60 color: '#7fd18b',
61 tagline: 'Sonnet, low effort, short answers, Haiku subagents',
62 steps: [
63 cfg('model', 'sonnet', 'cheaper per token than Opus'),
64 effort('low', 'less thinking per request'),
65 set(SUBAGENT.path, SUBAGENT.label, 'haiku', 'subagents run on the cheapest model'),
66 cfg('fast', false, 'fast mode bills at a higher rate'),
67 cfg('outputStyle', 'Concise', 'shorter answers, fewer output tokens'),
68 cfg('promptSuggestionEnabled', false, 'each suggestion is an extra model call'),
69 cfg('recap', false, 'the recap is an extra model call'),
70 cfg('autoCompact', true, 'keeps the context from growing unbounded'),
71 cfg('workflowSizeGuideline', 'small', 'smaller multi-agent fan-outs'),
72 cfg('workflowKeywordTriggerEnabled', false, 'no accidental ultracode fan-outs'),
73 set('env.MAX_MCP_OUTPUT_TOKENS', 'MCP output cap', '10000', 'caps MCP tool results (default 25000)'),
74 ],
75 },
76 {
77 id: 'flow',
78 name: 'Fast flow',
79 icon: '»',
80 color: '#f2b84b',
81 tagline: 'Edits auto-accepted, medium effort, fewer interruptions',
82 steps: [
83 cfg('model', 'sonnet', 'faster answers than Opus'),
84 effort('medium', 'balanced reasoning time'),
85 cfg('permissionMode', 'acceptEdits', 'file edits go through without a prompt'),
86 cfg('outputStyle', 'Concise', 'less to read between steps'),
87 cfg('promptSuggestionEnabled', true, 'Tab accepts the next likely prompt'),
88 cfg('turnDuration', true, 'see how long each turn took'),
89 cfg('progressBar', true, 'progress shows in the terminal tab'),
90 cfg('workflowSizeGuideline', 'medium', 'parallel agents when they help'),
91 ],
92 },
93 {
94 id: 'quality',
95 name: 'Max quality',
96 icon: '◆',
97 color: '#b48cff',
98 tagline: 'Most capable model, deepest reasoning, big workflows',
99 steps: [
100 cfg('model', 'best', 'the most capable model available to you'),
101 effort('xhigh', 'the deepest reasoning per request'),
102 set(SUBAGENT.path, SUBAGENT.label, null, 'subagents keep the model they ask for'),
103 cfg('outputStyle', 'default', 'full explanations, nothing trimmed'),
104 cfg('workflows', true, 'multi-agent workflows available'),
105 cfg('workflowKeywordTriggerEnabled', true, '"ultracode" starts a workflow'),
106 cfg('workflowSizeGuideline', 'large', 'room for large fan-outs'),
107 cfg('checkpoints', true, 'rewind any change'),
108 cfg('autoCompact', true, 'long sessions keep going'),
109 ],
110 },
111 {
112 id: 'guarded',
113 name: 'Guarded',
114 icon: '▣',
115 color: '#6cb6ff',
116 tagline: 'You approve each change; no reading secrets; no bypass',
117 steps: [
118 cfg('permissionMode', 'default', 'every edit and command asks first'),
119 cfg('useAutoModeDuringPlan', false, 'plan mode never acts on its own'),
120 set('permissions.disableBypassPermissionsMode', 'Bypass mode', 'disable', 'bypassPermissions cannot be turned on'),
121 {
122 layer: 'list',
123 path: 'permissions.deny',
124 label: 'Deny rules',
125 add: ['Read(./.env)', 'Read(./.env.*)', 'Read(./secrets/**)', 'Read(~/.ssh/**)', 'Read(~/.aws/**)'],
126 why: 'the Read tool is blocked on .env, secrets/, ~/.ssh, ~/.aws',
127 },
128 cfg('checkpoints', true, 'every change can be rewound'),
129 cfg('verbose', true, 'see each tool call in full'),
130 cfg('workflowKeywordTriggerEnabled', false, 'no fan-out from a keyword'),
131 ],
132 },
133 {
134 id: 'autopilot',
135 name: 'Autopilot',
136 icon: '➤',
137 color: '#ff8f6b',
138 tagline: 'Long unattended runs: auto mode, recap, push alerts',
139 steps: [
140 cfg('permissionMode', 'auto', 'the classifier approves safe actions'),
141 cfg('useAutoModeDuringPlan', true, 'plans run without stopping'),
142 effort('high', 'fewer wrong turns on long runs'),
143 cfg('workflows', true, 'parallel agents for big tasks'),
144 cfg('recap', true, 'a summary waits for you when you return'),
145 cfg('agentPushNotifEnabled', true, 'a push when Claude decides something'),
146 cfg('inputNeededNotifEnabled', true, 'a push when it needs you'),
147 cfg('autoCompact', true, 'the run never stops on a full context'),
148 ],
149 },
150]
151
152export const profileById = (id: string): Profile | undefined =>
153 PROFILES.find(p => p.id === id.trim().toLowerCase())
154
155// ---------------------------------------------------------------- planning
156
157export type Status = 'change' | 'same' | 'locked' | 'missing' | 'invalid'
158
159export type PlanItem = {
160 id: string
161 layer: Step['layer']
162 label: string
163 from: string
164 to: string
165 why: string
166 status: Status
167}
168
169export type Json = Record<string, unknown>
170
171/** A row's value as the menu shows it equals the value a profile sets. */
172export function sameValue(current: ConfigValue | undefined, wanted: ConfigValue): boolean {
173 if (current === wanted) return true
174 if (typeof current !== 'string' || typeof wanted !== 'string') return false
175 const c = current.toLowerCase()
176 const w = wanted.toLowerCase()
177 // the menu shows `Default (recommended)` for the value `default`
178 return c === w || c.startsWith(`${w} (`)
179}
180
181export const show = (v: unknown): string => {
182 if (v === undefined || v === null || v === '') return 'unset'
183 if (v === true) return 'on'
184 if (v === false) return 'off'
185 if (Array.isArray(v)) return v.length === 0 ? 'none' : `${v.length} rules`
186 return String(v)
187}
188
189export function getPath(obj: Json, path: string): unknown {
190 let cur: unknown = obj
191 for (const part of path.split('.')) {
192 if (cur === null || typeof cur !== 'object' || Array.isArray(cur)) return undefined
193 cur = (cur as Json)[part]
194 }
195 return cur
196}
197
198/** A copy of `obj` with `path` set, or removed when `value` is undefined; empty parents are dropped. */
199export function setPath(obj: Json, path: string, value: unknown): Json {
200 const [head, ...rest] = path.split('.')
201 const out: Json = { ...obj }
202 if (rest.length === 0) {
203 if (value === undefined) delete out[head!]
204 else out[head!] = value
205 return out
206 }
207 const child = out[head!]
208 const base = child !== null && typeof child === 'object' && !Array.isArray(child) ? (child as Json) : {}
209 const next = setPath(base, rest.join('.'), value)
210 if (Object.keys(next).length === 0) delete out[head!]
211 else out[head!] = next
212 return out
213}
214
215const listAt = (obj: Json, path: string): string[] => {
216 const v = getPath(obj, path)
217 return Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []
218}
219
220/**
221 * What the engine last sent (the main loop's model id and effort, from
222 * `turn.step`), and an activated effort still waiting for the next request.
223 */
224export type Live = { model?: string; effort?: string | number; pending?: string }
225
226/**
227 * Where a model's saved effort lives in settings.json. Undefined for an id with
228 * a dot (a Bedrock or Vertex id), which a dotted path cannot name.
229 */
230export const effortPath = (model: string): string | undefined =>
231 model.includes('.') || model.length === 0 ? undefined : `modelSettings.${model}.effortLevel`
232
233/** The effort the model in use runs at: its saved level, else what the last request carried. */
234export function currentEffort(user: Json, live: Live): string | undefined {
235 if (live.pending !== undefined) return live.pending
236 const path = live.model ? effortPath(live.model) : undefined
237 const saved = path ? getPath(user, path) : undefined
238 if (typeof saved === 'string') return saved
239 return live.effort === undefined ? undefined : String(live.effort)
240}
241
242export function planFor(profile: Profile, rows: readonly Row[], user: Json, live: Live = {}): PlanItem[] {
243 const modelStep = profile.steps.find((s): s is ConfigStep => s.layer === 'config' && s.key === 'model')
244 const modelRow = rows.find(r => r.key === 'model')
245 const switchesModel = modelStep !== undefined && modelRow !== undefined && !sameValue(modelRow.value, modelStep.value)
246 return profile.steps.map(step => {
247 if (step.layer === 'effort') {
248 const current = live.pending ?? (switchesModel ? undefined : currentEffort(user, live))
249 return {
250 id: 'effort',
251 layer: step.layer,
252 label: 'Effort',
253 from: current ?? 'default',
254 to: step.value,
255 why: `${step.why}; saved for the model in use at your next message`,
256 status: current === step.value ? ('same' as const) : ('change' as const),
257 }
258 }
259 if (step.layer === 'config') {
260 const row = rows.find(r => r.key === step.key)
261 const base = { id: `config:${step.key}`, layer: step.layer, why: step.why, to: show(step.value) }
262 if (!row) return { ...base, label: step.key, from: '—', status: 'missing' as const }
263 const item = { ...base, label: row.label, from: show(row.value) }
264 if (row.isLocked) return { ...item, status: 'locked' as const }
265 if (row.kind === 'choice' && typeof step.value === 'string' && row.options && !row.options.includes(step.value))
266 return { ...item, status: 'invalid' as const }
267 return { ...item, status: sameValue(row.value, step.value) ? ('same' as const) : ('change' as const) }
268 }
269 if (step.layer === 'settings') {
270 const current = getPath(user, step.path)
271 const same = (current ?? null) === step.value
272 return {
273 id: `settings:${step.path}`,
274 layer: step.layer,
275 label: step.label,
276 from: show(current),
277 to: show(step.value),
278 why: step.why,
279 status: same ? ('same' as const) : ('change' as const),
280 }
281 }
282 const have = listAt(user, step.path)
283 const missing = step.add.filter(x => !have.includes(x))
284 return {
285 id: `list:${step.path}`,
286 layer: step.layer,
287 label: step.label,
288 from: show(have),
289 to: missing.length === 0 ? show(have) : `+${missing.length} rules`,
290 why: step.why,
291 status: missing.length === 0 ? ('same' as const) : ('change' as const),
292 }
293 })
294}
295
296/** How much of a profile is already in force: applicable steps that match, of those that apply. */
297export function matchOf(plan: PlanItem[]): { same: number; of: number } {
298 const applicable = plan.filter(p => p.status === 'same' || p.status === 'change')
299 return { same: applicable.filter(p => p.status === 'same').length, of: applicable.length }
300}
301
302/** A row's value as `$.config.set` takes it back: a choice's option, not the menu's label for it. */
303export function restorable(row: Row): ConfigValue {
304 if (row.kind === 'choice' && row.options) return row.options.find(o => sameValue(row.value, o)) ?? row.value
305 return row.value
306}
307
308// ---------------------------------------------------------------- applying and undoing
309
310export type BackupEntry =
311 | { layer: 'config'; key: string; label: string; previous: ConfigValue }
312 | { layer: 'settings'; path: string; label: string; previous: unknown; had: boolean }
313 | { layer: 'list'; path: string; label: string; added: string[] }
314
315/** Applies the profile's settings and list steps to the settings object; returns the new object and what to undo. */
316export function applySettings(user: Json, steps: readonly (SettingsStep | ListStep)[]): { next: Json; entries: BackupEntry[] } {
317 let next = user
318 const entries: BackupEntry[] = []
319 for (const step of steps) {
320 if (step.layer === 'settings') {
321 const had = getPath(next, step.path) !== undefined
322 const previous = getPath(next, step.path)
323 if ((previous ?? null) === step.value) continue
324 next = setPath(next, step.path, step.value ?? undefined)
325 entries.push({ layer: 'settings', path: step.path, label: step.label, previous: had ? previous : null, had })
326 } else if (step.layer === 'list') {
327 const have = listAt(next, step.path)
328 const added = step.add.filter(x => !have.includes(x))
329 if (added.length === 0) continue
330 next = setPath(next, step.path, [...have, ...added])
331 entries.push({ layer: 'list', path: step.path, label: step.label, added })
332 }
333 }
334 return { next, entries }
335}
336
337/** Puts back what `applySettings` changed; a key the person changed since is restored too. */
338export function revertSettings(user: Json, entries: BackupEntry[]): Json {
339 let next = user
340 for (const entry of [...entries].reverse()) {
341 if (entry.layer === 'settings') next = setPath(next, entry.path, entry.had ? entry.previous : undefined)
342 else if (entry.layer === 'list') {
343 const kept = listAt(next, entry.path).filter(x => !entry.added.includes(x))
344 next = setPath(next, entry.path, kept.length === 0 ? undefined : kept)
345 }
346 }
347 return next
348}
349
350/** Whether the settings the engine has loaded hold `next`'s value at every path. */
351export function inSync(loaded: Json, next: Json, paths: string[]): boolean {
352 return paths.every(path => JSON.stringify(getPath(loaded, path) ?? null) === JSON.stringify(getPath(next, path) ?? null))
353}
354
355/** Where the user's settings file lives: `CLAUDE_CONFIG_DIR`, else `~/.claude`. */
356export function userSettingsPath(configDir: string | undefined, home: string | undefined): string | undefined {
357 const dir = configDir && configDir.length > 0 ? configDir : home ? `${home.replace(/[\\/]+$/, '')}/.claude` : undefined
358 return dir === undefined ? undefined : `${dir.replace(/[\\/]+$/, '')}/settings.json`
359}
360
361/** Pads with non-breaking spaces so Desktop keeps the width (plain spaces collapse there). */
362export const pad = (s: string, n: number): string => {
363 const cut = [...s]
364 return cut.length >= n ? `${cut.slice(0, Math.max(0, n - 2)).join('')}…\u00a0` : s + '\u00a0'.repeat(n - cut.length)
365}
366export const nb = (s: string): string => s.replace(/ /g, '\u00a0')
367
368export const paneColumns = (v: unknown): number => {
369 const n = typeof v === 'number' && Number.isFinite(v) ? Math.round(v) : 62
370 return Math.min(100, Math.max(44, n))
371}
372types/index.d.ts 67 lines1// The contract of the ultra-config plugin: the state its pane draws from.
2
3/** One `/config` row as `$.config.list()` returns it (ConfigRow in the API). */
4export type UltraConfigRow = {
5 key: string
6 label: string
7 description?: string
8 kind: 'boolean' | 'choice' | 'text' | 'number'
9 value: boolean | string | number | readonly string[]
10 options?: readonly string[]
11 isLocked: boolean
12}
13
14/** What the pane knows about the person's configuration, read on open and after every change. */
15export type UltraConfigSnapshot = {
16 rows: UltraConfigRow[]
17 /** ~/.claude/settings.json as parsed; `{}` when there is none. */
18 user: Record<string, unknown>
19 /** Where that file is; null when neither CLAUDE_CONFIG_DIR nor a home directory is known. */
20 path: string | null
21 /** Why the file could not be read (bad JSON): settings-layer steps are then skipped. */
22 error: string | null
23}
24
25export type UltraConfigEntry =
26 | { layer: 'config'; key: string; label: string; previous: string | number | boolean | readonly string[] }
27 | { layer: 'settings'; path: string; label: string; previous: unknown; had: boolean }
28 | { layer: 'list'; path: string; label: string; added: string[] }
29
30/** One applied profile, kept so it can be undone. */
31export type UltraConfigBackup = {
32 id: string
33 profile: 'saver' | 'flow' | 'quality' | 'guarded' | 'autopilot'
34 name: string
35 at: string
36 entries: UltraConfigEntry[]
37 refused: { label: string; reason: string }[]
38 /** The effort the profile set, saved per model at the next request. */
39 effort?: 'low' | 'medium' | 'high' | 'xhigh'
40}
41
42/** The main loop's model id and effort, as the last request carried them. */
43export type UltraConfigLive = { model?: string; effort?: string | number }
44
45/** An effort waiting for the next request to name the model it is saved under. */
46export type UltraConfigPending = { value: 'low' | 'medium' | 'high' | 'xhigh'; backupId: string }
47
48export type UltraConfigUi = {
49 /** The profile whose detail is open; null means the first. */
50 selected: string | null
51 /** A one-line result under the header (applied, undone, refused). */
52 notice: { text: string; tone: 'success' | 'warning' | 'error' } | null
53 isBusy: boolean
54}
55
56declare module 'claude-code' {
57 interface PluginState {
58 'ultra-config': {
59 ui: UltraConfigUi
60 snapshot: UltraConfigSnapshot
61 history: UltraConfigBackup[]
62 live: UltraConfigLive
63 pending: UltraConfigPending | null
64 }
65 }
66}
67