
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b $HOME/.local/bin init --source=~/ghq/github.com/0ta2/dots git@github.com:0ta2/dots.git
~/ghq/github.com/0ta2/dots/bootstrap.sh
Homebrew・mise を導入した後 mise run install (ドットファイル反映・不足ツール・skill・plugin の 導入・Codex 設定同期) を実行する。
mise run sync # ドットファイル反映 + Codex設定マージ (副作用なし、何度実行しても収束する)
mise run update # brew/mise/skill/uv tool の更新、最後に sync
直接 chezmoi コマンドを実行する場合は下記の通り:
~/.local/bin/chezmoi --source=~/ghq/github.com/0ta2/dots apply
コンピュータ名 を変更パスワードやパスキーを自動入力 をOFFドラッグにトラックパッドを使用 をONDock のサイズ小さく好みのサイズへ変更Dock の拡大を好みの大きさへ変更Dock を自動的に表示/非表示をOFF外観モードをダークに変更Spotlight検索を表示 のショートカットのチェックを外すCmd + スペース に変更Cmd + shift + スペース に変更CapsLock を Ctrl に変更するiwano.nvim / amanoukihashi.nvim をローカルのリポジトリから読み込むため、下記のシンボリックリンクを作成する:
mkdir -p ~/.local/share/nvim/site/pack/mine/opt
ln -s ~/ghq/github.com/0ta2/iwano.nvim ~/.local/share/nvim/site/pack/mine/opt/iwano.nvim-local
ln -s ~/ghq/github.com/0ta2/amanoukihashi.nvim ~/.local/share/nvim/site/pack/mine/opt/amanoukihashi.nvim-localhooks/register.tsx 105 lines1import type { Register } from 'claude-code'
2
3import { isDangerous, linkRefs, segmentsOf, type Callout } from './segments'
4
5const HEADING_COLORS = ['#ff9e64', '#7aa2f7', '#9ece6a'] as const
6const CALLOUTS: Record<Callout, { label: string; color: string }> = {
7 important: { label: '重要', color: '#f7768e' },
8 warning: { label: '注意', color: '#e0af68' },
9 note: { label: 'メモ', color: '#565f89' },
10}
11const DANGER_COLOR = '#f7768e'
12const ASK_COLOR = '#e0af68'
13const MINE_BACKGROUND = '#24283b'
14const FORMAT = [
15 '# Highlighting in replies',
16 'The transcript draws these markers in color, so the user notices them:',
17 '- `> [!IMPORTANT]` on its own line, the block below it quoted with `> `: the answer to what the user asked, and anything the user has to decide or do. At most one or two per reply.',
18 '- `> [!WARNING]`: a risk or a side effect the user should know before acting.',
19 '- `> [!NOTE]`: an aside that can be skipped.',
20 '- `##` headings split a long reply into parts; a short reply needs none.',
21 '- Write a pull request or issue as `owner/repo#123` so it becomes a link.',
22].join('\n')
23
24export const register: Register = on => {
25 on('prompt.compose', async (_$, e, next) => {
26 const composed = await next(e)
27 if (e.surfaces.length === 0) return composed
28 return { sections: [...composed.sections, { id: 'accent:format', text: FORMAT, scope: 'session' as const }] }
29 })
30
31 on('ui.render', { component: 'AssistantMessage' }, async ($, e, next) => {
32 if (e.props.isSummary) return next(e)
33 const segments = segmentsOf(e.props.text)
34 const hasRefs = linkRefs(e.props.text) !== e.props.text
35 if (!hasRefs && segments.every(one => one.kind === 'markdown')) return next(e)
36 const { Box, Markdown, Text } = $.ui.resolve(e)
37
38 return (
39 <Box flexDirection="column">
40 {segments.map((one, index) =>
41 one.kind === 'heading' ? (
42 <Text key={`h:${index}`} bold underline={one.level === 1} color={HEADING_COLORS[one.level - 1]}>
43 {`${one.level === 1 ? '■' : one.level === 2 ? '▍' : '・'} ${one.text}`}
44 </Text>
45 ) : one.kind === 'callout' ? (
46 <Box key={`c:${index}`} flexDirection="column" borderStyle="round" borderColor={CALLOUTS[one.callout].color} paddingX={1}>
47 <Text bold color={CALLOUTS[one.callout].color}>{CALLOUTS[one.callout].label}</Text>
48 <Markdown text={linkRefs(one.text)} dimColor={one.callout === 'note'} />
49 </Box>
50 ) : (
51 <Markdown key={`m:${index}`} text={linkRefs(one.text)} />
52 ),
53 )}
54 </Box>
55 )
56 })
57
58 on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
59 const command = e.props.tool === 'Bash' && typeof (e.props.input as { command?: unknown })?.command === 'string' ? (e.props.input as { command: string }).command : ''
60 if (!isDangerous(command)) return next(e)
61 const drawn = await next(e)
62 const { Box, Text } = $.ui.resolve(e)
63
64 return (
65 <Box flexDirection="column" borderStyle="round" borderColor={DANGER_COLOR} paddingX={1}>
66 <Text bold color={DANGER_COLOR}>⚠ 取り消しにくい操作</Text>
67 {drawn}
68 </Box>
69 )
70 })
71
72 on('ui.render', { component: 'AskUserQuestion' }, async ($, e, next) => {
73 const drawn = await next(e)
74 const { Box, Text } = $.ui.resolve(e)
75
76 return (
77 <Box flexDirection="column" borderStyle="double" borderColor={ASK_COLOR} paddingX={1}>
78 <Text bold color={ASK_COLOR}>🙋 あなたの判断待ち</Text>
79 {drawn}
80 </Box>
81 )
82 })
83
84 on('ui.render', { component: 'UserMessage' }, async ($, e, next) => {
85 const { origin, from } = e.props
86 const isMine = origin.kind === 'composer'
87 const sender = from?.name ?? (origin.kind === 'channel' ? origin.server : undefined)
88 // A collapsed row from someone else already names its sender in one line.
89 if (!isMine && (sender === undefined || !e.props.isExpanded)) return next(e)
90 const drawn = await next(e)
91 const { Box, Text } = $.ui.resolve(e)
92
93 return isMine ? (
94 <Box flexDirection="column" backgroundColor={MINE_BACKGROUND}>
95 {drawn}
96 </Box>
97 ) : (
98 <Box flexDirection="column">
99 <Text bold color="#7dcfff">{`📨 ${sender}`}</Text>
100 {drawn}
101 </Box>
102 )
103 })
104}
105hooks/segments.ts 97 lines1export type Callout = 'important' | 'warning' | 'note'
2export type Segment = { kind: 'markdown'; text: string } | { kind: 'heading'; level: number; text: string } | { kind: 'callout'; callout: Callout; text: string }
3
4const CALLOUT = /^>\s*\[!(IMPORTANT|WARNING|NOTE)\]\s*$/i
5const HEADING = /^(#{1,3})\s+(.+?)(?:\s+#+)?\s*$/
6const REPO_REF = /(?<![\w/.-])([\w.-]+\/[\w.-]+)#(\d+)\b/g
7const DANGER = /(^|[\s;&|(])(rm\s+-\w*[rf]|sudo\b|git\s+(push|reset\s+--hard|clean\s+-\w*f|branch\s+-D|checkout\s+--|restore\b)|gh\s+(pr\s+merge|repo\s+delete|release\s+delete)|chezmoi\s+apply|mise\s+run\s+chezmoi:apply|brew\s+(bundle|uninstall)|kill(all)?\b|chmod\b|chown\b|dd\s+if=|truncate\b)/
8
9export const isDangerous = (command: string): boolean => DANGER.test(command)
10
11/** What a reference inside stays as written: an inline code span of any backtick count, or a Markdown link. */
12const KEPT = /(?<!`)(`+)(?!`)[\s\S]*?(?<!`)\1(?!`)|\[[^\]\n]*\]\([^)\n]*\)/g
13const link = (text: string) => text.replace(REPO_REF, (all, repo, number) => `[${all}](https://github.com/${repo}/issues/${number})`)
14
15const fenceOf = (line: string): string | undefined => /^\s*(`{3,}|~{3,})/.exec(line)?.[1]
16/** Whether `line` closes a block opened by `fence`: the same character, at least as many, nothing after. */
17const closes = (line: string, fence: string): boolean => {
18 const marker = fenceOf(line)
19 return marker !== undefined && marker[0] === fence[0] && marker.length >= fence.length && line.trim() === marker
20}
21
22const linkOutsideKept = (text: string): string => {
23 let out = ''
24 let last = 0
25 for (const span of text.matchAll(KEPT)) {
26 out += link(text.slice(last, span.index)) + span[0]
27 last = span.index + span[0].length
28 }
29 return out + link(text.slice(last))
30}
31
32/** `owner/repo#123` becomes a link to it on GitHub, outside fenced blocks, inline code and existing links. */
33export const linkRefs = (text: string): string => {
34 const out: string[] = []
35 let prose: string[] = []
36 let fence: string | undefined
37 const flush = () => {
38 if (prose.length > 0) out.push(linkOutsideKept(prose.join('\n')))
39 prose = []
40 }
41 for (const line of text.split('\n')) {
42 if (fence !== undefined) {
43 out.push(line)
44 if (closes(line, fence)) fence = undefined
45 } else if (fenceOf(line) !== undefined) {
46 flush()
47 fence = fenceOf(line)
48 out.push(line)
49 } else {
50 prose.push(line)
51 }
52 }
53 flush()
54 return out.join('\n')
55}
56
57export const segmentsOf = (text: string): Segment[] => {
58 const out: Segment[] = []
59 let markdown: string[] = []
60 let callout: { callout: Callout; lines: string[] } | undefined
61 let fence: string | undefined
62 const flush = () => {
63 if (markdown.join('').trim()) out.push({ kind: 'markdown', text: markdown.join('\n') })
64 markdown = []
65 }
66 const close = () => {
67 if (callout) out.push({ kind: 'callout', callout: callout.callout, text: callout.lines.join('\n') })
68 callout = undefined
69 }
70
71 for (const line of text.split('\n')) {
72 if (callout && line.startsWith('>') && !CALLOUT.test(line)) {
73 callout.lines.push(line.replace(/^>\s?/, ''))
74 continue
75 }
76 close()
77 const marker = fenceOf(line)
78 const isFenced = fence !== undefined || marker !== undefined
79 if (fence === undefined) fence = marker
80 else if (closes(line, fence)) fence = undefined
81 const heading = isFenced ? null : HEADING.exec(line)
82 const opened = isFenced ? null : CALLOUT.exec(line)
83 if (opened) {
84 flush()
85 callout = { callout: opened[1]!.toLowerCase() as Callout, lines: [] }
86 } else if (heading) {
87 flush()
88 out.push({ kind: 'heading', level: heading[1]!.length, text: heading[2]! })
89 } else {
90 markdown.push(line)
91 }
92 }
93 close()
94 flush()
95 return out
96}
97