/spasi-sohrani and /spasi-vibe show or hide a set of seven icons in a band above the prompt

Мод для Claude Code: две команды показывают над строкой ввода набор из семи иконок по центру.
| Команда | Режим | Что показывает |
|---|---|---|
/spasi-sohrani | святые | семь святых, связанных со словом, знанием и связью |
/spasi-vibe | vibe | семь персонажей мира технологий: Линус Торвальдс, Стив Джобс, Билл Гейтс, Илон Маск, Сэм Альтман, Дарио Амодеи, Андрей Карпаты |
Одновременно показан один набор:
Мод запоминает показанный набор до конца сессии Claude Code.
/spasi-sohrani
| Иконка | Связь с интернетом и digital |
|---|---|
| Исидор Севильский | Интернет, базы знаний, поиск и систематизация информации. Его предлагали считать покровителем интернета, но официально этого не провозглашали. |
| Иоанн Богослов | Авторы, редакторы, создатели содержательного контента. Для нашей серии — символ ответственного обращения со словом; специальное покровительство digital-профессиям здесь не подразумевается. |
| Феофан Затворник | Сосредоточенная работа, цифровая гигиена, дистанционное наставничество. Символ внимания и внутренней дисциплины среди информационного шума. |
| Архангел Гавриил | Связь, телекоммуникации, передача сообщений. Официально провозглашён покровителем телекоммуникаций; связь с сетями и мессенджерами — современное продолжение этой темы. |
| Кирилл и Мефодий | Программирование, языки, кодировки, локализация и перевод. Это символическая аналогия: создание языка и передача смысла между разными системами. |
| Иоанн Дамаскин | Архитектура знаний, техническая документация, визуальная коммуникация. Символ стройного изложения сложных идей и осмысленной работы с изображениями. |
| Франциск Сальский | Журналистика, копирайтинг, блоги, SMM и редактура. Считается покровителем писателей и журналистов; распространение на digital-контент — современная интерпретация. |
/spasi-vibeШуточные иконки: реальные люди из мира технологий и то, чему они «покровительствуют».

| Персонаж | Чему «покровительствует» |
|---|---|
| Линус Торвальдс | Открытому коду, системному программированию, сисадминам и порядку в репозиториях. |
| Стив Джобс | Продуктовому мышлению, интерфейсам, дизайну и вниманию к деталям. |
| Билл Гейтс | Разработчикам прикладного софта, офисной автоматизации и совместимости. |
| Илон Маск | Инженерным амбициям, автоматизации, смелым запускам и невозможным дедлайнам. |
| Сэм Альтман | ИИ-продуктам, стартапам, привлечению инвестиций и превращению исследований в массовые сервисы. |
| Дарио Амодеи | Надёжности ИИ, безопасности моделей, вдумчивым экспериментам и осторожной работе с мощными системами. |
| Андрей Карпаты | Обучению машинному обучению, понятному коду, нейросетям и объяснению сложного простыми словами. |
Файлы каждого набора лежат в своей папке: assets/sohrani/ (01-isidore-of-seville … 07-francis-de-sales) и assets/vibe/ (01-linus-torvalds … 07-andrej-karpathy).
png/ — исходники по одному файлу на иконку: 321×321 у sohrani, 342×342 у vibe.jpg/ — копии PNG, уменьшенные до 180×180, для приложения Claude. Векторные исходники (около 1,1 млн символов) больше лимита, который движок принимает для одного SVG (131 072 символа), а копии 320×320 приложение не показывало (около 50 тысяч символов каждая). Поэтому при размере больше 90 px на экране Retina иконки будут чуть размыты.png/.В терминальной сессии Claude Code:
/plugin install spasi-sohrani --marketplace ponomr/spasi-sohrani
Ответьте y на вопрос о добавлении маркетплейса и выберите область установки.
Из командной строки:
claude plugin install spasi-sohrani --marketplace ponomr/spasi-sohrani
В приложении Claude (вкладка Code), где /plugin не работает: нажмите + рядом с полем ввода → Plugins → Add marketplace → ponomr/spasi-sohrani → Add plugin.
Icon size (px) — высота иконки в приложении, по умолчанию 90, от 32 до 400. В терминале иконки занимают 5 строк.
claude --plugin-dir /path/to/spasi-sohrani
claude plugin validate .
claude plugin test .hooks/register.tsx 123 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3import type { SetName } from '../types'
4
5// Each command toggles its own set; showing one set replaces the other.
6const SETS = {
7 sohrani: {
8 command: 'spasi-sohrani',
9 description: 'Показать или скрыть семь иконок над строкой ввода',
10 shownText: 'Иконки показаны над строкой ввода.',
11 icons: [
12 { name: '01-isidore-of-seville', alt: 'Святитель Исидор Севильский' },
13 { name: '02-john-the-theologian', alt: 'Апостол Иоанн Богослов' },
14 { name: '03-theophan-the-recluse', alt: 'Святитель Феофан Затворник' },
15 { name: '04-archangel-gabriel', alt: 'Архангел Гавриил' },
16 { name: '05-cyril-and-methodius', alt: 'Святые Кирилл и Мефодий' },
17 { name: '06-john-of-damascus', alt: 'Преподобный Иоанн Дамаскин' },
18 { name: '07-francis-de-sales', alt: 'Святой Франциск Сальский' },
19 ],
20 },
21 vibe: {
22 command: 'spasi-vibe',
23 description: 'Показать или скрыть семь vibe-иконок над строкой ввода',
24 shownText: 'Vibe-иконки показаны над строкой ввода.',
25 icons: [
26 { name: '01-linus-torvalds', alt: 'Линус Торвальдс' },
27 { name: '02-steve-jobs', alt: 'Стив Джобс' },
28 { name: '03-bill-gates', alt: 'Билл Гейтс' },
29 { name: '04-elon-musk', alt: 'Илон Маск' },
30 { name: '05-sam-altman', alt: 'Сэм Альтман' },
31 { name: '06-dario-amodei', alt: 'Дарио Амодеи' },
32 { name: '07-andrej-karpathy', alt: 'Андрей Карпаты' },
33 ],
34 },
35} as const satisfies Record<SetName, unknown>
36
37// The JPEGs in assets/<set>/jpg/ are square, SIDE pixels each (made from assets/<set>/png/).
38// The desktop drew nothing for 320px ones (about 50K characters each); 180px ones at quality 75 (about 17K) draw.
39const SIDE = 180
40// The terminal prompt box is 3 rows tall; 1.5x of it, with cells about twice as tall as wide.
41const TERMINAL_ROWS = 5
42const TERMINAL_COLUMNS = 10
43
44const shownSet = atom({ plugin: 'spasi-sohrani', key: 'shownSet' } as const, null as SetName | null)
45
46const svgCache = new Map<string, string>()
47
48export const register: Register = (on, options) => {
49 // The engine fills in the manifest's default (90px, twice the desktop prompt box).
50 const size = options.iconSize as number
51
52 on('session.start', async ($, e, next) => {
53 for (const set of Object.values(SETS)) {
54 await $.command.register({ name: set.command, description: set.description })
55 }
56
57 return next(e)
58 })
59
60 for (const [setName, set] of Object.entries(SETS) as [SetName, (typeof SETS)[SetName]][]) {
61 on('command.run', { command: set.command }, async $ => {
62 const shown = await update($, shownSet, value => (value === setName ? null : setName))
63
64 return { text: shown ? set.shownText : 'Иконки скрыты.' }
65 })
66 }
67
68 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
69 const setName = await read($, shownSet)
70 if (!setName) {
71 return next(e)
72 }
73 const icons = SETS[setName].icons
74
75 // The session survey also lives in this band: draw it under the icons.
76 const survey = e.props.hasSurvey ? await next(e) : null
77
78 const { Box } = $.ui.resolve(e)
79 let leaves
80 if (e.surface === 'terminal') {
81 const { Image } = $.ui.resolve(e)
82 const rows = Math.min(TERMINAL_ROWS, Math.max(1, e.props.maxRows))
83 leaves = icons.map(icon => (
84 <Image
85 key={icon.name}
86 source={{ file: `${$.plugin.root}/assets/${setName}/png/${icon.name}.png`, format: 'png' }}
87 columns={TERMINAL_COLUMNS}
88 rows={rows}
89 alt={icon.alt}
90 />
91 ))
92 } else {
93 const { Svg } = $.ui.resolve(e)
94 const sources = await Promise.all(
95 icons.map(async icon => {
96 const path = `${$.plugin.root}/assets/${setName}/jpg/${icon.name}.jpg`
97 const cached = svgCache.get(path)
98 if (cached) return cached
99 const { base64 } = await $.fs.read(path, { as: 'bytes' })
100 const svg =
101 `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${SIDE} ${SIDE}">` +
102 `<image href="data:image/jpeg;base64,${base64}" width="${SIDE}" height="${SIDE}"/></svg>`
103 svgCache.set(path, svg)
104
105 return svg
106 }),
107 )
108 leaves = icons.map((icon, i) => (
109 <Svg key={icon.name} source={sources[i] ?? ''} alt={icon.alt} width={size} height={size} />
110 ))
111 }
112
113 return (
114 <Box flexDirection="column" width="100%">
115 <Box flexDirection="row" flexWrap="wrap" justifyContent="center" alignItems="center" gap={1} width="100%">
116 {leaves}
117 </Box>
118 {survey}
119 </Box>
120 )
121 })
122}
123types/index.d.ts 8 lines1export type SetName = 'sohrani' | 'vibe'
2
3declare module 'claude-code' {
4 interface PluginState {
5 'spasi-sohrani': { shownSet: SetName | null }
6 }
7}
8