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

Skills for coding agents.
curl -fsSL shalom2552.github.io/agent-skills/install | bash
Or clone and run ./install. See ./install --help for options.
| Skill | Description |
|---|---|
docs-sync | Find and fix drift between docs and code. |
dotconf | Answer, change, and remember dotfile and app configs. |
read-only | Lock the session to read-only; change nothing. |
setup-sh25-skills | Install, manage and wire up shalom2552's agent-skills assets. |
system-audit | Read-only health, security and cleanliness audit of this machine. |
system-fix | Diagnose, fix, and document a system or config fault on this machine. |
to-prompt | Write a handoff prompt for a fresh agent. |
walk-with-me | Learn languages and idioms by coding with a coach. |
scripts/statusline.sh - status line for Claude Code.
Run setup-sh25-skills to wire statusLine.
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
| Mod | Description |
|---|---|
spinner-words | Swap the spinner verbs for dev ones. |
hooks/register.tsx 92 lines1import { 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}
92types/index.d.ts 8 lines1export type Mode = 'off' | 'read-only' | 'apply'
2
3declare module 'claude-code' {
4 interface PluginState {
5 'read-only': { mode: Mode }
6 }
7}
8