Agent Skills対応デスクトップマスコットを作ったので紹介だけします。かわいいので
かわいい

これはなに
伺かとかDesktop Mate™みたいなやつです。
2026年はAI秘書元年だというのをどっかの国会議員[1]が言っていたので、じゃあデスクトップマスコットをAI秘書にしようということで作りました。
つくりかた
Pythonのパッケージマネージャーuvと、PythonについてくるGUIライブラリTkinter、Tkinterで画像を表示するための画像処理ライブラリPillow、 .envファイルから環境変数を読み込むライブラリpython-dotenv、そして自作AIエージェントライブラリMagicaを使います。
まずStable DiffusionライブラリDiffusersのような画像生成AIでうちの子を生成して、Nano Banana Proでデスクトップマスコットっぽいポーズのグリーンバック画像に加工して、やる気を出します。

このままだと使えないので、背景削除ライブラリRembgや、ググって出てきたPhotoroomの背景削除ツールを使ったりしてグリーンバックを透過PNGにしたり、Tkinterの-transparentcolorオプションでちゃんと透過できるように色を変えたり縁取りしたりします。
ちなみに今回ここまでの作業が一番大変でした。
その後main.pyファイルを用意して、Magicaをインポートされたライブラリとして使用するためにインストールします。
uv run python -c "open('main.py','a').close()"
uv add --script=main.py git+https://gitlab.com/tkithrta/magica.git
ただ私のWindows環境にはGitがインストールされていないので、tar.gzファイルを展開してライブラリとしてインポートすることにします。
curl -fsSLO https://gitlab.com/tkithrta/magica/-/archive/main/magica-main.tar.gz
tar -xzf magica-main.tar.gz
uv run python -c "open('main.py','a').close()"
uv add --editable --script=main.py ./magica-main
あとはコーディングエージェントでバイブコーディングをするだけです。
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "magica",
# "pillow==12.1.0",
# "python-dotenv==1.2.1",
# ]
#
# [tool.uv.sources]
# magica = { path = "magica-main", editable = true }
# ///
import io
import os
import sys
import threading
import tkinter as tk
from contextlib import redirect_stderr, redirect_stdout
from tkinter import scrolledtext
from dotenv import load_dotenv
from magica import Agent
from PIL import Image, ImageTk
load_dotenv()
class MagicaMascot(tk.Tk):
def __init__(self):
super().__init__()
skills_dir = os.path.expanduser(os.getenv("MAGICA_SKILLS_DIR", "~/.magica/skills"))
self.agent = Agent(skills_dir)
self.overrideredirect(True)
if sys.platform == "win32":
self.attributes("-topmost", True, "-transparentcolor", "#000000")
else:
self.attributes("-topmost", True)
self.config(bg="#000000")
image_path = os.path.join(os.path.dirname(__file__), "mascot.png")
if os.path.exists(image_path):
mascot_image = Image.open(image_path).convert("RGBA")
else:
mascot_image = Image.new("RGBA", (32, 32), (0, 0, 0, 255))
self.photo = ImageTk.PhotoImage(mascot_image)
label = tk.Label(self, image=self.photo, bg="#000000", bd=0)
label.pack()
label.bind("<Button-1>", self._click_start)
label.bind("<B1-Motion>", self._drag_move)
self.panel = tk.Toplevel(self)
self.panel.overrideredirect(True)
self.panel.attributes("-topmost", True)
self.panel.config(bg="#333333", padx=10, pady=5)
self.panel.protocol("WM_DELETE_WINDOW", self.destroy)
self.header = tk.Frame(self.panel, bg="#333333")
self.header.pack(fill=tk.X)
self.header.bind("<Button-1>", self._click_start)
self.header.bind("<B1-Motion>", self._drag_move)
button_close = tk.Button(
self.header,
text="x",
command=self.destroy,
bg="#333333",
fg="#CCCCCC",
bd=0,
font=("Arial", 10, "bold"),
cursor="hand2",
)
button_close.pack(anchor="e")
button_close.bind("<Enter>", lambda e: button_close.config(bg="#CC0000"))
button_close.bind("<Leave>", lambda e: button_close.config(bg="#333333"))
self.message_area = scrolledtext.ScrolledText(
self.panel,
width=40,
height=12,
bg="#000000",
fg="#00FF00",
bd=0,
font=("Arial", 10, "bold"),
wrap=tk.WORD,
)
self.message_area.pack(fill=tk.BOTH, expand=True)
self.message_area.config(state=tk.DISABLED)
self.entry = tk.Entry(
self.panel,
bg="#333333",
fg="#FFFFFF",
insertbackground="#FFFFFF",
bd=0,
font=("Arial", 12),
)
self.entry.pack(fill=tk.X, pady=10)
self.entry.bind("<Return>", lambda e: self.process_task())
self.geometry("+1200+300")
self.panel.geometry("+1200+300")
self.update_idletasks()
self.panel.lift()
self.entry.focus_set()
def _click_start(self, e):
self.offset_x = e.x_root - self.winfo_x()
self.offset_y = e.y_root - self.winfo_y()
self.panel.lift()
self.entry.focus_set()
def _drag_move(self, e):
nx = e.x_root - self.offset_x
ny = e.y_root - self.offset_y
self.geometry(f"+{nx}+{ny}")
self.panel.geometry(f"+{nx}+{ny}")
def update_board(self, content):
self.message_area.config(state=tk.NORMAL)
self.message_area.delete(1.0, tk.END)
self.message_area.insert(tk.END, content)
self.message_area.see(tk.END)
self.message_area.config(state=tk.DISABLED)
def process_task(self):
prompt = self.entry.get().strip()
if not prompt or self.entry["state"] == tk.DISABLED:
return
self.entry.config(state=tk.DISABLED)
self.update_board(f"> {prompt}\nThinking...")
t = threading.Thread(target=self.execute_agent, args=(prompt,), daemon=True)
t.start()
def execute_agent(self, prompt):
output_buffer = io.StringIO()
try:
with redirect_stdout(output_buffer), redirect_stderr(output_buffer):
self.agent.run(prompt)
output = output_buffer.getvalue() or "Done."
except Exception as e:
output = f"Error: {e}"
self.after(0, self.finalize, f"> {prompt}\n{output}")
def finalize(self, content):
self.update_board(content)
self.entry.config(state=tk.NORMAL)
self.entry.delete(0, tk.END)
self.entry.focus_set()
if __name__ == "__main__":
app = MagicaMascot()
app.mainloop()
uv addコマンドが不要になるGit用インラインスクリプトメタデータ
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "magica",
# "pillow==12.1.0",
# "python-dotenv==1.2.1",
# ]
#
# [tool.uv.sources]
# magica = { git = "https://gitlab.com/tkithrta/magica.git" }
# ///
最後にmain.pyファイルがあるディレクトリにmascot.pngファイルと.envファイルを置いてMagicaの設定を行い、スキルを用意して起動します。
uv run main.py
uvwコマンドを使えばWindowsターミナルを閉じても動き続けます。
uvw run main.py
Tkinterの-transparentcolorオプションがWindows限定なのでいい感じに透過されませんが、一応Linuxでも動くみたいです(noVNCのFluxboxで確認)。

いいね
伺かは実験的にMCP対応しているみたいですが、Agent Skillsに対応したデスクトップマスコットを作ればMCPを使わずにスキルを作って簡単に機能を追加できるのでおすすめです。
Discussion