SLOPSHOPPER

read-only

Band above the prompt showing READ-ONLY or APPLY; blocks file edits while read-only.

newbandguardprompttool
v0.1.0MITupdated 2026-10-08shalom2552/agent-skills/skills/read-only
A shopper browsing a rack in a slop shop
README

Agent Skills

Skills for coding agents.

Install

curl -fsSL shalom2552.github.io/agent-skills/install | bash

Or clone and run ./install. See ./install --help for options.

Skills

SkillDescription
docs-syncFind and fix drift between docs and code.
dotconfAnswer, change, and remember dotfile and app configs.
read-onlyLock the session to read-only; change nothing.
setup-sh25-skillsInstall, manage and wire up shalom2552's agent-skills assets.
system-auditRead-only health, security and cleanliness audit of this machine.
system-fixDiagnose, fix, and document a system or config fault on this machine.
to-promptWrite a handoff prompt for a fresh agent.
walk-with-meLearn languages and idioms by coding with a coach.

Scripts

scripts/statusline.sh - status line for Claude Code.

Run setup-sh25-skills to wire statusLine.

Mods

Claude Code plugins in mods/. ./install mods adds this repo as the shalom2552 marketplace and installs them; without the installer:

/plugin install <mod> --marketplace shalom2552/agent-skills
ModDescription
spinner-wordsSwap the spinner verbs for dev ones.
Source 2 files
hooks/register.tsx 92 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Mode } from '../types'
5
6export const TOOL = 'mcp__read-only__mode'
7
8const mode = atom({ plugin: 'read-only', key: 'mode' } as const, 'off' as Mode)
9
10const set = ($: EngineInterface, next: Mode) => update($, mode, () => next)
11
12export const register: Register = on => {
13  on('session.start', async ($, e, next) => {
14    await $.tool.register({
15      name: 'mode',
16      description:
17        'Set the /read-only mode band. "apply" right before making a change the user explicitly approved; "off" when the user leaves read-only.',
18      inputSchema: {
19        type: 'object',
20        properties: { mode: { type: 'string', enum: ['apply', 'off'] } },
21        required: ['mode'],
22      },
23    })
24
25    return next(e)
26  })
27
28  on('skill.prompt', { skill: 'read-only' }, async ($, e, next) => {
29    await set($, 'read-only')
30
31    return next(e)
32  })
33
34  on('tool.call', { tool: TOOL }, async ($, e) => {
35    const asked = (e as { mode?: unknown }).mode
36    if (asked !== 'apply' && asked !== 'off') return { result: 'mode must be "apply" or "off".' }
37    if (asked === 'apply' && (await read($, mode)) === 'off') return { result: 'Read-only is not on.' }
38    await set($, asked)
39
40    return { result: asked === 'apply' ? 'APPLY until this turn ends.' : 'Read-only off.' }
41  })
42
43  on('tool.call', { tool: ['Edit', 'Write', 'NotebookEdit'] }, async ($, e, next) =>
44    (await read($, mode)) === 'read-only'
45      ? { deny: `Read-only: edits are blocked. Get the user's explicit approval for this change, then call ${TOOL} with "apply".` }
46      : next(e),
47  ).catch(($, e, next) => (next.called ? next(e) : { deny: 'Read-only: its edit guard failed.' }))
48
49  on('turn.complete', async ($, e, next) => {
50    if (e.agentId === undefined && (await read($, mode)) === 'apply') await set($, 'read-only')
51
52    return next(e)
53  })
54
55  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
56    const current = await read($, mode)
57    if (current === 'off' || e.props.hasSurvey) return next(e)
58
59    const { Box, Button, Text } = $.ui.resolve(e)
60    const isApply = current === 'apply'
61
62    return (
63      <Box gap={1}>
64        <Text bold inverse color={isApply ? 'warning' : 'planMode'}>
65          {isApply ? ' APPLY ' : ' READ-ONLY '}
66        </Text>
67        {!e.props.isWorking && !isApply && (
68          <Button
69            variant="primary"
70            key="apply"
71            label="Apply"
72            onPress={async () => {
73              await set($, 'apply')
74              void $.prompt.submit({ text: 'Apply that.', asUser: true })
75            }}
76          />
77        )}
78        {!e.props.isWorking && (
79          <Button
80            key="exit"
81            label="Exit"
82            onPress={async () => {
83              await set($, 'off')
84              void $.prompt.submit({ text: 'Leave read-only.', asUser: true })
85            }}
86          />
87        )}
88      </Box>
89    )
90  })
91}
92
types/index.d.ts 8 lines
1export type Mode = 'off' | 'read-only' | 'apply'
2
3declare module 'claude-code' {
4  interface PluginState {
5    'read-only': { mode: Mode }
6  }
7}
8