Claude가 일하는 동안 다음에 시킬 일을 메모해 뒀다가 버튼 한 번으로 입력창에 넣어요

Claude가 일하는 동안 다음에 시킬 일을 적어 뒀다가, 버튼 한 번으로 입력창에 넣어요.
/plugin marketplace add SeongGwangJu/k-mods
/plugin install memo-pad@k-mods
/m으로 메모장을 열고 닫아요. Claude가 한창 작업 중이어도 바로 열려요.[넣기]를 누르면 그 메모가 프롬프트 입력창에 채워져요(전송은 직접 Enter를 눌러야 해요).[x]를 누르면 그 메모만 지워져요.✎ 메모 N (/m)이 붙어서 잊지 않게 해줘요.dark는 어두운 터미널용 노란 파스텔 칩이에요. 밝은 터미널이면 theme, 회색 배경 터미널이면 gray(원본처럼 색 없음)로 바꾸세요./m 명령을 등록하고, 지금 작업 폴더에 저장된 메모를 불러와요./m을 실행할 때: 메모 패널을 열거나(이미 열려 있으면) 닫아요.$.store)에 저장해요.이 플러그인 전용 저장소($.store)에 메모 텍스트를 저장해요. 그 외 네트워크·일반 파일·모델 호출은 전혀 없어요.
[넣기]는 입력창에 글자를 채울 뿐, 자동으로 보내지는 않아요. Enter는 직접 눌러야 해요.메인테이너(SeongGwangJu)가 자기 기기에서 쓰던 오리지널 모드를 공개용으로 옮긴 것으로, 외부 upstream은 없습니다. 저장소 루트의 MIT 라이선스를 따릅니다.
테스트한 Claude Code 버전: 2.1.291
hooks/register.ts 114 lines1// memo-pad: Claude가 일하는 동안 다음 지시를 적어두는 작은 메모장.
2// /m 으로 열고 닫는다(작업 중에도 바로 열림). 6줄짜리 인라인 패널, Esc로 닫힘.
3// 메모마다 [넣기]는 프롬프트에 채우고(전송은 직접 Enter), [x]는 지운다.
4// 메모가 있으면 프롬프트 아래 힌트 줄 끝에 "메모 N"을 붙여서 잊지 않게 한다.
5// 폴더(작업 디렉터리)별로 따로 저장되고 세션이 끝나도 남는다.
6import type { On, PluginOptions } from 'claude-code'
7
8import { hintTail, storeKeyFor } from './notes'
9import { paletteOf } from './palette'
10
11const PANE = 'memo-pad'
12const ROWS = 6
13
14export function register(on: On, options: PluginOptions): void {
15 const palette = paletteOf(options.palette)
16
17 let notes: string[] = []
18 let storeKey: string | null = null
19 let isOpen = false
20
21 on('session.start', async ($, e, next) => {
22 await $.command.register({ name: 'm', description: '메모장 열기/닫기 (작업 중에도 열림)', immediate: true })
23 storeKey = storeKeyFor(await $.session.cwd())
24 const saved = await $.store.get(storeKey)
25 notes = Array.isArray(saved) ? (saved as string[]) : []
26 $.ui.invalidate('ui.render')
27 return next(e)
28 })
29
30 on('command.run', { command: 'm' }, async ($) => {
31 if (isOpen) {
32 await $.ui.close({ id: PANE })
33 isOpen = false
34 return {}
35 }
36 await $.ui.open({ id: PANE, title: '메모', focus: true, closeOnEscape: true, rows: ROWS })
37 isOpen = true
38 return {}
39 })
40
41 on('ui.close', async ($, e, next) => {
42 if (e.id === PANE) {
43 isOpen = false
44 $.ui.invalidate('ui.render')
45 }
46 return next(e)
47 })
48
49 // 힌트 줄 끝에 메모 개수. 엔진의 줄은 그대로 두고 tail만 붙인다.
50 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
51 if (notes.length === 0 || isOpen) return next(e)
52 return next({ ...e, props: { ...e.props, tail: hintTail(notes.length) } })
53 })
54
55 on('ui.render', { component: 'Pane' }, async ($, e, next) => {
56 if (e.requestId !== PANE) return next(e)
57 // Input은 terminal·desktop에만 있다(Pane 자체는 모든 surface에서 열릴 수 있다). 그 밖의
58 // surface(vscode·mobile)에서는 엔진 기본 그림에 맡긴다.
59 if (e.surface !== 'terminal' && e.surface !== 'desktop') return next(e)
60 const { Box, Text, Button, Input } = $.ui.resolve(e)
61 const save = async (list: string[]) => {
62 notes = list
63 $.ui.invalidate('ui.render')
64 if (storeKey) await $.store.set(storeKey, notes)
65 }
66 // Text에는 key가 없다(목록 재조정용이 아니라 Box·Button의 key는 hover 범위·주소 용도).
67 const badge =
68 notes.length === 0
69 ? null
70 : palette.badgeBg
71 ? Text({ backgroundColor: palette.badgeBg, color: palette.badgeFg, children: ` 메모 ${notes.length} ` })
72 : Text({ children: `메모 ${notes.length}개` })
73 return Box({
74 flexDirection: 'column',
75 children: [
76 ...(badge ? [badge] : []),
77 Input({
78 key: 'new',
79 label: '메모',
80 placeholder: '다음에 시킬 일을 적고 Enter',
81 value: '',
82 submitLabel: '추가',
83 autoFocus: true,
84 onSubmit: async (value: string) => {
85 const text = value.trim()
86 if (text) await save([...notes, text])
87 },
88 }),
89 ...notes.map((note, i) =>
90 Box({
91 key: `row-${i}`,
92 flexDirection: 'row',
93 columnGap: 1,
94 children: [
95 Button({
96 key: `use-${i}`,
97 label: '넣기',
98 onPress: async () => {
99 await $.prompt.fill({ text: note, mode: 'replace' })
100 await save(notes.filter((_, j) => j !== i))
101 await $.ui.close({ id: PANE })
102 isOpen = false
103 },
104 }),
105 Button({ key: `del-${i}`, label: 'x', onPress: () => save(notes.filter((_, j) => j !== i)) }),
106 Text({ wrap: 'truncate-end', children: note }),
107 ],
108 }),
109 ),
110 ],
111 })
112 })
113}
114hooks/notes.ts 12 lines1// memo-pad의 순수 함수. $를 받지 않아 테스트하기 쉽다.
2
3/** 작업 디렉터리별 저장 키. 폴더마다 메모가 따로 쌓인다. */
4export function storeKeyFor(cwd: string): string {
5 return `notes:${cwd}`
6}
7
8/** 프롬프트 힌트 줄 끝에 붙는 꼬리 문구. */
9export function hintTail(count: number): string {
10 return `✎ 메모 ${count} (/m)`
11}
12hooks/palette.ts 19 lines1// memo-pad의 색 팔레트.
2//
3// 원본(메인테이너 전용 모드)은 색을 전혀 쓰지 않는 무채색 패널이었다. 공개판은 다른 두 k-mods
4// 모드와 같은 모양으로 palette 옵션을 두되, 솔직하게: 패널 위쪽에 "메모 N" 배지 칩 하나만
5// 테마 색으로 강조한다(CONTRIBUTING.md 화면 원칙 #2의 칩 패턴). "gray"는 원본처럼 색이 아예 없다.
6export type Palette = {
7 badgeBg: string | undefined
8 badgeFg: string | undefined
9}
10
11// 어두운 터미널 기준 파스텔 칩 (기본)
12const DARK: Palette = { badgeBg: '#E3C56A', badgeFg: '#1E2127' }
13const THEME: Palette = { badgeBg: 'remember', badgeFg: 'inverseText' }
14const GRAY: Palette = { badgeBg: undefined, badgeFg: undefined }
15
16export function paletteOf(name: unknown): Palette {
17 return name === 'gray' ? GRAY : name === 'theme' ? THEME : DARK
18}
19