SLOPSHOPPER

session-extras

Remote Control row in the statuspane card and a caveman mode picker under it

bandtoastprocesstimer
v0.6.0MITupdated 2026-10-17marcinlukasik/mods/session-extras
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · session-extras
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ⟨Claude Code's own drawing⟩ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 🪨 caveman: off ▾ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
⟨Claude Code's own drawing⟩ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 🪨 caveman: off ▾ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
README

session-extras

Two small additions to the statuspane card above the Claude Code prompt:

  • 📡 Remote Control row inside the card while the session is reachable through Remote Control: on, or N attached while a phone or claude.ai has the session open. Gone outside Remote Control.
  • 🪨 caveman button in its own box under the card, framed like it. Pressed, it opens a list of caveman modes; the pick applies now and is saved as caveman's defaultMode, so new sessions start in it.

Needs

  • statuspane, which draws the card. Add its marketplace first, so the dependency installs:
  /plugin marketplace add xuanji86/claude-statuspane
  • caveman, for the mode picker (optional; without it the button reads off and picking a mode says caveman was not found):
  /plugin marketplace add JuliusBrussee/caveman
  /plugin install caveman@caveman
  • A terminal session. The desktop app draws neither the card nor the button.

Install

/plugin install session-extras --marketplace marcinlukasik/mods

Notes

  • Picking a mode writes ~/.config/caveman/config.json (or $XDG_CONFIG_HOME/caveman/config.json), keeping any other keys in it. /caveman <mode> typed at the prompt changes the current session only.
  • The Remote Control row is checked every 15 seconds, on each turn, and when a client attaches or detaches.

Develop

claude plugin validate ./session-extras
claude plugin test ./session-extras
Source 2 files
hooks/register.tsx 141 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, RenderSurface } from 'claude-code'
3
4export const ROW = 'remote-control'
5// Long ttl: the row is refreshed on every turn and attach, and cleared outside RC
6const TTL = 24 * 60 * 60
7
8const cavemanMode = atom({ plugin: 'session-extras', key: 'cavemanMode' } as const, null)
9const isCavemanMenuOpen = atom({ plugin: 'session-extras', key: 'isCavemanMenuOpen' } as const, false)
10
11// What the menu offers, in order; off removes the flag
12export const MENU_MODES = ['lite', 'full', 'ultra', 'wenyan-lite', 'wenyan', 'wenyan-ultra', 'off'] as const
13const KNOWN_MODES = new Set<string>([...MENU_MODES, 'wenyan-full', 'commit', 'review', 'compress'])
14
15// The statuspane row for this session, or null outside Remote Control
16export function rcRow(bridge: string | undefined, surfaces: readonly RenderSurface[]) {
17  if (!bridge) return null
18  const remotes = surfaces.filter(s => s !== 'terminal').length
19  return {
20    id: ROW,
21    label: '📡 Remote Control',
22    text: remotes > 0 ? `${remotes} attached` : 'on',
23    ttl: TTL,
24    state: 'ok' as const,
25  }
26}
27
28// Caveman's flag file holds the mode; same sanitizing and whitelist as its own statusline script
29export function parseCavemanMode(flag: string | undefined) {
30  const mode = (flag ?? '').slice(0, 64).toLowerCase().replace(/[^a-z0-9-]/g, '')
31  return KNOWN_MODES.has(mode) && mode !== 'off' ? mode : null
32}
33
34async function cavemanFlagPath($: EngineInterface) {
35  const dir = (await $.env.get('CLAUDE_CONFIG_DIR')) ?? `${await $.env.get('HOME')}/.claude`
36  return `${dir}/.caveman-active`
37}
38
39async function readCavemanMode($: EngineInterface) {
40  const path = await cavemanFlagPath($)
41  // Refuse symlinks, as caveman does: stat without resolving reports a link as 'other'
42  const stat = await $.fs.stat(path).catch(() => undefined)
43  if (stat?.kind !== 'file') return null
44  return parseCavemanMode(await $.fs.read(path).catch(() => undefined))
45}
46
47// Bridge session id is set while the session is reachable from claude.ai / mobile
48async function refresh($: EngineInterface) {
49  const rc = rcRow(await $.env.get('CLAUDE_CODE_BRIDGE_SESSION_ID'), await $.session.surfaces())
50  await (rc ? $.statuspane.progress(rc) : $.statuspane.clear(ROW))
51  const mode = await readCavemanMode($)
52  await update($, cavemanMode, () => mode)
53}
54
55async function pickCavemanMode($: EngineInterface, mode: string) {
56  const { exitCode, stderr } = await $.process.run(['node', `${$.plugin.root}/bin/set-caveman-mode.js`, mode])
57  if (exitCode !== 0) $.ui.toast(`caveman: ${stderr.trim() || 'mode not changed'}`)
58  const now = await readCavemanMode($)
59  await update($, cavemanMode, () => now)
60  await update($, isCavemanMenuOpen, () => false)
61}
62
63export const register: Register = on => {
64  on('session.start', async ($, e, next) => {
65    await refresh($)
66    // Remote Control connects a moment after start, so poll rather than wait for the first turn
67    $.clock.every(15_000, () => void refresh($))
68    return next(e)
69  })
70  on('turn.start', async ($, e, next) => {
71    await refresh($)
72    return next(e)
73  })
74  on('session.attach', async ($, e, next) => {
75    const result = await next(e)
76    await refresh($)
77    return result
78  })
79  on('session.detach', async ($, e, next) => {
80    const result = await next(e)
81    await refresh($)
82    return result
83  })
84
85  // Caveman button under the statuspane card; pressed, it opens a mode list framed like statuspane's settings
86  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
87    if (e.props.hasSurvey || e.surface === 'desktop') return next(e)
88    const { Box, Button, Text } = $.ui.resolve(e)
89    // Keep what the plugins beneath drew; never hand the band itself back, or it stops redrawing
90    const below = await next(e).catch(() => null)
91    const mode = await read($, cavemanMode)
92    const setOpen = (open: boolean) => update($, isCavemanMenuOpen, () => open)
93
94    const ours = (await read($, isCavemanMenuOpen)) ? (
95      <Box justifyContent="flex-end" paddingRight={1}>
96        <Box flexDirection="column" borderStyle="round" borderColor="claude" paddingX={1}>
97          <Text color="claude" bold>Caveman mode</Text>
98          {MENU_MODES.map(m => (
99            // hover needs a keyed Box around the Button, or the whole band is refused
100            <Box key={`caveman-row-${m}`}>
101              <Button
102                key={`caveman-${m}`}
103                label={`${(mode ?? 'off') === m ? '●' : '○'} ${m}`}
104                plain
105                hover={{ color: 'claude' }}
106                onPress={() => pickCavemanMode($, m)}
107              />
108            </Box>
109          ))}
110          <Box justifyContent="flex-end">
111            <Button key="caveman-close" label="Done" variant="primary" onPress={() => setOpen(false)} />
112          </Box>
113        </Box>
114      </Box>
115    ) : (
116      // Own box, framed like the statuspane card above it; keyed so the Button's hover is allowed
117      <Box justifyContent="flex-end" paddingRight={1}>
118        <Box key="caveman-open-row" borderStyle="round" borderColor="claude" paddingX={1}>
119          <Button
120            key="caveman-open"
121            label={`🪨 caveman: ${mode ?? 'off'} ▾`}
122            plain
123            dimColor
124            hover={{ color: 'claude' }}
125            onPress={() => setOpen(true)}
126          />
127        </Box>
128      </Box>
129    )
130
131    return below ? (
132      <Box flexDirection="column">
133        {below}
134        {ours}
135      </Box>
136    ) : (
137      ours
138    )
139  })
140}
141
types/index.d.ts 13 lines
1/** Caveman's active mode as its flag file says, null when off. */
2export type SessionExtrasCavemanMode = string | null
3
4declare module 'claude-code' {
5  interface PluginState {
6    'session-extras': {
7      cavemanMode: SessionExtrasCavemanMode
8      /** Whether the caveman mode list is open. */
9      isCavemanMenuOpen: boolean
10    }
11  }
12}
13