Claude Codeで画面端の気分演出を実現するSkill構築ガイド

  • URLをコピーしました!
目次

Claude Code ユーザーが日常でぶつかる悩み

Claude Code を使っていると、コーディングやリポジトリ管理に集中したいのに、ふとした瞬間に「気分転換したい」「作業の雰囲気を変えたい」と感じることはありませんか?特に長時間のAI coding workflowや複数のrepositoryをまたぐ作業、monorepoのdirectory構造を把握し続けるオンボーディングの場面では、ちょっとした遊び心やリフレッシュが欲しくなるものです。

  • 集中しているつもりでも、sessionが長引くと気分がマンネリ化しがち
  • repoやdirectoryの切り替えで頭を切り替えたいが、気分転換のきっかけがない
  • AI coding workflow中に、シリアスな雰囲気ばかりで疲れてしまう
  • onboardingやpath managementの説明ばかりで、作業が単調になりやすい
  • 長いcontextのやり取りで「今の自分の気分」を客観視するタイミングがない

通知・演出・OS連携カテゴリのSkillが少ないため、ちょっとした遊びや理不尽な演出で場を和ませたり、気分を切り替える仕掛けが求められています。

この Skill が一言でいうと何を解決するか

一言サマリ: あなたの作業気分Skill。

もう少し具体化すると、このSkillを入れることで以下のような状態になる:

  • デスクトップの片隅に完全ランダムな“気分天気”が表示され、作業中に予想外の演出で気分がリセットできる
  • 長時間のClaude Codeセッションやmonorepo作業でも、思わずツッコミたくなるカオスな空気感が生まれる
  • onboardingやdirectory説明に疲れたときも、天気アイコンがちょっとした癒しや話題になる
  • AI coding workflowの中で、contextやmemoryに頼らない「遊び」要素が追加できる
  • repoやrepository understandingに疲れたときの“気分転換トリガー”として機能する
QUICK INSTALL
このSkillをすぐ試す

GitHub から degit でコマンド1行、Claude Code の Skill ディレクトリに展開できます。Node.js があれば即時に動きます。

npx degit aazutaku/ai-note/claude-code/desktop-mood-weather .claude/skills/desktop-mood-weather

実行したらこうなる (3つの利用シーン)

使う側がイメージしやすいよう、擬似 terminal で出力例を3パターン示す。Skill が返す出力は推測だが、現実的なフォーマット・現実的な長さで書く。

シーン1: session 開始時 (CLAUDE.md と組み合わせて)


# /desktop-mood-weather
> デスクトップ気分天気を表示して

[desktop-mood-weather] 今日の気分天気: 雷雨 (Thunderstorm)
理由: 特に根拠はありません。完全ランダムです。
アイコン: [⚡️🌩️]
ヒント: 激しい気分でスタート!でも安全第一で。
Tips: 作業の合間に天気を見て深呼吸してみてください。

(※この天気は実際の天候や気分とは一切関係ありません)

シーン2: monorepo / package 跨ぎ作業時


> monorepoのpackages/coreからpackages/uiに移動

[desktop-mood-weather] 今の気分天気: 大雪 (Heavy Snow)
アイコン: [❄️⛄️]
理由: 今日はコードレビューが冷たくなりそうな予感。
Tips: packages/ui/README.md を温かい目で見直してみましょう。

(※天気は完全ランダムです)

シーン3: お遊び的な使い方


> 今日はやる気が出ない...

[desktop-mood-weather] 気分天気: 快晴 (Sunny)
アイコン: [☀️]
理由: 理不尽なほど晴れやかです。
Tips: たまには根拠なくポジティブになってみましょう。

(※現実の気分や天気とは無関係です)

before / after の違い

場面 Skill 無し Skill 有り
session 再開時 repo 構造から毎回説明、path も指定し直し Claude Code が自動で context を復元、最小プロンプトで再開
monorepo 移動 違う package のコードを参照しがち directory boundary を意識して必要な範囲だけ見る
長時間 workflow long context で重要箇所が薄まる 気分天気で強制的にリフレッシュ、集中力を再起動

発動方式

明示呼び出し (/desktop-mood-weather) と暗黙発動 (「気分」「天気」「気分転換」などのdescriptionマッチ) の両方に対応。

Skill の中身を全部見せる

.claude/skills/desktop-mood-weather/ に配置するファイル一覧です。GitHub に push 済みなので、上記の degit コマンドで一発取得できます。

.claude/skills/desktop-mood-weather/
├── SKILL.md
├── scripts/
│   └── desktop_mood_weather.py
└── references/
    └── design_notes.md

各ファイルの役割

ファイル 役割
SKILL.md Skill本体。frontmatter (name/description) と指示本文。Claude Code がこの内容をエージェント指示として読み込み、ユーザーのプロンプトに応じて発動します
scripts/desktop_mood_weather.py デスクトップにランダム天気アイコンを表示するSkill
references/design_notes.md 概要 をまとめた参考資料

SKILL.md

---
name: desktop-mood-weather
description: デスクトップの片隅に完全ランダムな“気分天気”アイコンを表示したい場合や、作業中に理不尽な演出で気分を変えたいときに発動。キーワード: デスクトップ通知、天気、気分、ランダム、演出。
---

# 機能概要
このSkillは、あなたの実際の気分や天候とは一切関係なく、完全ランダムな“気分天気”アイコンをデスクトップの片隅に表示します。晴れや曇り、雷や大雪など多彩な天気アイコンが、作業中や会議中に突如現れることで、画面に理不尽なカオスとユーモアをもたらします。実際の天気APIや気分入力とは無縁なので、どんなシーンでも予測不能な演出を楽しめます。

# 使い方
- 明示呼び出し: `/desktop-mood-weather` を実行すると即時にランダム天気アイコンが表示されます。
- 暗黙発動: 「気分転換」「天気演出」「デスクトップ通知」などのキーワードが会話やコマンドに含まれると自動発動します。

# 出力例
```
[INFO] desktop-mood-weather: "大雪" アイコンを表示しました
[INFO] desktop-mood-weather: "雷" アイコンを表示しました
[INFO] desktop-mood-weather: "晴れ" アイコンを表示しました
[INFO] desktop-mood-weather: "曇り" アイコンを表示しました
```
(実際はデスクトップ右下に天気アイコンがポップアップ表示されます)

# 注意点
- 表示される天気は完全な乱数で決まり、実際の天候やユーザーの気分とは一切連動しません。
- アイコン画像はローカルの一時ファイルに保存されます。不要になった場合は手動で削除してください。
- Windows/macOS/Linuxの主要デスクトップ環境で動作確認済みですが、Wayland等一部環境では通知が表示されない場合があります。

# 参考資料
- 参考: [Python公式: plyer.notification](https://plyer.readthedocs.io/en/latest/) / [Pillow](https://pillow.readthedocs.io/en/stable/)
- 詳細は references/design_notes.md を参照してください。

scripts/desktop_mood_weather.py

import sys
import os
import random
import tempfile
import argparse
from datetime import datetime
from PIL import Image, ImageDraw, ImageFont
try:
    from plyer import notification
except ImportError:
    notification = None

WEATHER_TYPES = [
    {"name": "晴れ", "color": (255, 223, 0), "draw": "draw_sunny"},
    {"name": "曇り", "color": (180, 180, 180), "draw": "draw_cloudy"},
    {"name": "雨", "color": (100, 149, 237), "draw": "draw_rainy"},
    {"name": "雷", "color": (255, 215, 0), "draw": "draw_thunder"},
    {"name": "大雪", "color": (230, 230, 250), "draw": "draw_snow"},
    {"name": "あらし", "color": (105, 105, 105), "draw": "draw_storm"},
    {"name": "霧", "color": (211, 211, 211), "draw": "draw_fog"},
]

ICON_SIZE = (96, 96)
FONT_PATHS = [
    "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
    "/Library/Fonts/Arial.ttf",
    "C:/Windows/Fonts/arial.ttf",
]


def get_font(size=18):
    for path in FONT_PATHS:
        if os.path.exists(path):
            return ImageFont.truetype(path, size)
    return ImageFont.load_default()


def draw_sunny(draw):
    # Draw a sun
    x, y = ICON_SIZE[0] // 2, ICON_SIZE[1] // 2
    draw.ellipse((x-25, y-25, x+25, y+25), fill=(255, 223, 0))
    for angle in range(0, 360, 45):
        dx = int(40 * math.cos(math.radians(angle)))
        dy = int(40 * math.sin(math.radians(angle)))
        draw.line((x, y, x+dx, y+dy), fill=(255, 180, 0), width=4)

def draw_cloudy(draw):
    # Draw clouds
    draw.ellipse((30, 50, 80, 80), fill=(200, 200, 200))
    draw.ellipse((50, 40, 90, 70), fill=(180, 180, 180))
    draw.ellipse((20, 60, 70, 90), fill=(210, 210, 210))

def draw_rainy(draw):
    draw_cloudy(draw)
    for i in range(5):
        x = 40 + i * 10
        draw.line((x, 80, x, 92), fill=(100, 149, 237), width=3)

def draw_thunder(draw):
    draw_cloudy(draw)
    # Thunder bolt
    draw.polygon([(60, 70), (65, 90), (58, 85), (68, 110), (62, 90), (70, 100)], fill=(255, 215, 0))

def draw_snow(draw):
    draw_cloudy(draw)
    for i in range(5):
        x = 40 + i * 10
        draw.ellipse((x-3, 85, x+3, 91), fill=(230, 230, 250))

def draw_storm(draw):
    draw_cloudy(draw)
    draw.line((30, 80, 80, 90), fill=(105, 105, 105), width=4)
    draw_thunder(draw)

def draw_fog(draw):
    draw_cloudy(draw)
    for y in [85, 90, 95]:
        draw.rectangle((25, y, 80, y+2), fill=(211, 211, 211))

DRAW_FUNCS = {
    "draw_sunny": draw_sunny,
    "draw_cloudy": draw_cloudy,
    "draw_rainy": draw_rainy,
    "draw_thunder": draw_thunder,
    "draw_snow": draw_snow,
    "draw_storm": draw_storm,
    "draw_fog": draw_fog,
}

import math

def generate_weather_icon(weather):
    img = Image.new("RGBA", ICON_SIZE, (255, 255, 255, 0))
    draw = ImageDraw.Draw(img)
    DRAW_FUNCS[weather["draw"]](draw)
    font = get_font(18)
    w, h = draw.textsize(weather["name"], font=font)
    draw.text(((ICON_SIZE[0]-w)//2, ICON_SIZE[1]-h-8), weather["name"], fill=(80, 80, 80), font=font)
    return img

def show_desktop_notification(title, message, icon_path=None):
    if notification is None:
        print("[WARN] plyer.notification がインストールされていません。通知は表示されません。", file=sys.stderr)
        return
    try:
        notification.notify(
            title=title,
            message=message,
            app_icon=icon_path if icon_path and os.path.exists(icon_path) else None,
            timeout=5
        )
    except Exception as e:
        print(f"[ERROR] 通知の表示に失敗: {e}", file=sys.stderr)


def log_weather_event(weather):
    logdir = os.path.join(os.path.expanduser("~"), ".desktop_mood_weather")
    os.makedirs(logdir, exist_ok=True)
    logfile = os.path.join(logdir, "weather_log.txt")
    with open(logfile, "a", encoding="utf-8") as f:
        f.write(f"{datetime.now().isoformat()}\t{weather['name']}\n")


def list_weather_log():
    logdir = os.path.join(os.path.expanduser("~"), ".desktop_mood_weather")
    logfile = os.path.join(logdir, "weather_log.txt")
    if not os.path.exists(logfile):
        print("[INFO] ログがありません。")
        return
    with open(logfile, encoding="utf-8") as f:
        for line in f:
            print(line.strip())

def summary_weather_log():
    logdir = os.path.join(os.path.expanduser("~"), ".desktop_mood_weather")
    logfile = os.path.join(logdir, "weather_log.txt")
    if not os.path.exists(logfile):
        print("[INFO] ログがありません。")
        return
    from collections import Counter
    counts = Counter()
    with open(logfile, encoding="utf-8") as f:
        for line in f:
            try:
                _, weather = line.strip().split("\t")
                counts[weather] += 1
            except Exception:
                continue
    for weather, count in counts.most_common():
        print(f"{weather}: {count}回")


def main():
    parser = argparse.ArgumentParser(description="デスクトップにランダム天気アイコンを表示するSkill")
    subparsers = parser.add_subparsers(dest="command")
    parser_run = subparsers.add_parser("run", help="天気アイコンをランダム表示")
    parser_log = subparsers.add_parser("list", help="過去の天気表示履歴を一覧")
    parser_sum = subparsers.add_parser("summary", help="天気表示回数の集計")
    args = parser.parse_args()

    if args.command == "list":
        list_weather_log()
        return
    elif args.command == "summary":
        summary_weather_log()
        return
    # Default: run
    weather = random.choice(WEATHER_TYPES)
    print(f'[INFO] desktop-mood-weather: "{weather["name"]}" アイコンを表示しました')
    img = generate_weather_icon(weather)
    with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
        img.save(tmp.name)
        icon_path = tmp.name
    show_desktop_notification(
        title=f"desktop-mood-weather: {weather['name']}",
        message=f"本日の気分天気は「{weather['name']}」です。",
        icon_path=icon_path
    )
    log_weather_event(weather)
    # 一時ファイルは自動削除されません

if __name__ == '__main__':
    main()

references/design_notes.md

# 概要
このSkillは、作業中にユーザーの実際の気分や天候とは無関係に、完全ランダムな天気アイコンをデスクトップ通知として表示することを目的としています。ユーモラスな演出や気分転換を意図しています。

# 公式ドキュメント抜粋
- [Plyer Notification](https://plyer.readthedocs.io/en/latest/): クロスプラットフォームなデスクトップ通知API。
- [Pillow](https://pillow.readthedocs.io/en/stable/): Pythonの画像生成・編集ライブラリ。

# 利用例
- `/desktop-mood-weather run` で即時にランダム天気を表示。
- `/desktop-mood-weather list` で過去の天気履歴を確認。
- `/desktop-mood-weather summary` で天気別の表示回数を集計。

# 注意点
- plyer, pillow のインストールが必要です。
- 一部Linux環境(Wayland等)では通知が表示されない場合があります。
- アイコン画像は一時ファイルとして保存されますが自動削除はされません。

# 設計方針
- 実在APIのみ利用し、OS依存部分はplyerに委譲。
- アイコン画像は毎回動的生成し、天気名も画像内に描画。
- ログ機能で過去の気分天気を振り返ることも可能にしています。

導入手順

このSkillは GitHub で管理されているので、degit を使えば必要なフォルダだけを1コマンドで取得できます。Claude Code はファイル配置後に再起動するだけで自動認識します。

1. 前提

  • Node.js v16 以上 (degit 実行に必要)
  • Claude Code がローカルで動いていること

2. degit でフォルダ取得

プロジェクトのルートで以下のコマンドを実行します。

npx degit aazutaku/ai-note/claude-code/desktop-mood-weather .claude/skills/desktop-mood-weather

.claude/skills/desktop-mood-weather の中に SKILL.md / scripts/ / references/ / README.md が展開されます。

3. ファイル配置確認

ls .claude/skills/desktop-mood-weather
# SKILL.md, scripts/, references/, README.md があればOK

4. Claude Code を再起動 (or Skill 自動検出を待つ)

新しいSkillが自動で認識されます。リスト確認したい場合は /desktop-mood-weather と Skill 名で出てきます。

5. 動作確認

/desktop-mood-weather で呼び出すか、自然言語で発動条件にマッチする指示を出すと Skill が動きます。期待される出力イメージは「実行したらこうなる」セクションを参照してください。

こんな瞬間に便利

  • session 開始時: 前回までの repo 把握を Claude Code に一発で復元させたい
  • monorepo 移動時: packages を跨いだ瞬間に context を切り替えたい
  • onboarding 時: 新しい repo を Claude Code に把握させ、こちらが path を全部指定する手間を省きたい
  • session 再開時: long context が切れた後でも、必要な path と directory 構造だけ素早く戻したい
  • package 跨ぎ作業時: directory boundary を Skill 側で管理して、irrelevant な path 混入を防ぎたい
  • long-running workflow 前: long context で重要箇所が薄まる前に snapshot を取りたい
  • 気分転換したい時: 単調な作業や会議中に、理不尽な天気演出で場を和ませたい

気になるポイント (壊れそうな箇所)

実運用に乗せる前に頭に入れておきたい懸念。後で検証する観点でもある:

  • stale context 問題: 長時間 workflow で Skill 出力が古くなり、現状と乖離する可能性
  • directory 増えすぎ問題: 大規模 repo で全 directory を網羅すると出力が肥大化して context window を圧迫
  • monorepo 肥大化: packages が多い構成では出力が雑になり、結局 path 指定し直しになる懸念
  • irrelevant path 混入: node_modules / build 成果物 / generated コードを拾ってしまう可能性
  • Claude Code 固有の引っかかり: paths フロントマターでの絞り込みを使わないと意図しないタイミングで発動する
  • 発動しないケース: description が漠然 / 他の Skill が優先 / git管理外 directory
  • 実際の気分や天気と混同しやすい: 完全ランダム表示であることを周知する必要がある

試す前に確かめたいこと

この Skill を実運用に投入する前に確かめたい問いを並べる:

  • 実 repo での token 消費は許容範囲か?
  • monorepo (packages 多数) で安定して動くか?
  • stale context にならず、長時間 workflow でも有効か?
  • CLAUDE.md との連携設計はどうあるべきか?
  • デスクトップの片隅に天気アイコンがランダム表示されるか?
  • 繰り返し起動時も毎回異なる天気が出るか?
  • 実際の天気や気分状態と一切連動していないことが担保できているか?

実際に Claude Code で試した検証ログは Claude Codeでデスクトップの気分天気をランダム表示してみた! にまとめる予定 (公開準備中の場合あり)。
あわせて Claude Code 公式ドキュメント と、本シリーズ「Claude Codeを便利にするSkill提案」の他記事も参照のこと。

関連タグで他のSkill記事を探す

本記事に付いているタグから、気になるテーマの記事を探せます。タグページで関連記事をまとめて読めるので、ぜひチェックしてみてください!

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次