✌️

Claude Codeの出力をVSCodeでファイルとして開くSkillsを作って快適

に公開

こんにちは!株式会社ZAICOのWebエンジニアの@yutonishiです。

今回作ったもの

タイトルの通りになりますが、VSCodeのターミナルでClaude Codeを動かしている時に、直前の出力内容をファイルとして開けるSkillsを作成しました。

なぜ作ったか

自分はClaude CodeをVSCodeのターミナルから使用しています。
この環境だとPlanモードの出力など、長めでしっかり読む必要のある回答の場合は読みづらいです。

調べてみるとClaude CodeのVSCode拡張機能があったので早速使ってみました

長文を読むという意味では、ターミナルに出力されるよりも読みやすくなりました
ただ、以下のような点が気になって採用を見送りました

  • Ctrl + Jによる改行ができない
  • ターミナルとは別の領域を必要としてVSCode全体のレイアウトが窮屈になる
    • Claude Codeの出力の長文を読むというのは頻度としてはそこまで多くないので、必要な時にパッと表示できるのが理想
  • チャットUIという点は同じなので、どこからが出力のスタートなのかがぱっと見でわかりづらい

以上のような点から、「直前の出力内容をそのままファイルとして表示できたら使いやすそう」と考えました。

実装と試行錯誤

方針

カスタムコマンドで実現できそうだと思って調べていたら、どうやらカスタムコマンドではなく今後は代わりにSkillsを使っていこうということになっていたみたいです。

The .claude/commands/ directory is the legacy format. The recommended format is .claude/skills/<name>/SKILL.md, which supports the same slash-command invocation (/name) plus autonomous invocation by Claude. See Skills for the current format. The CLI continues to support both formats, and the examples below remain accurate for .claude/commands/.

https://code.claude.com/docs/en/agent-sdk/slash-commands#creating-custom-slash-commands

今回は一旦自分だけが使えればOKなので~/.claude/skills/ 配下に以下のようなSkillsファイルを作成しました

~/.claude/skills/open-vscode/SKILL.md
---
allowed-tools: Write, Bash(code:*)
description: 直前の回答をMarkdownファイルに書き出してVS Codeで開く
---

直前のあなた自身の回答を、そのままMarkdownファイルに書き出してください。
書き出したら code コマンドでVS Codeで開いてください。

この時点でイメージした通りに、Claude Codeで出力 → 登録した/open-vscodeSkillsを実行 → VSCodeで出力内容がファイルとして開く

ただ、後述する問題が発生したので対処していきます。

つまづき1: 遅い

前述のSkillsををのまま実行したら、ファイルが開くまでに10秒以上かかりました。

コマンド実行後に表示される処理時間を見ると「Crunched for 17s」と出ていたので、
おそらくLLMが直前の回答の全文をもう一度生成し直して Write ツールに渡すような処理になっていたのかと思います。

そこで直前の回答の取得、ファイルへの書き込みに関してはLLMにやらせないように修正しました。

Claude Codeはセッションの会話ログを ~/.claude/projects/ 以下に JSONL 形式で保存しています。
もともとLLMでやっていた直前の回答を取得して、ファイルに書き込むという処理をシェルスクリプトにして、それをSkillsから呼び出すだけにするという構成にします

~/.claude/scripts/dump-last.sh
#!/usr/bin/env bash
set -euo pipefail

PROJECT_DIR=$(pwd | sed 's|/|-|g')
SESSION_DIR="$HOME/.claude/projects/${PROJECT_DIR}"

SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1)

# 直近のassistant textブロックを取得
LAST_TEXT=$(jq -s -r '
  reverse
  | map(select(.type=="assistant") | .message.content[]? | select(.type=="text") | .text)
  | .[0] // empty
' "$SESSION_FILE")

mkdir -p /tmp/claude-notes
OUT="/tmp/claude-notes/dump-$(date +%s).md"
printf '%s\n' "$LAST_TEXT" > "$OUT"
code -r "$OUT"
echo "$OUT"
~/.claude/skills/open-vscode/SKILL.md
---
allowed-tools: Bash(~/.claude/scripts/dump-last.sh)
description: 直前の回答をMarkdownファイルに書き出してVS Codeで開く
---

!`~/.claude/scripts/dump-last.sh`

!を付けると、その行は bash コマンドとして実行されます。Claude のやることは「スクリプトを実行してパスを受け取る」だけになり、処理時間は17秒から1〜2秒まで縮みました。

つまづき2: 確認が毎回出る

速くはなったものの、実行のたびに確認ダイアログが出ていました。

スクリプト実行後に Claude が「スクリプトが何を作ったか確認しよう」と気を利かせて、cat で生成ファイルを読みに行っていたことです。これは完全に余計な動作なので、SKILL.md のプロンプトで明示的に禁止するようにしました。

~/.claude/skills/open-vscode/SKILL.md
---
allowed-tools: Bash(~/.claude/scripts/dump-last.sh)
description: 直前の回答をMarkdownファイルに書き出してVS Codeで開く
---

以下を実行する。それ以外は一切しない。
- 生成されたファイルの内容を読みに行かない(cat/Read/head等を使わない)
- 結果を要約・解説しない
- 出力されたパスを1行だけそのまま返す

!`~/.claude/scripts/dump-last.sh`

完成版

~/.claude/scripts/dump-last.sh
#!/usr/bin/env bash
set -euo pipefail

PROJECTS_ROOT="$HOME/.claude/projects"
CWD="$(pwd)"

SANITIZED="-$(echo "$CWD" | sed -e 's|^/||' -e 's|[/_.]|-|g')"

SESSION_DIR=""
if [[ -d "$PROJECTS_ROOT/$SANITIZED" ]]; then
  SESSION_DIR="$PROJECTS_ROOT/$SANITIZED"
else
  # フォールバック: 最近更新されたプロジェクトディレクトリの中から、
  # JSONLの cwd フィールドが現在のディレクトリと一致するものを探す
  while IFS= read -r dir; do
    latest=$(ls -t "$dir"/*.jsonl 2>/dev/null | head -1 || true)
    [[ -z "$latest" ]] && continue
    found_cwd=$(jq -r 'select(.cwd) | .cwd' "$latest" 2>/dev/null | head -1)
    if [[ "$found_cwd" == "$CWD" ]]; then
      SESSION_DIR="$dir"
      break
    fi
  done < <(ls -td "$PROJECTS_ROOT"/*/ 2>/dev/null)
fi

if [[ -z "$SESSION_DIR" || ! -d "$SESSION_DIR" ]]; then
  echo "Session directory not found for cwd: $CWD" >&2
  exit 1
fi

SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1 || true)
if [[ -z "$SESSION_FILE" ]]; then
  echo "No session file in $SESSION_DIR" >&2
  exit 1
fi

LAST_TEXT=$(jq -s -r '
  reverse
  | map(select(.type=="assistant") | .message.content[]? | select(.type=="text") | .text)
  | .[0] // empty
' "$SESSION_FILE")

if [[ -z "$LAST_TEXT" ]]; then
  echo "No assistant text found" >&2
  exit 1
fi

mkdir -p /tmp/claude-notes
OUT="/tmp/claude-notes/dump-$(date +%s).md"
printf '%s\n' "$LAST_TEXT" > "$OUT"
code -r "$OUT"
echo "$OUT"

※つまづき1でご紹介したコードから少し変更を加えています。

~/.claude/skills/open-vscode/SKILL.md
---
allowed-tools: Bash(~/.claude/scripts/dump-last.sh)
description: 直前の回答をMarkdownファイルに書き出してVS Codeで開く
---

以下を実行する。それ以外は一切しない。
- 生成されたファイルの内容を読みに行かない(cat/Read/head等を使わない)
- 結果を要約・解説しない
- 出力されたパスを1行だけそのまま返す

!`~/.claude/scripts/dump-last.sh`

まとめ

今回ご紹介したSkillsを使えば、Claude Codeと壁打ちしている時など、「ちょっと長いしファイルで確認したいな」となった時にコマンド一発でファイル化してくれるので地味に便利です。
Claude CodeにはSkillsのように各々の好みに合わせて細かくカスタマイズしたりできる仕組みが用意されているのが面白いですね。

作ってみて、正直Claude CodeのSkillsである必要があるのかと言われると微妙ですが、Skillsとシェルスクリプトを使うとこんなこともできるのかという学びになったので記事にしました。

もっといいやり方あるよ〜という方がいればコメントいただけると嬉しいです!

ZAICO Developers Blog

Discussion