🧙

Claude CodeのSkillsを作成例から徹底理解する

に公開

はじめに:AIエージェントの「できること」を拡張する

Claude Codeは、ターミナル上で動くAIエージェントです。コードの読み書きだけでなく、PowerShellやPython、OSコマンドの実行もできます。しかし、プロジェクト固有の業務手順やツール連携をClaudeが最初から知っているわけではありません。

ここで登場するのが Skills です。

Skillsとは、Claude Codeに「特定の業務をどう実行するか」を教えるための仕組みです。SKILL.md というファイルに手順を書いておくだけで、Claudeはそのスキルを自律的に選択し、PowerShellスクリプトやPythonプログラムを実行して業務を遂行します。

新人に業務マニュアルを渡すのに似ています。マニュアル(SKILL.md)とツール(スクリプト)をセットで渡せば、新人はそれを読んで自分で判断し、作業を進めてくれる。Skillsはまさにそのデジタル版です。

本記事では、以下の4つのスキルを実際に構築する過程を通じて、Skillsの設計思想から実装の細部まで徹底的に解説します。

  • SharePointからExcelファイルをダウンロード(PowerShell + PnP.PowerShell)
  • ExcelをJSON形式に変換(Python + openpyxl)
  • IDを指定してメンバー情報を検索(Python)
  • 都道府県のWikipedia情報をブラウザ操作で取得(Python + Playwright)

特に、クラウドサービスのAPI操作やブラウザ操作はAIエージェント単体では苦手な領域です。Skillsを使えば、これらを 端末のローカル処理 として実行できるため、AIだけでは超えられなかった壁を突破できます。

この記事で得られること

  • Claude Code Skillsの設計思想と内部動作(Progressive Disclosure)の理解
  • SKILL.mdの書き方とフロントマターの設計指針
  • PowerShell・Python・Playwrightを組み合わせた業務自動化の実装
  • スクリプト配置パターン(スキル内配置 vs プロジェクトルート配置)の使い分け
  • 非エンジニアの業務活用や、アプリ/インフラ保守への応用可能性

前提知識

  • Claude Codeの基本操作(インストール・起動)
  • PowerShell / Pythonの基礎
  • 「AIエージェント」の概念(LLMが自律的にツールを選択・実行すること)

1. Claude Code Skillsとは何か

1-1. Skillsの位置づけ

Claude Codeには、拡張の仕組みとして MCP(Model Context Protocol)Skills があります。MCPは外部ツールとの連携プロトコルですが、Skillsは 「ファイルシステムベースの知識パッケージ」 です。

Skillsの本質は、Claude Codeが必要なときに動的に読み込む 「業務マニュアル+ツール一式」 です。Anthropic公式ブログでは、Skillsを 「新しいチームメンバーへのオンボーディングガイド」 に例えています。

1-2. Skillsの起動方式:モデル起動 vs ユーザー起動

Skillsには2つの起動方式があります。

起動方式 説明
モデル起動 Claudeが会話の文脈からスキルを自動選択して実行 「SharePointからファイルを取得して」→ Claudeが download-sharepoint スキルを選択
ユーザー起動 /スキル名 で明示的に呼び出し /download-sharepoint と入力

モデル起動の判断には、SKILL.mdの フロントマター(name / description) が使われます。Claude Codeは起動時にすべてのスキルのフロントマターを読み込み、ユーザーの指示に最も適したスキルを選択します。

1-3. Progressive Disclosure ― 段階的な情報開示

Skillsの最も重要な設計思想が Progressive Disclosure(段階的開示) です。これは「必要な情報を、必要なときに、必要な分だけ読み込む」という原則です。

レベル1: フロントマター(name + description)  → 常にコンテキストに存在(~100語)
レベル2: SKILL.md本文                         → スキル起動時に読み込み
レベル3: 補助ファイル(scripts/、references/)  → 必要に応じて読み込み or 実行

なぜこの設計が重要かというと、LLMには コンテキストウィンドウ という容量制限があるからです。すべてのスキル情報を最初から読み込むと、コンテキストを圧迫して精度が落ちます。Progressive Disclosureにより、スキルをいくつインストールしても、実際にコンテキストを消費するのは使われるスキルだけになります。

さらに、Pythonスクリプトなどの実行可能ファイルは コンテキストに読み込まずに実行できる という特徴があります。スクリプトのコード本体はコンテキストウィンドウを消費せず、実行結果(出力)のみがコンテキストに入ります。

1-4. Skillsでできること

Skillsを使うと、会議ドキュメントの一気通貫処理やブランド準拠の資料生成、開発ワークフローの自動化など、エンジニア・非エンジニアを問わず幅広い業務に適用できます。

Skillsでできること

1-5. Skillsを使うとうれしいこと

Progressive Disclosureによるコンテキスト効率、ローカル実行によるクラウドAIの制約突破、そしてSKILL.mdの公開標準化による移植性が、従来のAIエージェントとの差別化ポイントとなります。

Skillsを使うとうれしいこと


2. デモプロジェクトの全体像

2-1. デモの要件と機能

今回のデモでは、「これまでUI画面で行っていた作業を、AIエージェントとのチャットベースで実行できるようにする」ことを目指します。

デモの全体像
ユーザーがClaude Codeに自然言語で指示し、4つのスキルが連携して業務を遂行する。

デモで実行できる4つの機能は以下の通りです。

# スキル コマンド 技術スタック 内容
SharePointダウンロード /download-sharepoint PowerShell + PnP.PowerShell SharePointからExcelファイルを取得し botwork/ に保存
Excel → JSON変換 /excel-to-json Python + openpyxl ダウンロードしたExcelをJSON形式に変換し botwork に保存
メンバー検索 /member-lookup Python IDを指定してJSONからメンバー情報を取得
ロケーション検索 /search-location Python + Playwright Wikipediaの検索テキストボックスに都道府県名を入力し検索

2-2. 動作フロー

①〜③はパイプラインとして連携しています。チームメンバーの最新情報をSharePointからダウンロードして属性情報を検索できるようにします。④は独立したスキルです。

[SharePoint]
     │  ① /download-sharepoint (PowerShell + PnP.PowerShell)

[botwork/所属メンバー一覧_local.xlsx]
     │  ② /excel-to-json (Python + openpyxl)

[botwork/MemberList.json]
     │  ③ /member-lookup (Python)

[メンバー情報の表示]

[Wikipedia]
     │  ④ /search-location (Python + Playwright)

[都道府県の概要・目次の表示]

2-3. ディレクトリ構成とアーキテクチャ

超概要の構成は下記です。
アーキテクチャ図(超概要)
フォルダ構成とSkillsの関係図。SKILL.mdの「フロントマター」はClaude Codeのコンテキストに常にインプットされ、「本文」はスキル使用時にインプットされる。

この記事のデモの構成を細かくして見ると下記です。
アーキテクチャ図
.claude/skills/ 配下に4つのスキルを定義し、それぞれがSKILL.mdの「フロントマター」と「本文」で構成される。フロントマターはClaude Codeのコンテキストに常駐し、本文はスキル起動時にのみ読み込まれる(Progressive Disclosure)。公式のSkills構成に加えて、スクリプトの入出力先として botwork/ ディレクトリを、接続情報の外部化として .env ファイルを独自に設けている。これにより、機密情報をコードから分離しつつ、スクリプト間のデータ受け渡し(Excel → JSON → 検索結果)を一箇所で管理できる。

ディレクトリ構成は以下の通りです。

.
├── .claude/
│   └── skills/                   # Claude Code スキル定義
│       ├── download-sharepoint/
│       │   └── SKILL.md
│       ├── excel-to-json/
│       │   └── SKILL.md
│       ├── member-lookup/
│       │   └── SKILL.md
│       └── search-location/
│           └── SKILL.md
├── scripts/
│   ├── 01_downloadExcelFromSharepoint.ps1
│   ├── 02_excel_to_json_python.py
│   ├── 03_member_lookup.py
│   └── 04_search_location.py
├── botwork/                      # スクリプトの入出力先(.gitignore対象)
├── .env                          # 環境変数(.gitignore対象)
├── .env.example                  # 環境変数テンプレート
└── README.md

ここで重要なのは、SKILL.md(.claude/skills/配下)とスクリプト(scripts/配下)が分離している という点です。この設計選択については後述します。


3. 環境構築

3-1. 前提条件

項目 要件
OS Windows 11
Claude Code インストール済み
PowerShell 7.5.4 以上
Python 3.11 以上

3-2. セットアップ手順

リポジトリのクローンと環境変数の設定:

git clone https://github.com/Masa1984a/claude-skills-demo.git
cd claude-skills-demo

# 環境変数テンプレートをコピーして編集
cp .env.example .env

.env ファイルを自分の環境に合わせて編集します。

# SharePoint接続設定(PowerShell用)
SITE_URL=https://your-tenant.sharepoint.com/sites/YourSite
LOCAL_PATH=.\botwork\
LOCAL_FILE=your-file_local.xlsx
FILE_URL=/sites/YourSite/Shared Documents/General/your-file.xlsx

# Python用
BASE_DIR=botwork
EXCEL_FILE=your-file_local.xlsx
OUTPUT_FILE=MemberList.json
SHEET_NAME=Sheet1

Pythonライブラリのインストール:

pip install openpyxl playwright
python -m playwright install chromium

PowerShellモジュールのインストール(管理者権限で実行):

Install-Module -Name PnP.PowerShell -Scope CurrentUser -Force

4. SKILL.md ― スキル定義の徹底解説

ここからが本記事の核心です。4つのSKILL.mdを1つずつ読み解いていきます。

4-1. download-sharepoint:SharePointからExcelをダウンロード

下記のようにSharePointに格納されているExcelファイルをダウンロードします。

具体的なClaude Codeでの挙動は動画の通りです。

/download-sharepoint を実行すると、PowerShellスクリプトが起動し、ブラウザ認証ダイアログが表示される。認証完了後、SharePointからExcelファイルが botwork/ にダウンロードされる。

ダウンロード完了後、Excelファイルが botwork/ に格納されていることが確認できます。

それでは、SKILL.mdの中身を見ていきましょう。

---
name: download-sharepoint
description: SharePointからExcelファイルをダウンロードする
disable-model-invocation: false
---

SharePointからExcelファイルをダウンロードする:

1. 以下のPowerShellスクリプトを実行する:
   powershell -ExecutionPolicy Bypass -File "./scripts/01_downloadExcelFromSharepoint.ps1"
2. ダウンロード完了を確認する
3. ファイルが `botwork` に保存されたことを報告する

---

## 使い方

Claude Code上で以下を入力するだけで実行されます。
/download-sharepoint

解説:

フロントマター部分:

フィールド 意味
name download-sharepoint /download-sharepoint というスラッシュコマンドになる
description SharePointからExcelファイルをダウンロードする Claudeがスキル選択時に参照する説明文
disable-model-invocation false モデル起動を許可する(デフォルトはfalse)

disable-model-invocation: false は、ユーザーがスラッシュコマンドを打たなくても、Claudeが文脈から自動的にこのスキルを使えることを意味します。例えば「SharePointからファイルを取得して」と言えば、Claudeがこのスキルを選択します。

本文部分:

本文はClaudeへの「指示書」です。ここでは3ステップのシンプルな手順が記述されています。注目すべきは、PowerShellの実行コマンドが 具体的なパスとオプション付き で書かれている点です。

powershell -ExecutionPolicy Bypass -File "./scripts/01_downloadExcelFromSharepoint.ps1"

-ExecutionPolicy Bypass はWindows環境でスクリプト実行ポリシーを一時的に回避するオプションです。Claude Codeから実行する場合、実行ポリシーがデフォルトで制限されていることがあるため、この指定が必要です。

4-2. excel-to-json:ExcelからJSON変換

/excel-to-json を実行すると、Excelのヘッダー検出からJSON出力までが一気に処理されます。

Excelファイルの内容は下記画像となります。

具体的なClaude Codeでの挙動は動画の通りです。
excel-to-json 実行デモ
/excel-to-json 実行の様子。Pythonスクリプトがヘッダーを検出し、レコード数を表示した後、botwork/MemberList.json に出力する。

JSON出力後、JSONファイルが botwork/ に格納されていることが確認できます。

JSONファイルの内容は下記となります。

MemberList.json
[
  {
    "ID": "D001",
    "Name in Japanese": "山田 太郎",
    "Location": "北海道"
  },
  {
    "ID": "D002",
    "Name in Japanese": "佐藤 花子",
    "Location": "東京都"
  },
  {
    "ID": "D003",
    "Name in Japanese": "鈴木 一郎",
    "Location": "愛知県"
  },
  {
    "ID": "D004",
    "Name in Japanese": "高橋 美咲",
    "Location": "大阪府"
  },
  {
    "ID": "D005",
    "Name in Japanese": "田中 健",
    "Location": "福岡県"
  }
]

SKILL.mdはシンプルですが、意図的な設計判断が含まれています。

---
name: excel-to-json
description: ローカルのExcelファイル(MemberList)をJSON形式に変換する
disable-model-invocation: true
---

ExcelファイルをJSON形式に変換する:

1. 以下のPythonスクリプトを実行する:
   python "./scripts/02_excel_to_json_python.py"

2. 変換完了を確認する
3. ファイルが `botwork` に保存されたことを報告する

解説:

ここで注目すべきは disable-model-invocation: true です。これは モデル起動を無効化 しています。つまり、Claudeが自動的にこのスキルを選ぶことはなく、必ずユーザーが /excel-to-json と明示的に入力する必要があります。

なぜ無効化するのでしょうか? Excel → JSON変換は 中間処理 であり、不用意に実行されると既存のJSONファイルを上書きしてしまう可能性があるためです。データ変換のような副作用のある処理は、ユーザーの明示的な意思で実行する方が安全です。

本文も非常にシンプルで、「スクリプトを実行→確認→報告」の3ステップです。変換ロジックはすべてPythonスクリプト側に委譲しており、SKILL.mdはオーケストレーション(指揮)に徹しています。

4-3. member-lookup:IDでメンバー情報を検索

/member-lookup は対話形式で動作します。ClaudeがIDと取得フィールドを質問し、回答に応じてコマンドを組み立てます。

member-lookup 実行デモ
/member-lookup の対話フロー。Claudeが「IDを入力してください」と質問し、ユーザーが D001 と回答。続けて取得フィールドを確認し、Pythonスクリプトを実行して結果を整形表示する。

このスキルは4つの中で最も構造化されたSKILL.mdを持っています。

---
name: member-lookup
description: IDを指定してメンバー情報を検索する。名前・チーム・CLなど各種情報を取得できる。
---

# Member Lookup Skill

IDを指定してMemberListからメンバー情報を取得する。

## 手順

### STEP 1: IDを確認する
ユーザーにIDを質問する:
  検索したいメンバーのIDを入力してください。
  例: D001

### STEP 2: 取得フィールドを確認する
ユーザーに取得内容を質問する:
  取得する情報を選んでください:
  1. 日本語名のみ(デフォルト)
  2. 基本情報(日本語名・拠点)

カスタム指定の場合は取得可能なフィールド一覧を提示してフィールド名を入力してもらう。

### STEP 3: コマンドを実行する

選択に応じて以下のコマンドを実行する:

1. 日本語名のみ:
   python "./scripts/03_member_lookup.py" --id {ID}

2. 基本情報:
   python "./scripts/03_member_lookup.py" --id {ID} --fields name_ja location

### STEP 4: 結果を伝える
- status: ok の場合: 取得した情報をわかりやすく整形して伝える
- status: not_found の場合: IDが見つからなかった旨を伝え、IDの入力ミスがないか確認を促す
- status: error の場合: エラー内容を伝える

## 取得できるフィールド一覧

| エイリアス     | Excelの列名          |
|------------|------------------|
| name_ja    | Name in Japanese |
| location   | Location         |

解説:

このスキルは他の2つと比べて大幅に構造化されています。

STEP形式の対話フロー: Claudeに「ユーザーと対話しながら処理を進めよ」という指示を与えています。STEP 1でIDを質問し、STEP 2で取得フィールドを確認し、STEP 3で適切なコマンドを組み立てて実行する。Claudeは、このSTEPに従って会話を進行します。

条件分岐: STEP 3では、ユーザーの選択に応じてコマンドの引数が変わります。「日本語名のみ」なら --id だけ、「基本情報」なら --id--fields name_ja location を付与します。Claudeはこの指示を解釈して、適切なコマンドを生成します。

結果のハンドリング: STEP 4では、Pythonスクリプトが返すJSON出力の status フィールドに応じた対応方法を記述しています。これにより、Claudeはエラー時にも適切なメッセージをユーザーに伝えられます。

フィールドマッピングテーブル: 末尾のテーブルは、ユーザーが「カスタム指定」を選んだ場合の参照情報です。Claudeはこのテーブルを見て、利用可能なフィールドをユーザーに提示できます。

このように、SKILL.mdは単なるコマンド実行指示だけでなく、対話設計(UXデザイン) まで含めることができます。

4-4. search-location:Playwrightでブラウザ操作

/search-location を実行すると、実際にChromiumブラウザが起動してWikipediaを検索します。

search-location 実行デモ
/search-location の実行デモ。Claudeが都道府県を質問し、ユーザーが「東京都」と回答。Chromiumが起動してWikipediaのメインページを開き、検索ボックスに「東京都」と入力してEnterキーを押す。ページ遷移後、概要文と目次を取得してブラウザを閉じる。

Playwrightによるブラウザ操作がローカルで動く点が、このスキルの最大の特徴です。

---
name: search-location
description: 都道府県名を指定してPlaywright(Chromium)でWikipediaを検索し、概要と目次を返す
---

# Search Location Skill

都道府県を指定してWikipediaを検索し、概要と目次を取得する。

## 手順

### STEP 1: 都道府県を確認する

ユーザーに調べたい都道府県を質問する:
  調べたい都道府県を入力してください。
  例: 東京都 / 大阪府 / 北海道

### STEP 2: Pythonスクリプトを実行する

  python scripts/04_search_location.py --location "{都道府県}"

### STEP 3: 結果を伝える

- status: ok の場合: タイトル・概要・目次セクションをわかりやすく整形して伝える
- status: error の場合: エラー内容を伝え、Playwrightのインストール状況を確認するよう促す

## 初回セットアップ(必要な場合)

Playwrightが未インストールの場合は以下を実行:
  pip install playwright
  python -m playwright install chromium

解説:

このスキルの特徴は、ブラウザ操作をAIエージェントから実行できる ことです。

従来、AIエージェントサービスでのブラウザ操作は大きな制約がありました。クラウド上のAIサービスから直接ブラウザを操作するのは、セキュリティや技術的な理由で困難です。しかし、Claude Code SkillsはWindows端末上でローカルにPlaywrightを実行するため、ブラウザ操作が自然に行えます

末尾の「初回セットアップ」セクションは、Claudeへのフォールバック指示です。Playwrightが未インストールの場合に備え、インストール手順を記述しておくことで、Claudeがエラー発生時に自らセットアップを提案できます。

4-5. 4つのSKILL.mdの設計パターンまとめ

スキル 起動方式 対話の有無 複雑度 設計意図
download-sharepoint モデル起動+ユーザー起動 なし シンプル 単純実行。自動選択を許可
excel-to-json ユーザー起動のみ なし シンプル 明示的実行を強制
member-lookup モデル起動+ユーザー起動 あり(STEP形式) 中程度 対話→条件分岐→結果ハンドリング
search-location モデル起動+ユーザー起動 あり(STEP形式) 中程度 ブラウザ操作+エラーフォールバック

5. スクリプト ― 実装の徹底解説

5-1. 01_downloadExcelFromSharepoint.ps1 ― SharePoint接続

# UTF-8 BOM付きで保存すること
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Import-Module PnP.PowerShell

# .envファイルの読み込み(ルート(/)からの実行を想定)
$envPath = Join-Path (Get-Location) ".env"
if (-not (Test-Path $envPath)) {
    Write-Error ".envファイルが見つかりません: $envPath"
    exit 1
}

foreach ($line in Get-Content $envPath -Encoding UTF8) {
    if ($line -match "^\s*$" -or $line -match "^\s*#") { continue }
    $key, $val = $line -split "=", 2
    Set-Variable -Name $key.Trim() -Value $val.Trim()
}

Write-Host "接続先: $SITE_URL"
Write-Host "ファイルURL: $FILE_URL"

Connect-PnPOnline -Url $SITE_URL -UseWebLogin

Get-PnPFile -Url $FILE_URL `
            -Path $LOCAL_PATH `
            -Filename $LOCAL_FILE `
            -AsFile `
            -Force

Write-Host "ダウンロード完了: $LOCAL_PATH$LOCAL_FILE"

ポイント解説:

文字エンコーディング対策:
冒頭の [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 は、Claude Code(WSL環境)での文字化けを防止するためのものです。Claude CodeはWSL上で動作するため、PowerShellの出力がUTF-8でエンコードされていないと日本語が文字化けします。

.envファイルのパース:
PowerShellにはPythonの dotenv のような標準ライブラリがないため、手動で .env をパースしています。空行とコメント行(#始まり)をスキップし、= で分割してキーと値を変数に格納します。-split "=", 22 は「最初の = でのみ分割する」という意味で、値の中に = が含まれるURL等にも対応しています。

SharePoint認証:
Connect-PnPOnline -UseWebLogin は、ブラウザベースの対話型認証を使います。実行時にブラウザの認証ダイアログが表示され、ユーザーがログインするとトークンが取得されます。社内のSSO(シングルサインオン)やMFA(多要素認証)にもそのまま対応できる点が利点です。

5-2. 02_excel_to_json_python.py ― Excel → JSON変換

import json
import sys
from pathlib import Path
import openpyxl

sys.stdout.reconfigure(encoding="utf-8")

def load_env(env_path: Path) -> dict:
    if not env_path.exists():
        raise FileNotFoundError(f".envファイルが見つかりません: {env_path}")
    env = {}
    for line in env_path.read_text(encoding="utf-8").splitlines():
        if not line.strip() or line.strip().startswith("#"):
            continue
        key, _, val = line.partition("=")
        env[key.strip()] = val.strip()
    return env

env = load_env(Path(".env"))

BASE_DIR    = Path(env["BASE_DIR"])
EXCEL_PATH  = BASE_DIR / env["EXCEL_FILE"]
OUTPUT_PATH = BASE_DIR / env["OUTPUT_FILE"]
SHEET_NAME  = env["SHEET_NAME"]
HEADER_ROW     = 2
DATA_START_ROW = 3

wb = openpyxl.load_workbook(EXCEL_PATH, data_only=True)
ws = wb[SHEET_NAME]

headers = []
for cell in ws[HEADER_ROW]:
    if cell.value is None:
        break
    headers.append(str(cell.value).strip())

print(f"検出されたヘッダー ({len(headers)} 列): {', '.join(headers)}")

records = []
for row in ws.iter_rows(min_row=DATA_START_ROW, max_col=len(headers), values_only=True):
    if all(v is None for v in row):
        break
    record = {headers[i]: (str(v).strip() if v is not None else "") for i, v in enumerate(row)}
    records.append(record)

print(f"{len(records)} 件のレコードを取得しました。")

with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
    json.dump(records, f, ensure_ascii=False, indent=2)

print(f"JSON 出力完了: {OUTPUT_PATH}")

ポイント解説:

ヘッダー行とデータ開始行の分離:
HEADER_ROW = 2 / DATA_START_ROW = 3 と定数で定義しています。業務用Excelでは1行目にタイトルや説明が入り、2行目がヘッダー、3行目以降がデータというレイアウトが多いためです。

動的ヘッダー検出:
ヘッダーは固定ではなく、2行目のセルを順番に読んで None(空セル)に達した時点で終了します。これにより、列が追加・削除されてもスクリプトを修正する必要がありません。

空行終端:
データ行も all(v is None for v in row) で全列が空の行を検出した時点で終了します。Excelの末尾に空行がある場合の安全対策です。

ensure_ascii=False
JSON出力時に日本語をエスケープせずそのまま出力するオプションです。これがないと日本語が \u3042 のようなUnicodeエスケープになり、デバッグ時に読みづらくなります。

5-3. 03_member_lookup.py ― メンバー検索

import json
import argparse
import sys
from pathlib import Path

sys.stdout.reconfigure(encoding="utf-8")

def load_env(env_path: Path) -> dict:
    if not env_path.exists():
        raise FileNotFoundError(f".envファイルが見つかりません: {env_path}")
    env = {}
    for line in env_path.read_text(encoding="utf-8").splitlines():
        if not line.strip() or line.strip().startswith("#"):
            continue
        key, _, val = line.partition("=")
        env[key.strip()] = val.strip()
    return env

env = load_env(Path(".env"))

BASE_DIR  = Path(env["BASE_DIR"])
JSON_PATH = BASE_DIR / env["OUTPUT_FILE"]

FIELD_MAP = {
    "name_ja":      "Name in Japanese",
    "location":     "Location",
    "id":           "ID",
}

DEFAULT_FIELDS = ["name_ja"]

def load_members(path: Path) -> list[dict]:
    with open(path, encoding="utf-8") as f:
        return json.load(f)

def find_member(members: list[dict], id: str) -> dict | None:
    return next((m for m in members if m.get("ID", "").lower() == id.lower()), None)

def build_response(member: dict | None, id: str, fields: list[str]) -> dict:
    if member is None:
        return {
            "status": "not_found",
            "id": id,
            "message": f"ID '{id}' が見つかりませんでした。",
            "data": {}
        }

    data = {}
    for alias in fields:
        col = FIELD_MAP.get(alias)
        if col:
            data[alias] = member.get(col, "")
        else:
            data[alias] = f"[unknown field: {alias}]"

    return {
        "status": "ok",
        "id": member.get("ID", id),
        "data": data
    }

def main():
    parser = argparse.ArgumentParser(description="MemberList ID ルックアップ")
    parser.add_argument("--id",    required=True, help="検索するID")
    parser.add_argument("--fields", nargs="*",     help=f"取得フィールド ({', '.join(FIELD_MAP.keys())})")
    args = parser.parse_args()

    fields = args.fields if args.fields else DEFAULT_FIELDS

    try:
        members = load_members(JSON_PATH)
    except FileNotFoundError:
        result = {
            "status": "error",
            "message": f"JSONファイルが見つかりません: {JSON_PATH}"
        }
        print(json.dumps(result, ensure_ascii=False, indent=2))
        sys.exit(1)

    member = find_member(members, args.id)
    result = build_response(member, args.id, fields)
    print(json.dumps(result, ensure_ascii=False, indent=2))

if __name__ == "__main__":
    main()

ポイント解説:

構造化されたJSON出力:
このスクリプトの最大の特徴は、出力が 構造化されたJSON であることです。Claude Codeはこの出力をパースして、SKILL.mdの指示に従って結果をユーザーに伝えます。

{
  "status": "ok",
  "id": "D001",
  "data": {
    "name_ja": "山田太郎"
  }
}

status フィールドが ok / not_found / error の3値を取ることで、Claudeは結果を正確に分岐できます。これは AIとスクリプト間のインターフェース設計 として重要なパターンです。

FIELD_MAP(エイリアスマッピング):
Excelのヘッダー名(Name in Japanese)をそのままCLI引数に使うとスペースの扱いが面倒になります。name_ja のようなエイリアスを定義し、内部でマッピングすることでCLIの使い勝手を向上させています。

大文字小文字を無視した検索:
m.get("ID", "").lower() == id.lower() により、D001 でも d001 でも検索できます。ユーザー入力のゆらぎへの対応です。

5-4. 04_search_location.py ― Playwrightによるブラウザ操作

import json
import argparse
import sys
from playwright.sync_api import sync_playwright

sys.stdout.reconfigure(encoding="utf-8")

def search_wikipedia(location: str) -> dict:
    url = f"https://ja.wikipedia.org/wiki/{location}"

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False, slow_mo=500)
        page = browser.new_page()

        # Wikipediaのメインページを表示
        page.goto("https://ja.wikipedia.org/wiki/メインページ")
        page.wait_for_load_state("domcontentloaded")

        # 検索ボックスのセレクタ(新旧UI両対応)
        search_selectors = [
            "input[name='search']",
            "input#searchInput",
            "input.cdx-text-input__input",
            "[placeholder*='検索']",
        ]
        search_box = None
        for selector in search_selectors:
            el = page.query_selector(selector)
            if el:
                search_box = selector
                break

        if not search_box:
            raise Exception("検索ボックスが見つかりませんでした")

        page.click(search_box)
        page.wait_for_timeout(500)
        page.fill(search_box, location)
        page.wait_for_timeout(500)
        page.keyboard.press("Enter")
        page.wait_for_load_state("domcontentloaded")

        title = page.title()

        paragraphs = page.query_selector_all("#mw-content-text .mw-parser-output > p")
        summary = ""
        for para in paragraphs:
            text = para.inner_text().strip()
            if len(text) > 50:
                summary = text
                break

        sections = []
        toc_selectors = [
            ".vector-toc-text",
            "#toc .toctext",
            ".mw-parser-output h2 .mw-headline",
        ]
        for selector in toc_selectors:
            items = page.query_selector_all(selector)
            if items:
                for item in items[:8]:
                    text = item.inner_text().strip()
                    import re
                    text = re.sub(r"^\d+(\.\d+)*\s*", "", text)
                    text = re.sub(r"\s+", " ", text).strip()
                    if text and text != "ページ先頭":
                        sections.append(text)
                break

        browser.close()

    return {
        "title": title,
        "url": url,
        "summary": summary,
        "sections": sections
    }

def main():
    parser = argparse.ArgumentParser(description="都道府県のWikipedia検索")
    parser.add_argument("--location", required=True, help="検索する都道府県(例: 東京都)")
    args = parser.parse_args()

    try:
        result = search_wikipedia(args.location)
        output = {
            "status": "ok",
            "location": args.location,
            **result
        }
    except Exception as e:
        output = {
            "status": "error",
            "message": str(e),
            "hint": "pip install playwright && python -m playwright install chromium"
        }

    print(json.dumps(output, ensure_ascii=False, indent=2))

if __name__ == "__main__":
    main()

ポイント解説:

headless=Falseslow_mo=500
デモ用途のため、ブラウザを画面に表示し(headless=False)、操作を500ms遅延させています(slow_mo=500)。これにより、Claudeがブラウザを操作している様子が目に見えます。本番運用では headless=True にしてslow_moを削除するのが一般的です。

複数セレクタによるフォールバック:
WikipediaのUIはバージョンによってDOM構造が異なります。検索ボックスの検出に4つのセレクタを用意し、順番に試すことで新旧UI両方に対応しています。目次の取得も同様に3パターンのセレクタを用意しています。

search_selectors = [
    "input[name='search']",      # 標準
    "input#searchInput",          # レガシー
    "input.cdx-text-input__input", # 新UI (Codex)
    "[placeholder*='検索']",       # 日本語プレースホルダー
]

このような「複数のセレクタを順に試す」パターンは、ブラウザ自動化において堅牢性を高める定番テクニックです。

エラー時のhintフィールド:
例外発生時に hint フィールドでインストールコマンドを返しています。Claudeはこのhintを見て、SKILL.mdの「初回セットアップ」セクションと組み合わせてユーザーに適切な対処法を提案できます。


6. スクリプト配置パターンの設計判断

6-1. 2つの配置パターン

Claude Code Skillsにおいて、スクリプトの配置には2つのアプローチがあります。

パターンA:スキルディレクトリ内に配置

.claude/skills/
└── download-sharepoint/
    ├── SKILL.md
    └── scripts/
        └── 01_downloadExcelFromSharepoint.ps1

スキルが自己完結するため、他のプロジェクトへの移植や配布がしやすくなります。Anthropic公式ドキュメントでも、scripts/references/assets/ をスキルディレクトリ内に配置する構成が基本形として紹介されています。

パターンB:プロジェクトルートに scripts/ を配置(本プロジェクトの構成)

.claude/skills/        # SKILL.md のみ
scripts/               # 実装スクリプトをまとめて管理

6-2. 本プロジェクトがパターンBを採用した理由

本プロジェクトのスクリプトは、番号順にパイプラインとして連携する設計です。

01_download → 02_excel_to_json → 03_member_lookup

02_excel_to_json が生成した MemberList.json03_member_lookup が参照するように、スクリプト間に依存関係があります。このような場合、スクリプトを一箇所にまとめて管理するパターンBの方が見通しがよく合理的です。

6-3. 使い分けの指針

状況 推奨パターン
スキルを独立して配布・再利用したい A(スキル内に配置)
スクリプトが複数スキル間で依存・共有している B(プロジェクトルートに配置)
パイプライン的な処理フローがある B(プロジェクトルートに配置)
社内のSkillsマーケットプレイスに公開したい A(スキル内に配置)

7. AIエージェントの「できること」が変わる

7-1. クラウドAIサービスの限界を突破する

従来のクラウドベースAIエージェントサービスには、構造的な制約がありました。

操作 クラウドAIサービス Claude Code Skills
SharePoint接続 API制限・認証の壁 ローカルでPnP.PowerShell実行。既存の認証フローをそのまま利用
ブラウザ操作 クラウド側の隔離環境で動作するため、社内サイトへのアクセスやログイン済みセッションの利用が困難 ローカルでPlaywright実行。社内ネットワーク上のサイトにもアクセス可能で、操作の様子も画面で確認できる
ファイルシステム操作 制限あり ローカルのファイルシステムを直接操作
社内ネットワーク アクセス不可 端末からVPN経由でアクセス可能

Claude Code Skillsは すべてがユーザーの端末上で動作する ため、クラウドAIの「外の世界にアクセスしにくい」という制約を根本的に解消します。

7-2. 応用の可能性

アプリケーション・インフラ保守での活用:
PowerShellやBashスクリプトをSkillsとして定義すれば、サーバーのログ収集、設定変更、デプロイメントなどの運用タスクをチャットベースで実行できます。Playwrightを使えば、管理画面のスクリーンショット取得やヘルスチェックも自動化できます。

非エンジニアの日常業務での活用:
Excelの定型レポート生成、ファイルのリネーム・整理、メール下書きの作成など、日常的な業務をSkillsとして定義できます。非エンジニアでも、自然言語で「今月のレポートを作って」と言うだけで、裏側のスクリプトが実行されます。

7-3. AIエージェント構築の民主化

ここに大きなパラダイムシフトがあります。

従来、AIエージェントの構築は エンジニアが工数をかけて実装するもの でした。API連携、エラーハンドリング、ワークフローの設計。すべてにプログラミングスキルが必要でした。

しかしSkillsの世界では、こうした流れが生まれつつあります。

1. 非エンジニアが「こういう作業を自動化したい」と発想する
2. Claude Code自身の力(バイブコーディング)で便利ツール(スクリプト)を作る
3. そのスクリプトをSKILL.mdとセットで配置する
4. 以降はチャットで呼び出すだけ

つまり、エンジニアがフレームワークを構築する世界 から、非エンジニアが自分の業務知識をSkillsとしてパッケージし、Agenticに呼び出す世界 への移行が始まっています。SKILL.mdは「プログラミング言語」ではなく「自然言語で書く業務マニュアル」です。プログラミングの知識がなくても、業務の手順を明確に記述できれば、それがそのままAIエージェントの能力になります。


8. まとめ

本記事では、Claude Code Skillsの設計思想から実装の細部まで、4つの具体的なスキルを通じて徹底的に解説しました。

Skillsの設計思想:
Progressive Disclosure(段階的開示)により、必要な情報を必要なときにだけ読み込む。これにより、多数のスキルをインストールしてもコンテキストウィンドウを圧迫しません。

SKILL.mdの書き方:
フロントマター(name / description)はClaudeの自動選択に使われ、本文はClaudeへの指示書になる。対話フロー、条件分岐、エラーハンドリングまで自然言語で記述できます。

スクリプトとの連携:
スクリプトの出力を構造化JSON(statusフィールド付き)にすることで、Claudeが結果を正確に解釈して適切な応答を生成できます。

端末実行の優位性:
SharePoint接続やブラウザ操作など、クラウドAIサービスでは困難だった操作が、ローカル実行により実現できます。

AIエージェントの能力を拡張するのに、大規模なフレームワークは必要ありません。SKILL.mdとスクリプトという、シンプルなファイルの組み合わせで十分です。あなたの業務知識をSKILL.mdに書き出すところから、始めてみてはいかがでしょうか。


この記事のソースコード: https://github.com/Masa1984a/claude-skills-demo

参考:

Accenture Japan (有志)

Discussion