現セッションの scratchpad を Finder で開くボタンと /scratchpad コマンド

個人用のdotfiles設定リポジトリです。 パッケージ管理と Homebrew 管理は別の nix-config リポジトリで行い、このリポジトリは主にシェルやアプリ設定を管理します。
git clone https://github.com/yourusername/dotfiles.git ~/dotfiles
cd ~/dotfiles
cd ~/nix-config
darwin-rebuild switch --flake .#<nix-configで定義したホスト名>
./install.sh
既存の設定ファイルは~/backupディレクトリにバックアップされます。
install.sh: dotfilesのシンボリックリンク作成スクリプトMANUAL_APPS.md: 手動インストールが必要なアプリケーション一覧.config/: 各種アプリケーションの設定ファイル.zshrc: Zshシェルの設定.gitconfig: Git設定.tmux.conf: tmux設定.claude/: Claude Code の設定.codex/: Codex の設定hooks/index.tsx 77 lines1import type { EngineInterface, Register } from "claude-code";
2
3let fileCount = 0;
4let watcherStarted = false;
5
6async function scratchpadDir($: EngineInterface): Promise<string> {
7 const id = await $.session.id();
8 const root = await $.session.root();
9 const uid = (await $.process.run(["id", "-u"])).stdout.trim();
10 // scratchpad の実体パス規約 (ハーネス内部実装依存): /tmp/claude-<uid>/<root の / を - に置換>/<sessionId>/scratchpad
11 return `/tmp/claude-${uid}/${root.replaceAll("/", "-")}/${id}/scratchpad`;
12}
13
14async function openScratchpad($: EngineInterface): Promise<string> {
15 const dir = await scratchpadDir($);
16 await $.process.run(["mkdir", "-p", dir]);
17 await $.process.run(["open", dir]);
18 return dir;
19}
20
21async function refreshCount($: EngineInterface): Promise<void> {
22 const dir = await scratchpadDir($);
23 const r = await $.process.run(["find", dir, "-type", "f"]);
24 const n =
25 r.exitCode === 0 ? r.stdout.split("\n").filter((l) => l !== "").length : 0;
26 if (n !== fileCount) {
27 fileCount = n;
28 $.ui.invalidate("ui.render");
29 }
30}
31
32function ensureWatcher($: EngineInterface): void {
33 if (watcherStarted) return;
34 watcherStarted = true;
35 $.clock.every(5000, () => refreshCount($));
36}
37
38export const register: Register = (on) => {
39 on("session.start", async ($, e, next) => {
40 await $.command.register({
41 name: "scratchpad",
42 description: "現セッションの scratchpad を Finder で開く",
43 });
44 ensureWatcher($);
45 return next(e);
46 });
47
48 // リロード時は session.start が再発火しないため他フックからも起動できるようにする
49 on("prompt.submit", ($, e, next) => {
50 ensureWatcher($);
51 return next(e);
52 });
53
54 on("command.run", { command: "scratchpad" }, async ($) => {
55 return { text: `開いた: ${await openScratchpad($)}` };
56 });
57
58 on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
59 if (fileCount === 0) return next(e);
60 const { Box, Button } = $.ui.resolve(e);
61 // pr-watch 等が同じ帯を使うため、チェーンの結果に自分のボタンを併記する
62 const rest = await next(e);
63 return (
64 <Box flexDirection="column">
65 {rest}
66 <Button
67 key="scratchpad"
68 label={`scratchpad (${fileCount})`}
69 plain
70 dimColor
71 onPress={() => void openScratchpad($)}
72 />
73 </Box>
74 );
75 });
76};
77