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 bitwize-music 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 bitwize-music-studio/claude-ai-music-skills> /plugin install bitwize-music@bitwize-music
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
PostToolUse
Write|Editpython3 ${CLAUDE_PLUGIN_ROOT}/hooks/validate_track.pypython3 ${CLAUDE_PLUGIN_ROOT}/hooks/check_version_sync.pyIn the plugin's words
How bitwize-music describes its own hook set.
bitwize-music plugin hooks for quality enforcement
Where it lives
#!/usr/bin/env python3
"""PostToolUse hook: keep the Claude and Codex plugin versions in sync.
The Claude manifest and marketplace entry are the canonical pair. When the
Codex manifest exists, its version must match the Claude plugin manifest too,
but only for this plugin: the hook ships to every user and runs on every
Write/Edit, and other dual-host plugins may version their Codex adapter
separately. Malformed or partially-written manifests are ignored so an
editor's transient state does not turn this advisory hook into a hard failure.
"""
import json
import os
import subprocess
import sys
from pathlib import Path
MANIFEST_FILES = {"plugin.json", "marketplace.json"}
PLUGIN_DIRS = {".claude-plugin", ".codex-plugin"}
# The Codex-equals-Claude version rule is this plugin's release policy, not a
# general one; see the module docstring.
CODEX_SYNC_PLUGIN_NAME = "bitwize-music"
def _manifest_context(file_path: str):
"""Return ``(repository_root, manifest_dir, path)`` for a manifest path."""
if not isinstance(file_path, str) or not file_path:
return None
path = Path(file_path)
if path.name not in MANIFEST_FILES:
return None
for parent in (path.parent, *path.parents):
if parent.name in PLUGIN_DIRS:
return parent.parent, parent.name, path
return None
def is_manifest_file(file_path: str) -> bool:
return _manifest_context(file_path) is not None
def _read_json(path: Path):
try:
with path.open(encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, OSError, UnicodeDecodeError):
return None
def _plugin_version(data) -> str:
return data.get("version", "") if isinstance(data, dict) else ""
def _marketplace_version(data) -> str:
if not isinstance(data, dict):
return ""
plugins = data.get("plugins", [])
if not isinstance(plugins, list) or not plugins or not isinstance(plugins[0], dict):
return ""
return plugins[0].get("version", "")
def _version_pairs(root: Path):
"""Return version pairs that can be checked from this repository root."""
claude_plugin_path = root / ".claude-plugin" / "plugin.json"
marketplace_path = root / ".claude-plugin" / "marketplace.json"
claude_plugin = _read_json(claude_plugin_path)
if not isinstance(claude_plugin, dict):
return []
pairs = []
claude_version = _plugin_version(claude_plugin)
# A missing or half-written marketplace.json drops only its own pair; it
# must not also skip the independent Claude-vs-Codex comparison below.
marketplace_version = _marketplace_version(_read_json(marketplace_path))
if claude_version and marketplace_version:
pairs.append(
(
claude_plugin_path,
"plugin.json",
claude_version,
marketplace_path,
"marketplace.json",
marketplace_version,
)
)
codex_plugin_path = root / ".codex-plugin" / "plugin.json"
if claude_plugin.get("name") == CODEX_SYNC_PLUGIN_NAME and codex_plugin_path.exists():
codex_version = _plugin_version(_read_json(codex_plugin_path))
if claude_version and codex_version:
pairs.append(
(
claude_plugin_path,
".claude-plugin/plugin.json",
claude_version,
codex_plugin_path,
".codex-plugin/plugin.json",
codex_version,
)
)
return pairs
def _same_path(left: Path, right: Path) -> bool:
return os.path.normcase(os.path.abspath(str(left))) == os.path.normcase(
os.path.abspath(str(right))
)
def _modified_files(root: Path) -> set[str]:
# This runs in every user's session, inside whatever repo holds the edited
# manifest. Repo-local config such as core.fsmonitor can make git run
# commands, and safe.bareRepository=explicit stops git from discovering a
# bare repository planted inside a project. Command-line -c settings take
# precedence over repo config and count as protected configuration.
try:
result = subprocess.run(
[
"git",
"-c",
"core.fsmonitor=false",
"-c",
"safe.bareRepository=explicit",
"diff",
"--name-only",
],
capture_output=True,
text=True,
timeout=5,
cwd=str(root),
)
except (subprocess.TimeoutExpired, OSError):
return set()
return {
line.strip().replace("\\", "/")
for line in result.stdout.splitlines()
if line.strip()
}
def _is_modified(path: Path, root: Path, modified: set[str]) -> bool:
try:
relative = os.path.relpath(path, root).replace(os.sep, "/")
except ValueError:
relative = ""
absolute = str(path).replace(os.sep, "/")
return relative in modified or absolute in modified
def check_sync(data: dict) -> list[str]:
tool_input = data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
context = _manifest_context(file_path)
if context is None:
return []
root, _, _ = context
issues = []
modified = None
for left_path, left_label, left_version, right_path, right_label, right_version in _version_pairs(root):
if left_version == right_version:
continue
# If the counterpart is also modified, this is likely a sequential
# edit of a version pair. Defer until the second file is saved.
counterpart = None
if _same_path(Path(file_path), left_path):
counterpart = right_path
elif _same_path(Path(file_path), right_path):
counterpart = left_path
if counterpart is not None:
if modified is None:
modified = _mo#!/bin/bash
# Install git hooks for bitwize-music plugin
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
HOOKS_DIR="$REPO_ROOT/.git/hooks"
echo "Installing git hooks..."
# Install pre-commit hook
if [ -f "$SCRIPT_DIR/pre-commit" ]; then
cp "$SCRIPT_DIR/pre-commit" "$HOOKS_DIR/pre-commit"
chmod +x "$HOOKS_DIR/pre-commit"
echo "✓ Installed pre-commit hook"
else
echo "✗ pre-commit hook not found"
exit 1
fi
echo ""
echo "Git hooks installed successfully!"
echo ""
echo "The pre-commit hook requires:"
echo " - ruff (pip install ruff)"
echo " - pytest (pip install pytest)"
echo " - bandit (pip install bandit)"
echo " - pip-audit (pip install pip-audit)"
echo ""
echo "Install all at once:"
echo " pip install ruff pytest bandit pip-audit"
#!/usr/bin/env python3
"""PostToolUse hook: Validate track file YAML frontmatter after Write/Edit.
Only activates for files matching */tracks/*.md pattern.
Checks required frontmatter fields and valid status values.
"""
from __future__ import annotations
import json
import re
import sys
REQUIRED_FIELDS = ["title", "track_number", "status"]
VALID_STATUSES = [
"Not Started",
"Sources Pending",
"Sources Verified",
"In Progress",
"Generated",
"Final",
]
def is_track_file(file_path: str) -> bool:
"""True for ``.md`` files living in a directory segment named ``tracks``.
Claude Code passes the platform's *native* path, so on Windows this arrives
backslash-separated (``...\\tracks\\01-opener.md``) and mixed separators
(``C:/foo\\tracks/01-x.md``) are possible too. Backslashes are folded to
forward slashes before splitting rather than going through ``pathlib``:
``PurePosixPath`` would not split backslashes at all, ``PureWindowsPath``
behaves identically to this but costs an import on a hook that runs on
every Write/Edit. The trade-off is that a POSIX file whose *name* legally
contains a backslash could be split at it — vanishingly rare, and the
failure mode is a harmless extra frontmatter check.
Matching whole segments (rather than the old ``"/tracks/" in path``
substring test) also keeps ``soundtracks/``, ``tracks-old/`` and
``tracksnotadir/`` from matching, and additionally handles relative paths
such as ``tracks/01-x.md``, which the substring form rejected.
"""
if not file_path.endswith(".md"):
return False
# Exclude the final component: that is the filename, not a directory.
return "tracks" in file_path.replace("\\", "/").split("/")[:-1]
def extract_frontmatter(content: str) -> dict | None:
match = re.match(r"^---\s*\n(.*?)\n---", content, re.DOTALL)
if not match:
return None
fm = {}
for line in match.group(1).split("\n"):
if ":" in line:
key, _, value = line.partition(":")
fm[key.strip()] = value.strip().strip('"').strip("'")
return fm
def get_file_content(data: dict) -> str | None:
tool_input = data.get("tool_input", {})
# Write tool provides full content
if "content" in tool_input:
return tool_input["content"]
return None
def validate(data: dict) -> list[str]:
tool_input = data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
if not is_track_file(file_path):
return []
content = get_file_content(data)
if content is None:
# Edit tool — can't validate full frontmatter from partial edit
return []
fm = extract_frontmatter(content)
if fm is None:
return ["Track file is missing YAML frontmatter (--- block)."]
issues = []
for field in REQUIRED_FIELDS:
if field not in fm or not fm[field]:
issues.append(f"Missing required frontmatter field: {field}")
status = fm.get("status", "")
if status and status not in VALID_STATUSES:
issues.append(
f"Invalid status '{status}'. Must be one of: {', '.join(VALID_STATUSES)}"
)
return issues
def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, EOFError):
sys.exit(0)
# Well-formed JSON that is not an object (a list, null, a number, a string)
# has no `tool_input` to inspect. There is nothing to validate, and this
# hook must never break the user's session over an unexpected payload.
if not isinstance(data, dict):
sys.exit(0)
issues = validate(data)
if issues:
msg = "Track frontmatter validation failed:\n" + "\n".join(f" - {i}" for i in issues)
print(msg, file=sys.stderr)
sys.exit(2)
sys.exit(0)
if __name__ == "__main__":
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.
I love music but never learned an instrument. AI became the creative outlet that was always out of reach. This project started as a way to go deep on Claude Code plugin architecture, agentic workflows, multi-model orchestration, and MCP tooling.
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 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.
Creates and lays out Obsidian Canvas files as presentations, flowcharts, mind maps, mood boards, and knowledge graphs. It can also fill canvases with images, notes, and diagrams, generate content with AI, and export the results to PNG, SVG, or PDF.