Agents, skiils and plagins for Claude

Личный маркетплейс плагинов Максима для Claude. Сейчас в нём один плагин: max-kit.
plugins/max-kit/: агенты, скиллы и правило пула (описание в plugins/max-kit/README.md)GafarovMaxim/Claude, включить Sync automaticallyplugins/max-kit/agents/, поднять version в plugins/max-kit/.claude-plugin/plugin.json, закоммитить в mainhooks/register.tsx 106 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Gauge } from '../types'
5
6// Шкалы под полем ввода: контекст (подпись — имя мода «ctx»), 5H — лимит пяти часов, 7D — недели,
7// ◷ — минут до остывания кэша. Десктоп под вводом рисует только строку статуса — туда шкалы текстом;
8// терминал рисует строку подсказки — туда шкалы цветом. Передача в чистый контекст — отдельный мод handoff.
9const gauge = atom({ plugin: 'ctx', key: 'gauge' } as const, { ctx: null, h5: null, d7: null, cacheMin: null })
10const lastTurnAt = atom({ plugin: 'ctx', key: 'lastTurnAt' } as const, 0)
11const isTerminal = atom({ plugin: 'ctx', key: 'isTerminal' } as const, false)
12
13const CACHE_TTL_MIN = 60 // кэш промптов на подписке живёт час с последнего запроса (у API бывает 5 мин)
14const CELLS = 8
15
16let timer: (() => void) | undefined
17
18function cells(p: number, n = CELLS): string {
19 const full = Math.max(0, Math.min(n, Math.round((p / 100) * n)))
20 return '▪'.repeat(full) + '▫'.repeat(n - full) // ▪ и ▫ — малые квадраты одной пары, одного размера
21}
22
23function line(g: Gauge): string {
24 const parts: string[] = []
25 if (g.ctx !== null) parts.push(`${cells(g.ctx)} ${Math.round(g.ctx)}%`)
26 if (g.h5 !== null) parts.push(`5H ${cells(g.h5)} ${Math.round(g.h5)}%`)
27 if (g.d7 !== null) parts.push(`7D ${cells(g.d7)} ${Math.round(g.d7)}%`)
28 if (g.cacheMin !== null) parts.push(`◷ ${g.cacheMin}м`)
29 return parts.join('\u2003\u2003') // em-пробелы: обычные десктоп схлопывает в один
30}
31
32async function measure($: any) {
33 try {
34 const u = await $.session.usage()
35 const limits: any[] = u.rateLimits ?? []
36 const h5 = limits.find(l => l.kind === 'five_hour')?.percentUsed ?? null
37 const d7 = limits.find(l => l.kind === 'seven_day')?.percentUsed ?? null
38 const last = await read($, lastTurnAt)
39 const cacheMin = last > 0 ? Math.max(0, Math.round(CACHE_TTL_MIN - ((await $.clock.now()) - last) / 60000)) : null
40 const g: Gauge = { ctx: u.context?.percent ?? null, h5, d7, cacheMin }
41 await update($, gauge, () => g)
42 $.ui.status((await read($, isTerminal)) ? undefined : line(g) || undefined)
43 } catch (err: any) {
44 $.ui.status(`ошибка замера: ${String(err?.message ?? err).slice(0, 80)}`)
45 }
46}
47
48function color(p: number, warn: number, danger: number): string {
49 return p >= danger ? 'red' : p >= warn ? 'yellow' : 'green'
50}
51
52export const register: Register = on => {
53 on('session.start', async ($, e, next) => {
54 if (!timer) timer = $.clock.every(30000, () => void measure($))
55 void measure($)
56 return next(e)
57 })
58
59 on('turn.complete', async ($, e, next) => {
60 await update($, lastTurnAt, async () => await $.clock.now())
61 void measure($)
62 return next(e)
63 })
64
65 on('session.measure', async ($, e, next) => {
66 void measure($)
67 return next(e)
68 })
69
70 // Терминал: цветные шкалы в строке подсказки под вводом; пока человек печатает — подсказка движка
71 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
72 if (e.surface !== 'terminal') return next(e)
73 if (!(await read($, isTerminal))) {
74 await update($, isTerminal, () => true)
75 $.ui.status(undefined)
76 }
77 if (e.props.isDraft) return next(e)
78 const g = await read($, gauge)
79 if (g.ctx === null && g.h5 === null && g.d7 === null) return next(e)
80 const { Box, Text } = $.ui.resolve(e)
81 // Узкий терминал: шкалы короче, на совсем узком — только числа; строка переносится, а не режется
82 const cols = e.viewport?.columns ?? 120
83 const n = cols >= 90 ? CELLS : cols >= 60 ? 4 : 0
84 const meter = (label: string, p: number | null, warn: number, danger: number) =>
85 p === null ? null : (
86 <Box marginRight={2}>
87 <Text dimColor>{label} </Text>
88 {n > 0 && <Text color={color(p, warn, danger)}>{cells(p, n)}</Text>}
89 <Text dimColor={n > 0} color={n > 0 ? undefined : color(p, warn, danger)}> {Math.round(p)}%</Text>
90 </Box>
91 )
92 return (
93 <Box flexWrap="wrap">
94 {meter('CTX', g.ctx, 45, 60)}
95 {meter('5H', g.h5, 70, 90)}
96 {meter('7D', g.d7, 70, 90)}
97 {g.cacheMin !== null && (
98 <Text color={g.cacheMin <= 5 ? 'red' : g.cacheMin <= 15 ? 'yellow' : undefined} dimColor={g.cacheMin > 15}>
99 ◷ {g.cacheMin}м
100 </Text>
101 )}
102 </Box>
103 )
104 })
105}
106types/index.d.ts 8 lines1export type Gauge = { ctx: number | null; h5: number | null; d7: number | null; cacheMin: number | null }
2
3declare module 'claude-code' {
4 interface PluginState {
5 ctx: { gauge: Gauge; lastTurnAt: number; isTerminal: boolean }
6 }
7}
8