0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

増えたカスタムエージェントを frontmatter から自動で一覧化する ― 週1回 launchd で INDEX.md を更新する

0
Posted at

「Claude Code環境」シリーズの続きです。前作 会話ログを自動で間引いて肥大化を防ぐ で launchd 定期ジョブの基本パターンを紹介しました。今回は同じ仕組みを使って ~/.claude/agents/ のカスタムエージェント一覧を自動で維持する 話です。

~/.claude/agents/ に .md を1ファイル置くとカスタムエージェントが増やせますが、気づくと「何個あって、それぞれ model は何で、どの tool を許可しているか」が把握できなくなります。現在 27 個ある私の環境でそれが起きました。手で INDEX.md を書いて管理しようとしたところ、当然数日で実態と乖離します。

困りごと:一覧が腐る

カスタムエージェントを追加するたびに INDEX.md を手で更新するのは無理です。

  • 追加したことを忘れて書き洩らす
  • model を後から変えても INDEX.md に反映しない
  • namedescription を typo しても気づかない

「自動で生成する」以外に持続可能な管理方法がないと判断して agents-index.sh を作りました。

生成物:INDEX.md の実例

現時点の ~/.claude/agents/INDEX.md の冒頭はこうなっています。

<!-- AUTO-GENERATED by ~/.claude/scripts/agents-index.sh — DO NOT EDIT MANUALLY -->
# Agents Index (27 agents · 2026-07-28 02:02)

| Name | Model | Description | Tools |
|------|-------|-------------|-------|
| `architect` ([architect.md](./architect.md)) | opus | Software architecture specialist ... | ["Read", "Grep", "Glob"] |
| `build-error-resolver` ([build-error-resolver.md](./build-error-resolver.md)) | sonnet | Build and TypeScript error resolution specialist ... | ["Read", "Write", "Edit", "Bash", "Grep", "Glob"] |
| `doc-updater` ([doc-updater.md](./doc-updater.md)) | haiku | Documentation and codemap specialist ... | ["Read", "Edit", "Bash", "Grep", "Glob"] |

Name・Model・Description・Tools の4列。model が opus / sonnet / haiku で色分けされていることが一目でわかり、「この agent は Bash を許可していたか」もすぐ確認できます。

--json オプション付きで実行すると同じ内容を .index.json にも書き出すので、cost-tracker など他のスクリプトから再利用できます。

スクリプトの設計

~/.claude/scripts/agents-index.sh は Bash の外殻に Python3 インラインスクリプトを埋め込む構成です。

#!/usr/bin/env bash
set -uo pipefail

AGENTS_DIR="$HOME/.claude/agents"
INDEX_MD="$AGENTS_DIR/INDEX.md"
INDEX_JSON="$AGENTS_DIR/.index.json"
LOGFILE="$HOME/.claude/logs/agents-index.log"
mkdir -p "$HOME/.claude/logs"

EMIT_JSON=0
[ "${1:-}" = "--json" ] && EMIT_JSON=1

python3 - "$AGENTS_DIR" "$INDEX_MD" "$INDEX_JSON" "$EMIT_JSON" "$LOGFILE" <<'PY'
# ... (Python本体)
PY

Python をヒアドキュメントで埋め込む理由は、シェルの PATH 問題を回避しながら文字列処理を Python に任せるためです。Bash 単体で YAML をパースするのは危険で、かといって pip install pyyaml を前提にすると環境依存が増えます。

PyYAML 非依存の frontmatter パーサ

エージェント定義は「1階層のキーバリュー」しか使わないため、正規表現と行分割で十分です。

def load_frontmatter(path):
    text = path.read_text(encoding="utf-8", errors="replace")
    m = re.match(r"^---\n(.*?)\n---\n", text, flags=re.DOTALL)
    if not m:
        return None, "missing frontmatter"
    body = m.group(1)
    out = {}
    for line in body.split("\n"):
        if not line.strip() or line.startswith("#"):
            continue
        if ":" not in line:
            continue
        k, _, v = line.partition(":")
        k = k.strip()
        v = v.strip()
        # quoted string 剥がす
        if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")):
            v = v[1:-1]
        out[k] = v
    return out, None

re.DOTALL--- 間を丸ごと取り、: で partition して最初の : より左をキーにします。tools のような JSON 配列値は文字列のまま扱い、表示時に 60 文字で打ち切ります。

欠落フィールドの警告

namedescription が空のエージェントは一覧に載っても役に立ちません。パース後に必須フィールドをチェックし、INDEX.md の末尾に警告セクションを追記します。

if not name:
    warnings.append(f"{p.name}: missing 'name'")
if not desc:
    warnings.append(f"{p.name}: missing 'description'")

生成結果の末尾はこうなります。

## ⚠️ Validation warnings
- foo-agent.md: missing 'description'

frontmatter がまるごと抜けているファイルは "missing frontmatter" として警告に回り、テーブルには載りません。エラーで止まらず警告として記録だけする設計にしたのは、INDEX.md の生成自体は成功させて「壊れているのはこのファイルだ」と分かる状態を維持するためです。

launchd 設定:日曜 6:30 に自動実行

~/Library/LaunchAgents/com.shun.agents-index.plist の StartCalendarInterval がこうなっています。

<key>StartCalendarInterval</key>
<dict>
    <key>Hour</key>
    <integer>6</integer>
    <key>Minute</key>
    <integer>30</integer>
    <key>Weekday</key>
    <integer>0</integer>
</dict>

Weekday: 0 が日曜。週明けに仕事を始める前に常に最新の一覧が手元にある、という設計です。

プロセス優先度は Nice: 10 + LowPriorityIO: true + ProcessType: Background の三重で下げています。INDEX 生成はディスク I/O を伴いますが作業の優先度を下げることで Claude Code 本体の応答に影響させません。

ロードは launchctl load で一度だけ。

launchctl load ~/Library/LaunchAgents/com.shun.agents-index.plist

手動で今すぐ走らせたい場合は直接叩きます。

~/.claude/scripts/agents-index.sh --json
# → agents=27 warnings=0 → ~/.claude/agents/INDEX.md

踏んだ落とし穴

  • INDEX.md 自身がループ対象になるif p.name == "INDEX.md": continue で除外が必要。入れ忘れると INDEX が INDEX を読んで壊れる
  • description の | でテーブルが崩れる.replace("|", "\\|") をパーサ後に必ず通す。実コードにも入れてあるが最初は忘れていた
  • tools が JSON 配列文字列で来るため 60 字制限を超えやすい["Read", "Write", "Edit", "Bash", "Grep", "Glob"] で既に 50 字近い。MCP ツールが混ざると即 overflow するので打ち切り + ... が必須
  • plist に StandardOutPath と ProgramArguments の >> リダイレクトを両方書くと二重ログになる → 実 plist ではどちらも同じファイルを向いているため実害はないが、整理するなら片方に統一すべき
  • mac がスリープ中だと StartCalendarInterval はスキップされる → launchd は目覚めてから実行するが、スリープのまま日曜を越えると月曜朝まで更新されない。週次更新で十分なので受け入れているが、厳密にしたいなら pmset -a wake 1 併用

まとめ

  • ~/.claude/agents/ が増えてくると手動の INDEX.md は必ず腐る → 生成を自動化する
  • PyYAML 非依存の1行パーサで frontmatter を読み、Markdown テーブルと .index.json を生成
  • 欠落フィールドは警告セクションとして INDEX.md 末尾に追記し、スクリプト自体は止めない
  • launchd の Weekday: 0 / Hour: 6 / Minute: 30 で日曜 6:30 に自動実行。Nice: 10 + LowPriorityIO で作業の邪魔をしない
  • 手動でも agents-index.sh --json 一発で即時更新できる

次回は、このエージェント一覧を使って「model ごとのコスト傾向を可視化する」仕組みの話を書く予定です。


Lily@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています

皆さんの ❤️ やシェアが励みになります!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?