watch
Lets Claude watch a video from a URL or local file by pulling timestamped frames and a transcript. Works in Claude Code, and the watch skill downloads with yt-dlp and extracts frames with ffmpeg.
What mojiemoji-github runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Claude Code
> /plugin marketplace add jozobeer/mojiemoji-plugin> /plugin install mojiemoji-github@mojiemoji-plugin
Cursor
Cursor also runs Claude Code hooks from .claude/settings.json, so copy these hooks there to use them in Cursor.
What fires, and when
PreToolUse
Bash|mcp__.*github.*"${CLAUDE_PLUGIN_ROOT}/hooks/mojiemoji_japanese_gate.py"PostToolUse
Edit|Write|MultiEdit"${CLAUDE_PLUGIN_ROOT}/hooks/mojiemoji_md_edit_warn.py"Where it lives
#!/usr/bin/env python3
"""PreToolUse hook: gate Japanese (and opt-in English/Latin #148) GitHub body submissions without properly-styled mojiemoji stamps.
Fires on two posting paths:
1. Bash tool with `gh` posting the body:
- `gh (issue|pr|release) (create|comment|review)` (high-level), OR
- `gh api .../reviews|comments|issues|releases ...` (raw REST POST,
used by skills like cross-repo-review that batch-publish reviews).
2. MCP GitHub tools whose `tool_input` carries a Japanese `body`
field, including nested review `comments[].body` fields. The MCP
matcher uses both server-alias signals (anything
with `github` in the namespace) AND known GitHub-specific tool
name patterns (`*pull_request*`, `*issue_write`, `add_issue_comment`,
`*release*`, etc.) so installations that aliased the GitHub MCP
server to a non-`github` name are still covered. Title /
commit_message / file content / description are intentionally NOT
inspected — only the `body` posting-prose field is inspected, and
each body value must be decorated on its own.
And EITHER:
1. inspected text has zero `mojiemoji.jozo.beer` URLs, OR
2. at least one mojiemoji URL is missing any of the required style
parameters (`background=transparent`, `font=*`, `color=*`,
`animation=*`, `outline=darker`, `outline_width=2`), OR
3. a URL uses a non-canonical font/animation, an invalid outline
value, pairs a color-shifting animation with an outline, uses a
Tailwind 600+ color (invisible on dark mode), or contains a
3-kanji single-stamp text (must split as 2+1).
The Bash path also reads referenced body files (`--body-file PATH` /
`--input PATH` / `-F body=@PATH`) and interpreter-invoked scripts so
file-routed / dynamically-built bodies are covered too.
When triggered, blocks the tool call (exit 2) and prints reminder to
stderr so Claude sees it before submission. Bypass: include
`MOJIEMOJI_HOOK_DISABLED=1` anywhere in the inspected text — for Bash
that's the command line (prefix idiom matches the git pre-commit hook),
for MCP that's the body itself.
Implementation lives in `hooks/gate/` — this script is the thin entry
pipeline that wires the routing / validator modules together. See
https://github.com/jozobeer/mojiemoji-plugin/issues/101.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
# `hooks/gate/__init__.py` already splices the skill scripts dir onto
# sys.path, but Claude Code invokes this file as `python3 hooks/...`
# rather than as a package member, so `gate` itself isn't importable
# until `hooks/` is on the path. Splice both — hooks/ for `import gate`,
# scripts/ for any direct `from lib.X import Y` consumers downstream.
_HOOKS_DIR = Path(__file__).resolve().parent
if str(_HOOKS_DIR) not in sys.path:
sys.path.insert(0, str(_HOOKS_DIR))
from gate.extract import JP_RE, LATIN_RE, MOJI_URL_RE, extract_inspect_text # noqa: E402
from gate.extract import forces_pr_body, is_pr_body_submission, pr_body_target_repo # noqa: E402
from lib.plugin_root import plugin_root # noqa: E402
from gate.validators import ( # noqa: E402
PIPELINE,
validate_catalog_leftovers,
validate_schema_version,
)
from lib.repo_policy import POLICY_LEAKS, POLICY_UNKNOWN, repo_policy_state # noqa: E402
_PR_BODY_LEAK_REMINDER = (
"🚫 このリポジトリは PR body を commit message にコピーする設定です\n"
"\n"
"検出: squash / merge commit message が `PR_BODY` のリポジトリに、mojiemoji\n"
"stamp を含む PR body を投稿しようとしています。GitHub が PR body を squash /\n"
"merge commit に転記するため、`<img src=\"https://mojiemoji.jozo.beer/...\">`\n"
"の HTML が commit 履歴に恒久的に残ってしまいます (issue #138)。\n"
"\n"
"## 対処\n"
"1. PR body から mojiemoji stamp を外して投稿する (推奨。本文の装飾は不要)\n"
"2. どうしても装飾したいなら、投稿経路に応じて FORCE を明示する:\n"
" - Bash: command 先頭に `MOJIEMOJI_FORCE_PR_BODY=1` を付ける\n"
" - MCP: body 内に `MOJIEMOJI_FORCE_PR_BODY=1` を含める\n"
"\n"
"issue / review / comment など他の surface は通常どおり装飾して構いません。\n"
f"詳細: {plugin_root()}/skills/mojiemoji-github/SKILL.md\n"
)
def main() -> int:
try:
data = json.load(sys.stdin)
except Exception:
return 0
inspect_texts = extract_inspect_text(data)
if inspect_texts is None:
return 0
jp_texts = [text for text in inspect_texts if JP_RE.search(text)]
en_texts = [text for text in inspect_texts if LATIN_RE.search(text)]
# English/Latin opt-in support (#148). Pure English bodies are NOT gated
# by default (to avoid forcing stamps on English-first users).
# Opt in for a submission by including the marker:
# MOJIEMOJI_ENGLISH_GATE=1 (in Bash command or inside the body text)
english_enabled = any(
"MOJIEMOJI_ENGLISH_GATE=1" in (t or "")
for t in (inspect_texts or [])
) or "MOJIEMOJI_ENGLISH_GATE=1" in (
(data.get("tool_input", {}) or {}).get("command", "")
)
if not jp_texts and not (english_enabled and en_texts):
return 0
# Determine which bodies to enforce stamping rules on. Japanese and
# opt-in English bodies are both active when a payload mixes them.
active = [
text
for text in inspect_texts
if JP_RE.search(text) or (english_enabled and LATIN_RE.search(text))
]
cwd = data.get("cwd", "")
if is_pr_body_submission(data) and not forces_pr_body(data):
owner, repo = pr_body_target_repo(data) or (None, None)
state = repo_policy_state(
owner=owner,
repo=repo,
cwd=Path(cwd) if cwd else None,
)
has_stamp = any(MOJI_URL_RE.search(text) for text in active)
if not has_stamp and state in (POLICY_LEAKS, POLICY_UNKNOWN):
return 0
if has_stamp and state == POLICY_LEAKS:
sys.stderr.write(_PR_BODY_LEAK_REMINDER)
return 2
for inspect_text in active:
urls = MOJI_URL_RE.findall(inspect_text)
for stage in PIPELINE:
rc #!/usr/bin/env python3
"""PostToolUse hook: warn (not block) when an Edit/Write/MultiEdit on a
documentation `*.md` file leaves prestamp drift.
Local file edits never flow through the gh / MCP gate, so README /
docs / SKILL.md / agents prompts can silently drift away from the
catalog without anyone noticing — exactly the dogfood-followup problem
the project keeps re-discovering (#91). This hook reads the file after
the edit lands, runs `prestamp.py` over it in a subprocess, and if the
output differs from the current contents, emits a unified diff to
stderr with the suggested transform.
It never blocks — the goal is awareness, not enforcement. The CI
drift check (#91 / catalog-drift-check sibling) is the hard gate.
Matched file paths (anything else is silently ignored):
- any `*.md` under the repo root, restricted to:
- `README.md`
- `docs/**/*.md`
- `agents/**/*.md`
- `skills/**/SKILL.md`
- `CHANGELOG.md`
Authors who want a clean before/after region can wrap it with
`<!-- mojiemoji:off -->` / `<!-- mojiemoji:on -->` — prestamp respects
the markers, so this hook will be silent for those segments.
Exit code is always 0. Output on stderr only fires when there is
something the author should look at.
"""
import json
import os
import re
import subprocess
import sys
from pathlib import Path
SKILL_MD_SUFFIX = "SKILL.md"
JP_RE = re.compile(r"[\u3040-\u309f\u30a0-\u30ff\u4e00-\u9fff]")
# Also consider Latin for English/i18n drift warnings (#148)
LATIN_RE = re.compile(r"[A-Za-z][A-Za-z'-]{2,}")
def _is_documentation_md(path: Path, repo_root: Path) -> bool:
try:
rel = path.resolve().relative_to(repo_root.resolve())
except (ValueError, OSError):
return False
rel_str = str(rel).replace(os.sep, "/")
if rel_str == "README.md" or rel_str == "CHANGELOG.md":
return True
if rel_str.startswith("docs/") and rel_str.endswith(".md"):
return True
if rel_str.startswith("agents/") and rel_str.endswith(".md"):
return True
if rel_str.startswith("skills/") and rel_str.endswith(SKILL_MD_SUFFIX):
return True
return False
def _has_japanese(text: str) -> bool:
return JP_RE.search(text) is not None
def _has_latin(text: str) -> bool:
return LATIN_RE.search(text) is not None
def _find_repo_root(start: Path) -> Path | None:
cur = start.resolve()
for parent in (cur, *cur.parents):
if (parent / ".git").exists() or (parent / ".claude-plugin").exists():
return parent
return None
def _file_path_from_payload(payload: dict) -> Path | None:
tool_input = payload.get("tool_input") or {}
raw = tool_input.get("file_path")
if isinstance(raw, str) and raw:
return Path(raw)
return None
def _run_prestamp(text: str, plugin_root: Path) -> tuple[str, int]:
script = plugin_root / "skills" / "mojiemoji-github" / "scripts" / "prestamp.py"
if not script.exists():
return "", -1
proc = subprocess.run(
[sys.executable, str(script)],
input=text,
capture_output=True,
text=True,
timeout=15,
)
return proc.stdout, proc.returncode
def _unified_diff(original: str, transformed: str, label: str) -> str:
import difflib
diff_lines = difflib.unified_diff(
original.splitlines(keepends=True),
transformed.splitlines(keepends=True),
fromfile=f"a/{label}",
tofile=f"b/{label}",
n=2,
)
return "".join(diff_lines)
def main() -> int:
try:
payload = json.loads(sys.stdin.read() or "{}")
except json.JSONDecodeError:
return 0
tool_name = payload.get("tool_name") or ""
if tool_name not in {"Edit", "Write", "MultiEdit"}:
return 0
file_path = _file_path_from_payload(payload)
if file_path is None:
return 0
if file_path.suffix != ".md":
return 0
plugin_root_env = os.environ.get("CLAUDE_PLUGIN_ROOT")
plugin_root = Path(plugin_root_env) if plugin_root_env else None
repo_root = _find_repo_root(file_path.parent) or plugin_root
if repo_root is None:
return 0
if not _is_documentation_md(file_path, repo_root):
return 0
if not file_path.exists():
return 0
try:
current = file_path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
return 0
if not (_has_japanese(current) or _has_latin(current)):
return 0
prestamp_root = plugin_root or repo_root
transformed, rc = _run_prestamp(current, prestamp_root)
if rc != 0:
return 0
if transformed == current:
return 0
try:
rel_label = str(file_path.resolve().relative_to(repo_root.resolve()))
except (ValueError, OSError):
rel_label = file_path.name
diff = _unified_diff(current, transformed, rel_label)
if not diff.strip():
return 0
sys.stderr.write(
"📎 mojiemoji prestamp drift detected on doc edit "
f"({rel_label}). The catalog would transform this further "
"— run `prestamp.py` on the file if you want to apply, or "
"wrap the affected region in `<!-- mojiemoji:off -->` … "
"`<!-- mojiemoji:on -->` if it should stay raw. "
"Suggested diff:\n\n"
)
sys.stderr.write(diff)
sys.stderr.write("\n")
return 0
if __name__ == "__main__":
sys.exit(main())
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
mojiemoji.jozo.beer のスタンプ画像で本語の GitHub Markdown (issue / / レビュー / リプライ / リリースノート) を に — これは【マジで】やばい【バグ】ですね のように ワードだけスタンプ化 する mid-sentence インライン強調を でに埋め込む Claude Code 🚀 的にはこの README 自体が dogfooding なので、上から下までスタンプまみれ
Lets Claude watch a video from a URL or local file by pulling timestamped frames and a transcript. Works in Claude Code, and the watch skill downloads with yt-dlp and extracts frames with ffmpeg.
Gives Claude Code users access to MeiGen for discovering inspiration and creating images and videos with AI models. It also offers skills for product photoshoot planning, social video thumbnails, and visual creative direction.
Edits CapCut and JianYing video projects from the terminal or an LLM agent, covering subtitles, timing, speed, volume, templates, animations, and cutting long videos into shorts. The result opens in CapCut with every track still editable.
Turns a single line of story text into a finished short drama, covering the script, storyboards, character-consistent video and voiceover. Works with the AI model providers you already use, so you skip assembling each step by hand.
A Claude Code plugin for planning and producing AI-generated music albums, with skills for album concepts, tracklists, progress dashboards, idea tracking, and album artwork prompts.
A skill that builds AI teaching personas of specific Buddhist masters, grounded in Buddhist scripture texts and tested for fidelity to their teachings. It includes ethics gates for boundaries on what the persona will say.
Gives journalists and researchers ready-made guidance for fact-checking, source verification, interview prep, story pitches and data journalism, plus accessibility audits for news sites. Works in Claude Code and Codex.
A local video studio for your coding agent. Describe a video in one sentence: your agent directs, your machine renders. Claude Code, Codex, Cursor, Devin and more. No API keys, no uploads.