把最後一則回覆裡要你親手跑的指令(程式碼區塊裡 sudo 開頭的行、! <cmd>、前一句叫你自己跑的 shell 區塊)列成對話旁的清單,每段帶上回覆裡的前一句說明;按數字鍵勾完成、按 y 再按編號複製指令,全部完成後按 r 回報,送出「N/N 完成了」。只整理,不代跑

繁體中文版:README.zh-TW.md. The mod's on-screen text is in Traditional Chinese, and it recognizes "run this yourself" hints written in Chinese.
A Claude Code mod that turns the commands Claude's last reply asks you to run yourself into a checklist in a pane beside the conversation. Steps like sudo are always yours to run, yet the commands are often buried in a long reply. Tick each one off with its number key once it has run; when all are ticked, press r to report, which sends "N/N 完成了" ("N/N done"). The mod only organizes the list; it never runs a command for you.
Tested on: Claude Code 2.1.289 (0.1.0, loaded with --plugin-dir; tested the toast at 141 columns, opening with /your-turn, ticking with number keys, and the report button appearing; the first, powerline-style version was tested sending "3/3 完成了" from the report button and replacing the whole list on new commands; the TUI-panel version was tested in a 160-column tmux for frame titles, right-edge alignment, frames turning gray when done, and the report button; 0.2.0 was tested splitting into three frames in reply order with only the finished frame turning gray; 0.3.0 was tested listing both lines of a "請你在終端機登入 gcloud" ("please log in to gcloud in your terminal") block, not listing an ls after "我剛剛跑了" ("I just ran"), the note line above each step, and the report button after ticking everything; 0.6.0 was tested on 2.1.290 sending "5/5 完成了" by pressing r after ticking everything). The mods API is still in early access, so a Claude Code release may require changes.

A live Claude Code 2.1.290 session: the reply asks you to run 5 commands by hand, split into two frames, 在終端機跑 ("run in a terminal") and 在提示框打 ("type in the prompt"); 1 and 2 are ticked. With everything ticked:

The group icons are Nerd Font U+F120 and U+F075; without a Nerd Font they render as boxes.
#fabd2f = run in a terminal, aqua #8ec07c = type in the prompt, green #b8bb26 = done and keys. While a frame still has unfinished commands, its border uses the group color; once all are done, it turns dark gray #504945.sudo and other commands you run in a separate terminal go under 在終端機跑; commands starting with ! go under 在提示框打 (typed into Claude's prompt). ! cmd on one line) is a dim gray note: the sentence before it in the reply, copied as is (for example "wait for the install, then start the service:" or "takes about 2 minutes:"), truncated if too long. That sentence is the nearest non-blank line outside a code block; an inline ! cmd` uses its own line. Each sentence appears only once; when two code blocks have no text between them, the second gets no note.! whoami, then 3 sudo commands, then ! claude plugin list draws three frames: prompt 1, terminal 2–4, prompt 5).q to close the list (as in away-receipt). "q: 關閉" ("close") sits at the end of the key bar, also when the list is empty.y, then a number (like vim's y3), to copy that command to the clipboard without ticking it; useful when a command is too long for the pane. After y, the key bar changes to "1–3 按編號複製那條指令" ("press a number to copy that command"); press y again to cancel. After copying, a toast says "已複製第 N 條" ("copied #N") and the list goes back to ticking. Commands starting with ! are copied with the !, ready to paste into the prompt. From the 10th command on there is no number key: after y, click that command's ○.r (or click it) to send "N/N 完成了" for you (labeled as coming from your-turn; the model sees the original text).sudo. A copied prompt, $ sudo …, counts too; a trailing \ joins the next line.! <command>: inline code (for example ` ! gcloud auth login ) or a line in a code block. The ! must be followed by a space, so code like !e.agentId or !== does not count; neither does a placeholder such as ! <cmd> (anything containing <…>`).# comments. For example gcloud auth login after "請你在終端機登入 gcloud:". An untagged block counts as a whole only if every such line starts with an ASCII character and contains no arrows (→ ←) or box-drawing characters (─ │ ┌); continuation lines after a trailing \ are not checked. Otherwise (a prompt for you to copy elsewhere, a diagram drawn for you, output annotated with ←) only its sudo and ! lines count. A preceding sentence that starts with 我 ("I") does not count ("我自己試了一下:" / "I tried it myself:", "我在終端機跑了:" / "I ran in the terminal:" are Claude describing what it did).ls or git status after "我剛剛跑了:" / "I just ran:") do not count.sudo -k at both the start and the end); one mentioned twice in a row is listed once ("打 ! whoami,看 ! whoami 印出誰" / "type ! whoami and see who ! whoami prints").! go under 在提示框打 (! sudo … included), everything else under 在終端機跑./your-turn: opens the list, drawn at any width.turn.complete). Subagent turns do not count./clear the list is empty ($.state is reset)./your-turn. Once you have opened it with /your-turn (and not closed it by hand since), the threshold for automatic opening drops to 110 columns (measured 2026-10-05: a 141-column tmux pane did not open automatically).q work only while the pane has focus. On automatic opening it gets focus only if the prompt is empty; after you press Esc to go back to the prompt, use ctrl+x tab to switch back to the pane.sudo lines written as a script example in a code block are listed too.XMODIFIERS=@im=fcitx), every line is listed as a command. Blocks tagged text or another non-shell language are not.hooks/register.tsx 358 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, UiPressArgument } from 'claude-code'
3
4import type { Step } from '../types'
5
6// your-turn:主對話每輪結束時,從最後一則回覆(turn.complete 的 answer)抽出要你親手跑的指令:
7// 程式碼區塊裡 sudo 開頭的行(行尾 \ 接下一行)、`! <cmd>`(行內 code 或區塊裡的行);
8// 前一句有「你/手動/自己/親手/在終端機/另開」的 shell 區塊,整塊每行都算;
9// 沒標語言的區塊要每行都以英數符號開頭、沒有箭頭和框線字才算(整段中文 prompt、框線圖只取 sudo 與 ! 行)。
10// 清單照回覆裡出現的順序編號,不重排;連續在同一處跑的指令同一框,換地方跑就開新框。
11// 每段指令(一個程式碼區塊,或一行裡的行內 `! cmd`)上方帶回覆裡的前一句當說明(等多久、等什麼再跑)。
12// 抽到指令就重算清單、打開對話旁的 pane(主動打開:終端機 ≥144 欄才畫,你用 /your-turn 開過一次後降到 110 欄;
13// 沒畫出來就跳 toast 提示打 /your-turn;提示框是空的才拿得到焦點)。
14// pane 有焦點時按數字鍵勾完成、按 q 關掉;按 y 再按編號,把那條指令複製到剪貼簿(指令太長、pane 顯示不完時用)。
15// 全部勾完出現「回報」,按 r 或點它替你送出「N/N 完成了」。mod 只整理清單,不代跑任何指令。
16
17const PANE = 'your-turn'
18const TITLE = '要你跑的指令'
19// Button 的 hotkey 只收一個數字或小寫字母:前 9 條有數字鍵,第 10 條起只能用滑鼠點
20const MAX_KEYS = 9
21
22// TUI 面板樣式(學 lazygit/btop):連續在同一處跑的指令一個圓角框,標題嵌在上框線;底部是進度條加 lazygit 式按鍵列。
23// 框線自己用 Text 畫:Box 的 border 會蓋在子元素上面,標題疊不上去(2026-10-05 實測)
24// 配色和終端機一致,用 Gruvbox Dark Hard(morhetz/gruvbox)的 bright 色:黃=在終端機跑、水綠=在提示框打、綠=完成。
25// 框裡還有沒做完的指令時框線用該組顏色,全做完變深灰;做完的指令刪除線+淡色
26const YELLOW = '#fabd2f'
27const AQUA = '#8ec07c'
28const GREEN = '#b8bb26'
29const GRAY = '#928374'
30const DARK_GRAY = '#504945'
31const TRACK = '━' // U+2501 進度條:做完的塗綠、其餘塗深灰
32// 每列:「│ 」與「 │」4 欄+引擎畫的「1: 」3 欄+○/✔+空白+「$ 」2 欄
33const ROW_FIXED = 11
34// 進度條最長 16 格;同一行另有「進度 」和「 10/10」,約 12 欄
35const TRACK_MAX = 16
36const FOOT_FIXED = 12
37// 說明行:「│ 」與「 │」4 欄+縮排 2 欄
38const NOTE_FIXED = 6
39// 分組:sudo 和其他要你另開終端機的指令在終端機跑;! 開頭的在 Claude 的提示框打
40const GROUPS = [
41 { icon: '', color: YELLOW, title: '在終端機跑', has: (cmd: string) => !cmd.startsWith('!') }, // nf-fa-terminal
42 { icon: '', color: AQUA, title: '在提示框打', has: (cmd: string) => cmd.startsWith('!') }, // nf-fa-comment
43] as const
44
45const stepsAtom = atom({ plugin: 'your-turn', key: 'steps' } as const, [] as Step[])
46// 按了 y、等你按編號:這時按編號是複製,不是勾選(像 vim 的 y3)
47const yankAtom = atom({ plugin: 'your-turn', key: 'yank' } as const, false)
48
49export const register: Register = on => {
50 on('session.start', async ($, e, next) => {
51 const result = await next(e)
52 // 名稱衝突會丟例外,包住免得中斷 hook
53 try {
54 await $.command.register({ name: 'your-turn', description: 'your-turn:打開要你親手跑的指令清單' })
55 } catch (err) {
56 $.ui.log(`[your-turn] 註冊 /your-turn 失敗:${String(err)}`)
57 }
58 return result
59 })
60
61 // 你打的指令=asked:任何寬度都畫
62 on('command.run', { command: 'your-turn' }, async $ => {
63 await $.ui.open({ id: PANE, title: TITLE, focus: true })
64 return { text: '[your-turn] 已打開清單' }
65 })
66
67 on('turn.complete', async ($, e, next) => {
68 const result = await next(e)
69 if (e.agentId) {
70 return result
71 }
72 const cmds = extractCommands(e.answer)
73 // 這則回覆沒有新指令:清單不動
74 if (cmds.length === 0) {
75 return result
76 }
77 // 照回覆裡出現的順序編號,不按分組重排:前後常有依賴(先裝套件才能啟動服務)
78 await update($, stepsAtom, () => cmds.map(c => ({ ...c, isDone: false })))
79 await update($, yankAtom, () => false)
80 try {
81 const opened = await $.ui.open({ id: PANE, title: TITLE, focus: true })
82 if (!opened.isPlaced) $.ui.toast(`your-turn:${cmds.length} 條要你親手跑的指令,終端機太窄沒畫出來,打 /your-turn 打開`, { timeoutMs: 8000 })
83 } catch (err) {
84 $.ui.log(`[your-turn] 打開 pane 失敗:${String(err)}`)
85 }
86 return result
87 })
88
89 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
90 const { Box, Button, Text } = $.ui.resolve(e)
91 const steps = await read($, stepsAtom)
92 const isYanking = await read($, yankAtom)
93 // 和 away-receipt 一樣按 q 關;引擎內建的 ctrl+x x 要按兩個鍵。清單空的時候也要有,不然 q 沒作用
94 const close = <Button key="close" plain hotkey="q" label="關閉" onPress={() => $.ui.close({ id: PANE })} />
95 // 標題是一般文字,不用色塊
96 const header = (
97 <Text>
98 <Text bold>your-turn</Text>
99 <Text color={GRAY}>{` ${steps.length === 0 ? '沒有要你跑的指令' : `${steps.length} 條指令待你親手跑`}`}</Text>
100 </Text>
101 )
102 if (steps.length === 0) {
103 return (
104 <Box flexDirection="column">
105 {header}
106 <Box marginTop={1}>{close}</Box>
107 </Box>
108 )
109 }
110 const width = e.props.bodyColumns
111 const room = Math.max(10, width - ROW_FIXED)
112 const done = steps.filter(s => s.isDone).length
113 const isAllDone = done === steps.length
114 // 按鈕只放編號與 ○/✔(按鈕文字不能加刪除線、也不能上色);$/!、指令另用 Text 畫,做完的指令加刪除線。
115 // 右框線前補空白,讓每列剛好 pane 寬
116 const row = (s: Step, i: number, color: string, frame: string) => {
117 const isBang = s.cmd.startsWith('!')
118 const body = clip(isBang ? s.cmd.slice(1).trimStart() : s.cmd, room)
119 const mark = s.isDone ? '✔' : '○'
120 const label = i < MAX_KEYS ? mark : `${i + 1}: ${mark}`
121 const keyCols = i < MAX_KEYS ? 3 : 0
122 const pad = Math.max(0, width - 7 - keyCols - cols(label) - cols(body))
123 const press = (p: UiPressArgument) => (isYanking ? yank($, s.cmd, i, p) : toggle($, i))
124 return (
125 <Box key={`row-${i + 1}`} flexDirection="row">
126 <Text color={frame}>{'│ '}</Text>
127 {i < MAX_KEYS
128 ? <Button key={`step-${i + 1}`} plain hotkey={String(i + 1)} label={label} dimColor={s.isDone} onPress={press} />
129 : <Button key={`step-${i + 1}`} plain label={label} dimColor={s.isDone} onPress={press} />}
130 <Text color={color} bold dimColor={s.isDone}>{isBang ? ' ! ' : ' $ '}</Text>
131 <Text dimColor={s.isDone} strikethrough={s.isDone}>{body}</Text>
132 <Text color={frame}>{`${' '.repeat(pad)} │`}</Text>
133 </Box>
134 )
135 }
136 // 說明行:回覆裡這段指令的前一句,淡灰色、縮排 2 欄,太長截斷
137 const noteLine = (note: string, i: number, frame: string) => {
138 const body = clip(note, Math.max(10, width - NOTE_FIXED))
139 return (
140 <Box key={`note-${i + 1}`} flexDirection="row">
141 <Text color={frame}>{'│ '}</Text>
142 <Text color={GRAY}>{body}</Text>
143 <Text color={frame}>{`${' '.repeat(Math.max(0, width - NOTE_FIXED - cols(body)))} │`}</Text>
144 </Box>
145 )
146 }
147 // 照清單順序切框:連續在同一處跑的指令放同一框,換地方跑就開新框,所以同一組可能出現兩個框。
148 // 每框上方空一行,標題嵌在上框線;框裡還有沒做完的指令時框線用該組顏色
149 const runs: { g: (typeof GROUPS)[number]; members: { s: Step; i: number }[] }[] = []
150 steps.forEach((s, i) => {
151 const g = GROUPS.find(x => x.has(s.cmd)) ?? GROUPS[0]
152 const last = runs.at(-1)
153 if (last?.g === g) last.members.push({ s, i })
154 else runs.push({ g, members: [{ s, i }] })
155 })
156 const groups = runs.map(({ g, members }, n) => {
157 const frame = members.some(m => !m.s.isDone) ? g.color : DARK_GRAY
158 const title = ` ${g.icon} ${g.title} `
159 return (
160 <Box key={`box-${n}`} flexDirection="column" marginTop={1}>
161 <Text>
162 <Text color={frame}>{'╭─'}</Text>
163 <Text color={g.color} bold>{title}</Text>
164 <Text color={frame}>{`${'─'.repeat(Math.max(0, width - 3 - cols(title)))}╮`}</Text>
165 </Text>
166 {members.flatMap(m => [...(m.s.note ? [noteLine(m.s.note, m.i, frame)] : []), row(m.s, m.i, g.color, frame)])}
167 <Text color={frame}>{`╰${'─'.repeat(Math.max(0, width - 2))}╯`}</Text>
168 </Box>
169 )
170 })
171 // 進度條跟著 pane 寬度縮,最長 16 格
172 const track = Math.max(4, Math.min(TRACK_MAX, width - FOOT_FIXED))
173 const filled = Math.round((done / steps.length) * track)
174 // lazygit 式按鍵列:按鍵塗綠、說明用預設字色、項目之間用深灰 │ 隔開
175 const key = (s: string) => <Text color={GREEN} bold>{s}</Text>
176 const sep = <Text color={DARK_GRAY}>{' │ '}</Text>
177 const range = `1–${Math.min(MAX_KEYS, steps.length)}`
178 const keys = isYanking
179 ? [key(range), ' 按編號複製那條指令']
180 : isAllDone
181 ? [key('r'), ' 回報:告訴 Claude 全部完成']
182 : e.props.isFocused
183 ? [key(range), ' 勾選', sep, key('再按一次'), ' 取消']
184 : [key('ctrl+x tab'), ' 切過來', sep, key(range), ' 勾選']
185 // 再按一次 y 取消複製模式
186 const yankButton = <Button key="yank" plain hotkey="y" label={isYanking ? '取消複製' : '複製'} onPress={() => update($, yankAtom, v => !v)} />
187 return (
188 <Box flexDirection="column">
189 {header}
190 {groups}
191 <Box flexDirection="column" marginTop={1}>
192 <Text>
193 <Text color={GRAY}>進度 </Text>
194 <Text color={GREEN}>{TRACK.repeat(filled)}</Text>
195 <Text color={DARK_GRAY}>{TRACK.repeat(track - filled)}</Text>
196 <Text color={isAllDone ? GREEN : GRAY}>{` ${done}/${steps.length}`}</Text>
197 </Text>
198 {/* 複製、關閉鈕接在按鍵列尾:引擎畫成「y: 複製」「q: 關閉」 */}
199 <Box flexDirection="row">
200 <Text>{keys}{sep}</Text>
201 {yankButton}
202 <Text>{sep}</Text>
203 {close}
204 </Box>
205 </Box>
206 {isAllDone && (
207 <Button key="report" variant="primary" hotkey="r" label={`回報 ${steps.length}/${steps.length} 完成`} onPress={() => report($, steps.length)} />
208 )}
209 </Box>
210 )
211 })
212}
213
214// 前一句有這些字的 shell 區塊,整塊每行都是要你跑的指令;
215// 前一句以「我」開頭的不算(「我自己試了一下:」「我在終端機跑了:」是 Claude 說它做了什麼)
216const HINT_RE = /你|手動|自己|親手|在終端機|另開/
217const SELF_RE = /^(?:[-*]\s+|\d+\.\s+)?我/
218// shell 區塊:``` 後面是這幾種語言標記,或沒有標記(沒有標記的還要過 isShellLike)
219const SHELL_LANGS = new Set(['', 'bash', 'sh', 'shell', 'zsh', 'console'])
220
221// 回覆裡要你親手跑的指令,照出現順序;每段(一個程式碼區塊,或一行裡的行內 `! cmd`)的第一條帶前一句當 note。
222// 同一條在後面的步驟再出現就再列一次(例如最後再 sudo -k 一次);
223// 緊接著重複的只留一條(「打 `! whoami`,看 `! whoami` 印出誰」)
224function extractCommands(text: string): { cmd: string; note: string }[] {
225 const found: { cmd: string; note: string }[] = []
226 let inBlock = false
227 let isHinted = false
228 // 前一句:區塊外最近一行非空白的字;區塊結束就清掉,連續兩個區塊之間沒有字就不帶
229 let lead = ''
230 // 這一段還沒掛上的說明;和上一條掛出去的一樣就不重複(行內 `! cmd` 那行接著就是程式碼區塊時)
231 let note = ''
232 let lastNote = ''
233 const startSegment = (s: string) => {
234 note = s !== lastNote ? s : ''
235 }
236 const push = (cmd: string) => {
237 found.push({ cmd, note })
238 if (note !== '') lastNote = note
239 note = ''
240 }
241 // 區塊裡行尾 \ 的指令:先接起來,到沒有 \ 的那行才收
242 let pending = ''
243 const lines = text.split('\n')
244 for (const [n, raw] of lines.entries()) {
245 const line = raw.trim()
246 const fence = /^(?:```|~~~)\s*(\S*)/.exec(line)
247 if (fence) {
248 if (inBlock) {
249 lead = ''
250 } else {
251 const lang = (fence[1] ?? '').toLowerCase()
252 isHinted = SHELL_LANGS.has(lang) && HINT_RE.test(lead) && !SELF_RE.test(lead) && (lang !== '' || isShellLike(lines, n + 1))
253 startSegment(lead)
254 }
255 inBlock = !inBlock
256 pending = ''
257 continue
258 }
259 if (inBlock) {
260 if (pending !== '') {
261 pending = `${pending} ${line.replace(/\\$/, '').trim()}`
262 if (!line.endsWith('\\')) {
263 push(pending)
264 pending = ''
265 }
266 continue
267 }
268 // 照抄提示字元的寫法($ sudo …)也算;有提示字的區塊每行都算,空行與 # 註解除外
269 const cmd = isHinted
270 ? (line === '' || line.startsWith('#') ? undefined : line.replace(/^\$\s+/, ''))
271 : /^(?:\$\s+)?(sudo\s.+|!\s+\S.*)$/.exec(line)?.[1]
272 if (cmd === undefined || (cmd.startsWith('!') && isPlaceholder(cmd))) continue
273 if (cmd.endsWith('\\')) pending = cmd.slice(0, -1).trim()
274 else push(cmd)
275 continue
276 }
277 if (line === '') continue
278 const plain = line.replace(/`|\*\*/g, '')
279 // `!` 後面要有空白:`!e.agentId`、`!==` 這類程式碼不算
280 const inline = [...line.matchAll(/`(!\s+[^`]+)`/g)].map(m => (m[1] ?? '').trim()).filter(cmd => !isPlaceholder(cmd))
281 if (inline.length > 0) {
282 startSegment(plain)
283 for (const cmd of inline) push(cmd)
284 }
285 lead = plain
286 }
287 return found.filter((f, i) => f.cmd !== found[i - 1]?.cmd)
288}
289
290// 沒標語言的區塊從 from 行起到結束 fence:空行、# 註解與接在行尾 \ 後面的續行以外,
291// 每行(去掉 $ 提示字)都以英數符號開頭、行內沒有箭頭(→ ←)和框線字(─ │ ┌),才當 shell。
292// 不符合的是說明、要你貼去別處的 prompt、圖或加了註記的輸出,不是指令
293function isShellLike(lines: string[], from: number): boolean {
294 let isContinued = false
295 for (const raw of lines.slice(from)) {
296 const line = raw.trim()
297 if (/^(?:```|~~~)/.test(line)) break
298 const isSkipped = isContinued || line === '' || line.startsWith('#')
299 isContinued = line.endsWith('\\')
300 if (isSkipped) continue
301 const cmd = line.replace(/^\$\s+/, '')
302 if (!/^[\x21-\x7e]/.test(cmd) || /[←-⇿─-╿]/.test(cmd)) return false
303 }
304 return true
305}
306
307// 說明用的佔位寫法(`! <cmd>`)不是真的指令;sudo 區塊裡的 <佔位> 仍算(要你填好再跑)
308function isPlaceholder(cmd: string): boolean {
309 return /<[^>]+>/.test(cmd)
310}
311
312async function toggle($: EngineInterface, i: number) {
313 await update($, stepsAtom, list => list.map((s, j) => (j === i ? { ...s, isDone: !s.isDone } : s)))
314}
315
316// 複製後回到勾選模式;複製本身看不到,用 toast 告訴你複製了哪條。! 開頭的連 ! 一起複製,貼到提示框就能跑
317async function yank($: EngineInterface, cmd: string, i: number, p: UiPressArgument) {
318 await update($, yankAtom, () => false)
319 const copied = await $.ui.copy({ text: cmd, surface: p.surface })
320 $.ui.toast(copied.isCopied ? `your-turn:已複製第 ${i + 1} 條` : `your-turn:沒有複製到(${copied.reason})`, { timeoutMs: 4000 })
321}
322
323async function report($: EngineInterface, total: number) {
324 try {
325 const sent = await $.prompt.submit({ text: `${total}/${total} 完成了`, asUser: true })
326 if (sent.drop !== undefined) $.ui.log(`[your-turn] 回報被擋:${sent.drop}`)
327 } catch (err) {
328 $.ui.log(`[your-turn] 回報送出失敗:${String(err)}`)
329 }
330}
331
332// 超過 width 欄就截斷,最後一欄換成 …
333function clip(s: string, width: number): string {
334 if (cols(s) <= width) return s
335 let out = ''
336 let used = 0
337 for (const ch of s) {
338 const w = cols(ch)
339 if (used + w > width - 1) break
340 out += ch
341 used += w
342 }
343 return `${out}…`
344}
345
346// 終端機上的欄寬:中日韓字與全形字 2 欄,其他(含 Nerd Font 圖示、○、✔)1 欄
347function cols(s: string): number {
348 let n = 0
349 for (const ch of s) {
350 const c = ch.codePointAt(0) ?? 0
351 const isWide = (c >= 0x1100 && c <= 0x115f) || (c >= 0x2e80 && c <= 0xa4cf) || (c >= 0xac00 && c <= 0xd7a3)
352 || (c >= 0xf900 && c <= 0xfaff) || (c >= 0xfe30 && c <= 0xfe4f) || (c >= 0xff00 && c <= 0xff60)
353 || (c >= 0xffe0 && c <= 0xffe6) || (c >= 0x20000 && c <= 0x3fffd)
354 n += isWide ? 2 : 1
355 }
356 return n
357}
358types/index.d.ts 15 lines1// 一條要使用者親手跑的指令;isDone=使用者在 pane 按數字鍵勾了完成;
2// note=回覆裡這段指令的前一句(只有每段第一條有,空字串=不畫說明行)
3export type Step = { cmd: string; isDone: boolean; note?: string }
4
5declare module 'claude-code' {
6 interface PluginState {
7 'your-turn': {
8 // 最近一則帶指令的回覆抽出的清單;$.state 在 /clear 後歸零
9 steps: Step[]
10 // 按了 y、等使用者按編號複製
11 yank: boolean
12 }
13 }
14}
15