Content
<p align="center">
<img src="assets/banner.jpg" width="80%" alt="Oh My Cassette banner" />
</p>
<h1 align="center">
Oh My <a href="https://trycassette.online/">Cassette</a>: Pocket AI Co-editor for Video Montage
</h1>
<p align="center">
<a href="https://github.com/Cassette-Editor/oh-my-cassette/releases">
<img src="https://img.shields.io/github/v/release/Cassette-Editor/oh-my-cassette?style=flat-square&color=blue" alt="Version">
</a>
<a href="https://github.com/Cassette-Editor/oh-my-cassette/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/Cassette-Editor/oh-my-cassette?style=flat-square" alt="License">
</a>
<a href="https://github.com/Cassette-Editor/oh-my-cassette/stargazers">
<img src="https://img.shields.io/github/stars/Cassette-Editor/oh-my-cassette?style=flat-square&logo=github" alt="Stars">
</a>
<a href="https://github.com/Cassette-Editor/oh-my-cassette/fork">
<img src="https://img.shields.io/github/forks/Cassette-Editor/oh-my-cassette?style=flat-square&logo=github" alt="Forks">
</a>
<img src="https://img.shields.io/badge/Python-3.11--3.13-purple.svg" alt="Python">
<a href="https://github.com/Cassette-Editor/oh-my-cassette/actions/workflows/ci.yml">
<img src="https://img.shields.io/github/actions/workflow/status/Cassette-Editor/oh-my-cassette/ci.yml?branch=main&style=flat-square&label=CI" alt="CI">
</a>
<a href="https://trycassette.online">
<img src="https://img.shields.io/badge/Cassette-Compatible-1f6feb?style=flat-square&logo=data%3Aimage%2Fwebp%3Bbase64%2CUklGRhgHAABXRUJQVlA4WAoAAAAQAAAANwAANwAAQUxQSMkCAAABkIVt29lI75hr27Yx9tq2bdu2bdu2bdt23e4kfQ86TZN%2FeRgRE4DfcIX9Oyr%2BCuE7ZFJeX0a0oK0SE7SsKyNSmTU2KjQvKypKsTVWumhcVkSEIsuNVNG4KJ9WOabqqPK3yVm1yDnjCzX8OCWLWhknfabG7yZkUiPz5HcU8HWvVK5kGPuegj4blEqJV%2B9XFPhpZ09nmU0UWi7gLKteLHtRBTqx5CIKvotl%2B%2B%2F2tNfgd7%2FC6%2BHpgCwTv4r2YVQGJJhn2meRPo3PCoU5lhhE%2BT41F1wsssoswo%2FphaFiycVGrfQLC0Dl0ssNqugSMq0uAQ0LrbQlJLlkXVUSGgdttjnISr6T8TuCIGDQVom0FVa0PxSCRu2Ltwcp2pdOFLe2P1nKWX6LXnc7nRhR53mvuaezEpyU58d0AdwaRG1mX18oDOcQLH%2FqizEjUmpSZidHrXuarXmggmAOQ9IYb78HnK1BwVVm2%2BTUG%2BON31IryGgZCwBh8bzqBZ9EqhSNvkauB7Zwb2ko9Dh8PnFMfowmdVkDDjzq5eVS7lm25S1aSKuBHS%2Fdsk%2FP4QylDA%2BstxOfJO2xOWWZ892VpR3zlXvyIqO0Btj2bZnZmFMBYh9xS7YfJEcmM02rLVeFl7uTNPlXkFc8gBwOW8g1BaE4WViShiS5F4tf5ti%2FK%2FPpU9UdkvR4t7pZ45dn4GTV%2FvJwPebx7J3S67Sprjyr07oz7ewA78b3ebI8cO%2B8g7weSOUBNVO4YxS7HYq6%2BiBxNBvOMmYJ4svGALzvO%2BSUxkLDXN9HfRuX4vMMr9t7MusGhHFwxtLOAuL8tUDRwgPs01%2Fe8i6v31u%2FehTfG5c6XIKIAQvJo42XNbWuRhylDVkA7yfbhQBippUKY%2BVKrBbOCShbAx5VUgsCwHvvp1HSjDCumsnVEDvzKXJSOCmtyC4YfFpOyRhnnF8Mv2TaYviLBABWUDggKAQAAPAYAJ0BKjgAOAA%2BTSCLRCKiIRgN%2FgAoBMS0htgATIB%2FRvwg8Cf6r%2BM37Vdtj559kc4Z9Yvtn5Gfkzzg7Sf%2BC%2FKTgCePfx3%2FVfmBzAfTz%2B7epP%2BKf2X8wvYDuAPUA%2Flv9E%2F2P3O%2FDz%2FeeSD8v%2FuHsB%2Fxz%2Bef6r%2B7fu%2Fxm36xJmyyFkngkpmRPYsV6xmH5QF4lb%2Fl9hSgk5D3PqHNQLXrlv7NrItI%2FEHrxBF2jRuhKjEBVmBB10mJvHzkR2Q2arL3GqP10RyllNZGUghwhZxRilAA%2Fuw6mSpSUmz%2BC9%2BKh7D7qK39xAKDdKGxdWNME94DsfqLZMWOWeIeTypqgNWds0IPEbdim9%2FrYj%2BEd8AhWw%2F%2Bzi%2FwdIkNi7bJQyZ55GARC1%2FHJxJRDbUPxx7XEJNOqXwK2TTvcPfg%2FB1Sxtu3%2F%2BqXZV7Ea27hgAqAwjmpzYt%2FUnFY%2BC0oW45G3Loc8bVetEXzeXitUxCUpACSaawCSSfLqRe1eG3zUIXqEc82GnC78rFUxGMUSd%2FG9fuFsdPGi96Lm3KIZEKgeEYxznL%2BMFlhtO40ZN%2FW3ejoFZfedwy4KUpiGElmS9DdaNzh40f3qq2ox2aJonnJDtPET3GC8%2FxLitJKLpjNVH%2Fs2f5UmAx%2B5LKDbHdllFCDWoI3Pp%2FVHmOId6JL4jNCWVVHcq5vtCed3YAxjpLyN7Mu2NYOtVFR9IFyf7Vd5AztRBIGFD79z80kLXz%2F%2F%2BfO3t2lkek5ydWcB0weeaYMf%2Fwaie0%2Bighho%2Bx1LSEM4hwlpukY3yztN8wyYCF8fSe9AWia9TSnFp57nMo5YifIm9RF6TM%2Fvo6yBTI0Pi%2FGt%2F%2F6mnejWgvH1%2F%2F%2BhS5iYOwLt%2BHG5ouw5J01Fd%2F1WfcWXdBL9MEwe3fGBYZUYUOiM3FvKWH0ikirZAvG0xfpgf%2FgnXTZaTTeyzpnuvF289BWGVcYoRTe2gBbGUT00tzQ4XuCIo9OV7ysRBBK4PTfYTi%2FntO%2FGJkP7JHWEAf7RfyPpf2z%2F3gmxcwD3IXTbEB9aXF%2BVQYVzi8x%2FWj1eluYh3G6xEWj5aKxK09fn3AaJy6LKTQjgk8209Tv7brUzGKt26W4a5uQVRnc6cAzlXdvZB3nrAUJ4SsTWRbCM9m6TDQ7A0dctlIEQzv%2FUTvpPTnyuaOqiVP5PsHXhi9x0791xIp4e1U8sUVgkxVIgE2QYZjQXnY9Kuv0ciSsqTTzWZ6it0Z1Y9qOMi%2F%2BDwjsHCI%2FTwjcrXfrv2jVjya%2Fh3h%2FRzBb7Mszb6Pr54Dpg%2B6OQeVLDvWMOcd9P7x1uSSp%2FrzOS7zB9i5HJvNTeTjIi%2Be5Xf9eSzCoym6aKUC6i6Gkf7O96HwkdmR9TOx3ZuNOJY%2F%2BdF%2F%2F%2FnRxgu4Mj%2F29%2FT%2FJ34nP%2F6r9U3fQV%2BjAyqrfV23tQAAA&logoColor=white" alt="Cassette Compatible">
</a>
<a href="https://github.com/nousresearch/hermes-agent">
<img src="https://img.shields.io/badge/Hermes%20Agent-Compatible-7c3aed?style=flat-square&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI%2BPHBhdGggZD0iTTUgNGgzdjZoOFY0aDN2MTZoLTN2LTdIOHY3SDV6IiBmaWxsPSJ3aGl0ZSIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjIiIGZpbGw9IiMxMTEiLz48L3N2Zz4%3D&logoColor=white" alt="Hermes Agent Compatible">
</a>
<a href="https://github.com/openai/codex">
<img src="https://img.shields.io/badge/Codex-Compatible-000000?style=flat-square&logo=openai&logoColor=white" alt="Codex Compatible">
</a>
<a href="https://claude.com/claude-code">
<img src="https://img.shields.io/badge/Claude%20Code-Compatible-d97757?style=flat-square&logo=anthropic&logoColor=white" alt="Claude Code Compatible">
</a>
<a href="https://opencode.ai">
<img src="https://img.shields.io/badge/OpenCode-Compatible-0b0b0b?style=flat-square&logo=opencode&logoColor=white" alt="OpenCode Compatible">
</a>
</p>
<p align="center">
<a href="./README.zh-cn.md">简体中文</a> | <b>English</b>
</p>
<table align="center" width="82%">
<tr>
<td>
<video src="https://github.com/user-attachments/assets/efcfda75-f09b-4bde-8a69-e84410f28e10" controls width="100%"></video>
</td>
</tr>
</table>
<h3 align="center">
<em>
Arm Codex, Claude, or <a href="https://github.com/nousresearch/hermes-agent">Hermes</a> with <a href="https://trycassette.online">Cassette</a>'s amazing video editing power.<br />
Add a friend, chat your video into shape.
</em>
</h3>
# 🎥 Overview
**Oh My Cassette** is an open-source AI video editing plugin and local MCP server for [Claude Code](https://claude.com/claude-code), [Codex](https://github.com/openai/codex), [Hermes Agent](https://github.com/nousresearch/hermes-agent), and [OpenCode](https://opencode.ai). It turns natural-language chat into finished montage videos on [Cassette](https://trycassette.online) — beat-synced cuts, auto-matched music, subtitles, transitions, and picture-in-picture — with minimal token overhead.
Just send your videos or audio files and chat with your agent from anywhere on your phone. Your agent will work with Cassette to understand your intent, organize your media, plan the edit, and turn your materials into a ready-to-share video with powerful montage capabilities.
We believe everyone carries a film of their own. **Oh My Cassette** helps you capture sparks of inspiration, shape scattered moments into a story, and turn raw clips into montage videos without wrestling with timelines or editing tools.
Be the director of your own story, effortlessly.
<table>
<tr>
<td align="center" width="33%">
<h3>📱 Capture Anywhere</h3>
<p>Send clips, audio, and ideas to your AI editing friend anytime, anywhere.</p>
</td>
<td align="center" width="33%">
<h3>💬 Chat-to-Edit</h3>
<p>Describe the video you want in natural language instead of wrestling with timelines.</p>
</td>
<td align="center" width="33%">
<h3>🎵 Smart Music Matching</h3>
<p>Automatically match music to the mood, rhythm, and story of your footage.</p>
</td>
</tr>
<tr>
<td align="center" width="33%">
<h3>🌌 Montage Storytelling</h3>
<p>Organize scattered moments into rhythmic, emotional, and memorable video stories.</p>
</td>
<td align="center" width="33%">
<h3>✨ Advanced Editing Ability</h3>
<p>Support title captions, picture-in-picture, beat sync, hard cuts, transitions, motion graphics, and stylized edits.</p>
</td>
<td align="center" width="33%">
<h3>✅ Ready to Share</h3>
<p>Preview, refine, and export polished videos ready to post on social platforms.</p>
</td>
</tr>
</table>
## 🎬 Case Videos
Every case below was edited end-to-end by an AI agent through Oh My Cassette, from the exact prompt shown — real inputs, real processing times, and the output is what the agent delivered.
<table width="100%">
<tr>
<td width="33%" valign="top">
<video src="https://github.com/user-attachments/assets/ff19c37d-1eb4-4702-a2e1-eac704bdbae8" controls width="100%"></video>
<h3>Daily Moments Vlog</h3>
<p>
<sub>🎞️ Input: 15 video clips · 1 audio track</sub><br />
<sub>⏱️ Processing Time: 10 mins</sub>
</p>
<p><b>Prompt</b><br />
Edit to the provided audio with beat-synced cuts. Pick the best shots from all video materials and match them to the lyrics and rhythm. Change shots whenever the lyrics change, keep a travel-like and everyday emotional progression, add centered lyric subtitles to each matching shot, and make the whole edit rhythmically synced.</p>
<p><code>Healing</code> <code>Daily</code> <code>Feel-good</code> <code>Lifestyle</code></p>
</td>
<td width="33%" valign="top">
<video src="https://github.com/user-attachments/assets/b85285a9-30ed-4f9b-b314-538bfd9dbdd6" controls width="100%"></video>
<h3>Travel Vlog</h3>
<p>
<sub>🎞️ Input: 13 video clips · 1 audio track</sub><br />
<sub>⏱️ Processing Time: 15 mins</sub>
</p>
<p><b>Prompt</b><br />
Edit a 30-second travel vlog using all materials with a clear viewing flow. Add the title subtitle "KOTA KINABALU" at the beginning and end. Include the vlog shooting time, overlay the character video onto similar scenic shots as a picture-in-picture effect, keep the rhythm light and memorable, and use hard cuts plus music beat sync for a fresh vacation feeling.</p>
<p><code>Travel</code> <code>Vlog</code> <code>Beat Sync</code> <code>PiP</code></p>
</td>
<td width="33%" valign="top">
<video src="https://github.com/user-attachments/assets/73059786-246e-4808-b7cf-4deb35994ebc" controls width="100%"></video>
<h3>Cinematic Mood Short</h3>
<p>
<sub>🎞️ Input: 15 video clips · 1 audio track</sub><br />
<sub>⏱️ Processing Time: 17 mins</sub>
</p>
<p><b>Prompt</b><br />
Use my materials to edit a lyric-driven emotional short of around 40 seconds. Start with empty scenic shots to build the mood, then intercut solitary character moments, memories, and emotional close-ups. Alternate character shots with atmospheric shots, advance with lyric subtitles on the beat, arrange subtitle words in varied sizes, and end with a quiet pause shot.</p>
<p><code>Music</code> <code>MV</code> <code>Cinematic</code></p>
</td>
</tr>
<tr>
<td width="33%" valign="top">
<video src="https://github.com/user-attachments/assets/97fdfe3a-f420-4135-8c62-494bbb7ea436" controls width="100%"></video>
<h3>Cooking Tutorial</h3>
<p>
<sub>🎞️ Input: 12 video clips · 1 audio track</sub><br />
<sub>⏱️ Processing Time: 8 mins</sub>
</p>
<p><b>Prompt</b><br />
Use these materials to edit a cooking video for stir-fried pork with green peppers. Keep the steps smooth, preserve the cooking sounds, and add simple subtitles. The subtitles can be generated based on the visuals to help viewers understand each step. Keep the style clean and natural without covering the ingredients.</p>
<p><code>Food</code> <code>Tutorial</code> <code>Explainer</code></p>
</td>
<td width="33%" valign="top">
<video src="https://github.com/user-attachments/assets/8da38e40-7876-4743-a32d-5fd95265f37e" controls width="100%"></video>
<h3>Commercial</h3>
<p>
<sub>🎞️ Input: 15 video clips · 15 sound effects · 1 audio track</sub><br />
<sub>⏱️ Processing Time: 13 mins</sub>
</p>
<p><b>Prompt</b><br />
Use these materials to edit a 15-second product commercial. Start with opening the bottle cap, add bubbles, water splashes, ice cubes, and product details in the middle, and end with the full product and brand. Keep it refreshing, thirst-quenching, and ad-like.</p>
<p><code>Product</code> <code>Short</code> <code>Commercial</code></p>
</td>
<td width="33%" valign="top">
<video src="https://github.com/user-attachments/assets/9374de93-c0ff-4bf2-b1ed-fe393579f0c2" controls width="100%"></video>
<h3>Game Highlight Video</h3>
<p>
<sub>🎞️ Input: 5 video clips · 1 audio track</sub><br />
<sub>⏱️ Processing Time: 5 mins</sub>
</p>
<p><b>Prompt</b><br />
Use my game footage to edit a 20-30 second martial arts combat short. Give it an ink-painting visual style and a tense, solemn atmosphere. Create the cinematic feeling of entering the battlefield alone and fighting through the encirclement.</p>
<p><code>Game</code> <code>Highlight</code> <code>Beat Sync</code></p>
</td>
</tr>
</table>
<p align="center">
<strong>★ More cases are on the way. Star the project to follow along.</strong>
</p>
# 🏄 Try without installation
We host a public web demo so you can try the Oh My Cassette workflow from a desktop or mobile browser without installing Codex, Claude, or Hermes locally:
<a href="http://43.134.224.156:8080/"> TRY NOW! </a>
> [!WARNING]
> The public demo is for illustration and evaluation only. It is intentionally unauthenticated, so do **not** upload sensitive, private, confidential, illegal, copyrighted, regulated, or otherwise restricted content.
>
> Uploaded files, prompts, generated outputs, job state, and troubleshooting metadata may be processed by this demo server, Cassette, DeepSeek, and third-party music/search providers used by the BGM flow. Treat anything you upload as visible to the demo operator and external services involved in the workflow.
>
> The demo may be reset, rate-limited, unavailable, or changed at any time. We do not guarantee data retention, deletion timing, confidentiality, fitness for production use, output quality, copyright compliance of generated/editing results, or uninterrupted service. You are responsible for the content you upload and for reviewing any generated output before sharing it.
>
> Browser refreshes, tab closes, or navigation away from the web demo start a new web session and best-effort cleanup the previous web session's temporary uploads, chat history, and finished job files.
>
> By default, the demo uses the server-side DeepSeek configuration when available. You can open **Settings** in the web UI and provide your own DeepSeek API key for testing. Your key is sent only with requests to this demo server and is not written to this repository or server-side disk by the web app, but it still transits the public demo server; use a key you can rotate and monitor.
<details>
<summary>Deploy Web Demo Locally</summary>
The web demo is a single FastAPI service that keeps the existing Cassette gateway flow but replaces the chat surface with a browser UI. It stores runtime uploads, jobs, exports, screenshots, and outbox data under `CASSETTE_ASSET_ROOT` instead of the repository.
1. Clone the repository and create a dedicated web environment:
```bash
git clone https://github.com/Cassette-Editor/oh-my-cassette.git
cd oh-my-cassette
python3 -m venv .venv-web
. .venv-web/bin/activate
pip install -U pip
pip install -r requirements-web.txt
python -m playwright install chromium
```
2. Configure Cassette and DeepSeek runtime settings as process environment variables. The web demo does not require Hermes Agent or `~/.hermes/.env`.
```bash
cp deploy/oh-my-cassette-web.env.example ./oh-my-cassette-web.env
$EDITOR ./oh-my-cassette-web.env
```
At minimum, set:
```dotenv
CASSETTE_URL=https://sg.trycassette.online/agent
CASSETTE_AUTH_EMAIL=you@example.com
CASSETTE_AUTH_PASSWORD='your-cassette-password'
CASSETTE_ASSET_ROOT=$HOME/.oh-my-cassette/cassette
CASSETTE_BROWSER_TIMEOUT_SEC=1800
DEEPSEEK_API_KEY='your_deepseek_api_key'
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-v4-flash
OMC_WEB_HOST=0.0.0.0
OMC_WEB_PORT=8080
OMC_WEB_LOG_DIR=./web_demo/logs
```
If you load the file with `. ./oh-my-cassette-web.env`, quote any value that contains shell-special characters, spaces, or `#`. The systemd `EnvironmentFile` format also accepts quoted values.
Cassette upload/analysis waits inherit `CASSETTE_BROWSER_TIMEOUT_SEC` by default. Set `CASSETTE_UPLOAD_TIMEOUT_SEC` only when you need a different upload timeout; `0` means wait forever.
Load the variables before starting the service:
```bash
set -a
. ./oh-my-cassette-web.env
set +a
```
If you do not want to put a DeepSeek key on the server, leave `DEEPSEEK_API_KEY` empty and enter a temporary key from the web UI **Settings** panel. That browser-provided key is attached to API requests for that session and is not persisted by the web app.
Web demo service logs are separate from Hermes Agent / OhMyCassette plugin logs. By default they are written to `./web_demo/logs/web_demo.log` relative to the service working directory, or to `$OMC_WEB_LOG_DIR/web_demo.log` when `OMC_WEB_LOG_DIR` is set.
Cassette job records for the web demo are also separate when `CASSETTE_ASSET_ROOT` is set as above: raw job JSON lives in `$CASSETTE_ASSET_ROOT/jobs/cassette_*.json`, and the web UI exposes a per-job **Log** link for jobs owned by the current browser session.
3. Build the browser UI (Vite + React → `web_demo/frontend/dist`). Requires Node.js + npm at build time only:
```bash
./web_demo/build_frontend.sh
```
4. Start the web demo:
```bash
. .venv-web/bin/activate
python -m web_demo.server
```
> The server serves the built `web_demo/frontend/dist`. If it has not been built yet, `/` returns a clear 503 telling you to run the build. Re-run `web_demo/build_frontend.sh` after pulling changes under `web_demo/frontend`.
Open `http://127.0.0.1:8080/` for local testing, or `http://<server-ip>:8080/` from another device if the host firewall/security group allows inbound TCP `8080`.
4. Optional systemd deployment:
```bash
sudo cp deploy/oh-my-cassette-web.service.example /etc/systemd/system/oh-my-cassette-web.service
sudo cp deploy/oh-my-cassette-web.env.example /etc/oh-my-cassette-web.env
sudo $EDITOR /etc/oh-my-cassette-web.env
sudo systemctl daemon-reload
sudo systemctl enable --now oh-my-cassette-web
sudo systemctl status oh-my-cassette-web
journalctl -u oh-my-cassette-web -f
```
Before enabling the service, edit `/etc/systemd/system/oh-my-cassette-web.service` if your repository or virtualenv path differs from the example.
`journalctl` shows uvicorn stdout/access logs; Web demo business logs still go to `$OMC_WEB_LOG_DIR/web_demo.log`.
5. Smoke test:
```bash
curl -fsS http://127.0.0.1:8080/ -o /dev/null
python3 -m compileall -q web_demo tools.py notifier.py browser.py
tail -f ./web_demo/logs/web_demo.log
```
Then open the browser UI, upload a small video, send an edit request, and watch the job card/events until the export download appears.
</details>
# 🚀 Quick Start
## Before You Start 🎬
Oh My Cassette connects Codex, Claude, or Hermes to the <a href="https://trycassette.online/agent">Cassette Agent</a>. You need:
* Codex, Claude Code, or a working Hermes Agent installation.
* A Cassette account.
* For Hermes only, a configured gateway such as QQ or Telegram.
> [!TIP]
> **Apply for a Cassette account here:** [**Cassette Sign Up**](https://trycassette.online/signup/)
If you plan to use Hermes and it is not installed yet:
```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
```
If the Hermes Agent gateway is not configured yet:
```bash
hermes gateway setup
```
Oh My Cassette currently supports QQ and Telegram gateways.
## Requirements
- macOS, Linux, or Windows for the local MCP plugin (Codex, Claude Code, or OpenCode). The Hermes Agent gateway path is macOS/Linux only.
- Codex, Claude Code, or OpenCode for the local MCP plugin, or Hermes Agent with its gateway configured.
- Python 3.11–3.13.
- `ffmpeg`, required for Hermes gateway normalization and optional API export thumbnails.
Install system tools:
```bash
# macOS
brew install uv ffmpeg
```
```bash
# Debian/Ubuntu Linux
sudo apt-get update
sudo apt-get install -y ffmpeg
curl -LsSf https://astral.sh/uv/install.sh | sh
```
```powershell
# Windows (PowerShell)
winget install Python.Python.3.13
winget install Gyan.FFmpeg
```
## Install
### Codex
```bash
codex plugin marketplace add https://github.com/Cassette-Editor/oh-my-cassette.git
codex plugin add oh-my-cassette@cassette-editor
```
Start a new Codex task after installation so plugin discovery runs again. The plugin contributes the host-neutral `cassette-video-edit` skill and a local MCP process named `cassette`.
### Claude Code
```bash
claude plugin marketplace add Cassette-Editor/oh-my-cassette
claude plugin install oh-my-cassette@cassette-editor
```
Restart Claude Code after installation. You can verify the installation with:
```bash
claude plugin details oh-my-cassette@cassette-editor
```
### OpenCode
OpenCode has no plugin marketplace for MCP servers, so add the server and skill from a checkout. Clone the repository, then point OpenCode at the launcher from your project's `opencode.json` (a copy is committed at the repo root):
```jsonc
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"cassette": {
"type": "local",
"command": ["python3", "/absolute/path/to/oh-my-cassette/scripts/run_local_mcp.py"],
"enabled": true,
"environment": { "CASSETTE_MCP_HOST": "opencode" }
}
}
}
```
OpenCode discovers the host-neutral `cassette-video-edit` skill from the repository's `.agents/skills/` directory, or copy it to `~/.config/opencode/skills/cassette-video-edit/` for global use. On Windows, use `"python"` instead of `"python3"` in `command`.
### Any other MCP host
The runtime is host-neutral, so any client that launches a local stdio MCP server can use it. Point the client at `scripts/run_local_mcp.py` (run with `python3`, or `python` on Windows) and set `CASSETTE_RUNTIME_ADAPTER=mcp`. The server ships full workflow guidance in its MCP `instructions`, and every tool returns a typed `phase`/`next_action` so a host without the packaged skill can still drive the flow. For the best experience, also install the `cassette-video-edit` skill (or equivalent system prompt) so the guided-choice and review steps are followed.
### Hermes
Install through the Hermes plugin manager (recommended):
```bash
hermes plugins install Cassette-Editor/oh-my-cassette
```
The Hermes installer prompts for your Cassette account email and password and saves them to `~/.hermes/.env`. Then run the setup finisher — it installs Playwright Chromium into the Hermes Python environment, detects `ffmpeg`/`ffprobe`, and lets you pick the Cassette region — and enable the plugin:
```bash
python3 ~/.hermes/plugins/cassette/scripts/install_plugin.py --setup-only
hermes plugins enable cassette
hermes gateway restart
```
You can check the install status anytime from the Diagnose section.
<details>
<summary>Alternative: guided installer from a git checkout (for development)</summary>
```bash
git clone https://github.com/Cassette-Editor/oh-my-cassette.git
cd oh-my-cassette
python3 scripts/install_plugin.py
```
Run the installer and follow the prompts to set up Oh My Cassette with your Cassette account.
The installer:
- installs the plugin into `~/.hermes/plugins/cassette` as a symlink by default;
- asks whether to enable the plugin with `hermes plugins enable cassette`;
- asks which Cassette URL to use:
- `https://sg.trycassette.online/agent` (Asia, default)
- `https://trycassette.online/agent` (America)
- optionally saves Cassette login and Jamendo credentials into `~/.hermes/.env`;
- detects `ffmpeg` and `ffprobe` paths for service environments;
- installs Python Playwright and Chromium into the Hermes Python environment;
- restarts the Hermes gateway service.
To copy files instead of creating a symlink:
```bash
python3 scripts/install_plugin.py --copy --force
```
For non-interactive installs:
```bash
python3 scripts/install_plugin.py \
--skip-plugin-enable \
--skip-cassette-url \
--skip-cassette-auth \
--skip-jamendo-auth
```
</details>
## Use with Codex or Claude over local MCP
Codex and Claude use the same self-contained runtime. In this README, **MCP server** means a local child process connected over stdin/stdout: it opens no port and does not depend on the FastAPI web-demo service. The separate Cassette backend remains the editing engine and continues to handle authentication, media processing, agent runs, project state, and rendering.
The web demo is intentionally different. Browsers still need the retained FastAPI server for uploads, chat sessions, and frontend endpoints; none of that behavior is removed by the local MCP plugin.
### First-run authentication
Missing credentials do not prevent the MCP process from starting. The first tool that needs Cassette returns `auth_required` and an exact private-terminal setup command. Run that command outside the agent conversation; it prompts for the password with `getpass`, verifies the account before writing anything, and stores credentials in the platform-standard Oh My Cassette config directory.
From a git checkout, the equivalent command is:
```bash
python3 scripts/setup_local_mcp.py --email you@example.com
```
Credentials may also come from process environment variables. Environment values take precedence over protected local config. Importing an existing Hermes `.env` is explicit and optional:
```bash
python3 scripts/setup_local_mcp.py --import-hermes
```
The setup command creates config directories with mode `0700` and credential files with mode `0600`, rejects symlinks and permissive files, and never persists access or refresh tokens. If the account does not have full API access, setup reports the optional browser path instead of silently changing transports.
### Guided editing flow
1. Ask Codex or Claude to edit one or more media files in the current project.
2. The plugin ingests only media inside the active project or another explicitly trusted root. It canonicalizes paths and rejects traversal and symlink escapes.
3. Describe the edit, answer any guided choices, and start the job. MCP jobs run in the background by default.
4. The host checks status with bounded 30-second long polls. During a long poll the runtime emits MCP progress notifications (visible in hosts that request them), so the wait shows live stage updates instead of a silent block. The skill monitors for up to 25 minutes, then returns the still-running job ID so you can continue later without tight-polling.
5. If Cassette needs a real user decision, answer it with the returned job ID. On hosts that support MCP elicitation, `cassette_job_status` collects the answer inline and returns the already-resumed status; other hosts use the `cassette_answer_question` round-trip. API jobs persist their private continuation metadata across host restarts.
6. When editing completes, review the result. Rendering starts only after an explicit `export` decision.
7. The result contains validated absolute paths, file URIs, MIME type, size, and an MCP resource link for each exported artifact. Large media bytes are never embedded in the tool response.
When a background job reaches a terminal state (finished, needs input, failed, or cancelled), the MCP runtime posts a best-effort local desktop notification — `osascript` on macOS, `notify-send` on Linux — so you learn a long render is done even after the monitor budget hands the job back. Set `CASSETTE_MCP_NOTIFY=0` to disable it.
Sessions are isolated by a cryptographically random session ID. Codex and Claude share host-neutral storage, so you can deliberately hand a session or job ID from one host to the other; nothing is shared implicitly.
Additional trusted media directories can be registered during setup:
```bash
python3 scripts/setup_local_mcp.py --allowed-root /absolute/path/to/media
```
Exports stay under the shared Oh My Cassette data directory at `cassette/exports/<job_id>/`. Only files contained in that job-specific directory can be returned.
### API and optional browser transports
The API transport is the default. It connects directly to the separate Cassette backend, retries authentication once after a `401`, and keeps access tokens in memory only. Because API continuation metadata is persisted, paused API jobs can resume after Codex or Claude restarts.
Browser transport is an explicit fallback for accounts without full API access and for parity diagnostics. Install pinned Playwright and Chromium only when needed:
```bash
python3 scripts/setup_local_mcp.py --with-browser
```
Browser jobs can resume while the same MCP process is alive. After a host restart they return `browser_session_lost`, because live browser objects cannot be persisted.
### MCP tools
The local MCP runtime exposes the same 14 tool names as Hermes:
| Tool | Purpose |
|---|---|
| `cassette_ingest_media` | Safely ingest trusted project media into an isolated session |
| `cassette_list_assets` | Read the session's media manifest |
| `cassette_make_prompt` | (legacy, browser transport) Build a full edit brief |
| `cassette_match_bgm` | Match Free To Use background music |
| `cassette_match_exact_bgm` | Match a specific title and artist |
| `jamendo_music_matcher` | Match structured Jamendo preferences |
| `cassette_answer_question` | Answer a guided question or resume a paused job |
| `cassette_run_job` | Run one conversational turn (`message` = the user's verbatim words); `export=true` renders |
| `cassette_job_status` | Read status or wait briefly for a change |
| `cassette_review_completion` | Review completion and explicitly approve export |
| `cassette_cancel_job` | Request cooperative cancellation |
| `cassette_timeline` | Read the live project timeline as a bounded text digest (+ optional contact sheet) |
| `cassette_edit` | Surgical no-LLM edit / undo through the manual command lane (`CASSETTE_DIRECT_EDIT=1`) |
| `cassette_config` | Get/set the session's model + thinking level (static product list, applies next turn) |
Every tool returns a structured envelope with `ok`, typed `data` or `error`, `session_id`, `job_id`, the current phase, and a runtime-derived `next_action`.
### The live editor deep link and plan review
Every session on the API transport carries ONE stable `editor_url` — a `…/try?projectSessionId=<id>&chatSessionId=<uuid>` link that opens the **real Cassette editor on the session's live conversation**: the timeline repaints as edits land, the preview plays without any server render, and the plan-review card is interactive. The link stays the same across every turn of the session (jobs are conversational turns on one persistent agent thread with memory). Anyone with the link can view *and* edit that one project (the same capability-URL posture as Cassette's public try page), so treat it like a share link.
Two concurrency semantics worth knowing: a plugin turn never cancels a run started from the open editor tab (it fails typed as `thread_busy` instead — wait and retry), while typing a fresh message in the tab DOES cancel an in-flight plugin turn (the tab takes over; existing product behavior).
**Behavior change (0.4.1):** the agent now receives the user's `message` verbatim (no brief wrapper), sessions are multi-turn on one thread, and a turn ends with the edit committed but **nothing rendered** — the envelope carries `timeline_delta`, `quality.timeline_ctl`, and a contact-sheet preview instead; pass `export=true` on the turn where the user asks to finish (browser transport keeps its render-per-job behavior). Model/thinking are session preferences via `cassette_config` — never asked upfront, defaults match the web editor.
**Behavior change (0.4.0):** on MCP hosts, `edit_plan_review` now surfaces as a real question by default (`CASSETTE_PLAN_REVIEW=user`) instead of being silently auto-approved — answer with `approve`, `revise <feedback>`, or `reject`, in chat or in the open editor tab (first answer wins). Set `CASSETTE_UNATTENDED=1` to restore the previous fully headless behavior. Status envelopes additionally carry `timeline_delta` (what changed) and `plan_progress`, fed by the run's SSE event stream (`CASSETTE_API_STREAM=0` disables).
## Ready to Use with Hermes 📼
**Now you can pick up your phone and DM your agent! Don't forget to keep your agent alive and network connected.**
In QQ or Telegram:
1. Send one or more video, image, or audio files.
2. Wait for the saved-material acknowledgement.
3. Send an edit instruction in the same conversation, or prefix it with `/edit`. Your words go to the Cassette agent verbatim — no model/optimization/BGM questionnaire. Use `/refine`, `/music`, or `/cassette_model` when you want those.
4. Each turn ends with the edit saved (timeline delta + contact-sheet preview + live link, no render). Keep editing in the same conversation — the agent remembers. Say "export" when you want the video, and the plugin renders and delivers it through the gateway when supported.
| Command | Explanation |
|---|---|
| `/new` or `/reset` | Clear your assets and start a new conversation with Hermes |
| `/edit <instruction>` | Edit the current video based on your instruction. |
| `/refine <instruction>` | Refine your edit instruction and start editing. |
| `/music <BGM request>` | Match and add a BGM to your assets based on your request. |
| `/cut` | Stop the current Cassette edit. |
| `/check_assets` | Check the uploaded assets and their status. |
| `/cassette_model` | Select the current Cassette model and thinking level. |
| `/cassette language zh` | Set Cassette’s response language to Chinese. |
| `/cassette language en` | Set Cassette’s response language to English. |
| `/cassette status <job_id>` | Check the status of a specific job. |
| `/cassette cancel <job_id>` | Cancel a specific job. |
* Assets and video state are preserved within the same conversation session. You can send additional messages in the same session to further modify the edited video results.
* Use `/new` or `/reset` to start a fresh Hermes session and clear the live Cassette browser session and your assets for that conversation.
* QQ is set to Chinese and Telegram is set to English by default, you can set language by command `/cassette language zh/en` manually.
## Update
For Codex:
```bash
codex plugin marketplace upgrade cassette-editor
codex plugin add oh-my-cassette@cassette-editor
```
For Claude Code:
```bash
claude plugin marketplace update cassette-editor
claude plugin update oh-my-cassette@cassette-editor
```
The local launcher updates its locked, plugin-managed virtual environment automatically on the next start. Browser binaries are installed only when requested.
If installed through the Hermes plugin manager:
```bash
hermes plugins update cassette
hermes gateway restart
```
If installed from a git checkout (symlink install), update the checkout:
```bash
git pull --ff-only
hermes gateway restart
```
If the plugin was installed with --copy, reinstall the copied plugin after pulling:
```bash
git pull --ff-only
python3 scripts/install_plugin.py --copy --force
hermes gateway restart
```
See [CHANGELOG.md](./CHANGELOG.md) for what changed in each release.
<details>
<summary>Migrating a symlink install to the Hermes plugin manager (optional)</summary>
Existing symlink installs keep working — migration is optional. To switch:
```bash
rm ~/.hermes/plugins/cassette # removes only the symlink, not your checkout
hermes plugins install Cassette-Editor/oh-my-cassette
hermes gateway restart
```
Your credentials in `~/.hermes/.env` and the plugin's enabled state carry over;
already-set values are not prompted again. Don't run
`hermes plugins install --force` on top of a symlink — it fails with a
confusing error instead of replacing it.
</details>
# ❓ FAQ
### Which AI agents does Oh My Cassette work with?
Claude Code, Codex, OpenCode, and Hermes Agent are supported out of the box, and any other MCP host can connect to the local `cassette` MCP server. Sessions live in a host-agnostic data directory, so you can start an edit in one host and continue it in another.
### Does it edit videos locally or in the cloud?
The plugin runs locally beside your agent and handles media ingestion, edit planning, and job supervision. The actual editing and rendering happen on [Cassette](https://trycassette.online), so you don't need a GPU or any editing software installed.
### Do I need a Cassette account?
Yes. The installer asks for your Cassette account email and password on first run and stores them locally; the plugin authenticates with Cassette on your behalf.
### What kinds of videos can it make?
Montage and story edits — vlogs, travel videos, music-driven shorts, cooking tutorials, product commercials, and game highlights — with beat-synced cuts, subtitles, picture-in-picture, transitions, and auto-matched background music. See [Case Videos](#-case-videos) for real examples with prompts and processing times.
### Can I see what the agent is doing mid-edit?
Yes. After each editing turn you get a timeline contact sheet, a per-turn preview, and a live editor deep link into the Cassette timeline; before the edit runs, the agent surfaces the plan as a storyboard sheet (one source frame per planned beat) for review.
### Can I try it without installing anything?
Yes — the [public web demo](http://43.134.224.156:8080/) runs the full workflow in your browser. It is unauthenticated and for evaluation only, so don't upload sensitive content.
### Is Oh My Cassette free and open source?
The plugin is open source under the MIT license. Cassette itself is a separate hosted service with its own account and terms.
# 🔨 Development & Troubleshooting
> [!TIP]
> Join our Discord community to connect with contributors and fellow `oh-my-cassette` users.
>
> [](https://discord.gg/qd9NY4k8d7)
## Common Q&A
### 1. Why does my Hermes Agent fail to respond on QQ or Telegram?
Please check your Hermes Agent model setup, network connectivity, and API connectivity. You can also restart the gateway:
```bash
hermes gateway stop
hermes gateway restart
```
### 2. Why is there a network issue when connecting to Cassette?
Please check if you can access https://sg.trycassette.online/agent or https://trycassette.online/agent. If not, you may check your network settings to open Cassette.
### 3. Why is the runtime slow?
The editing process depends on Hermes Agent API latency and Cassette service load. An edit task may take approximately 5–20 minutes depending on the complexity of the task, the selected model, and the thinking level.
If Hermes or Cassette gets stuck, first send `/cut` to stop the current Cassette edit, then send `/stop` to stop Hermes. After that, try again in the same session.
## Diagnose
For the Codex and Claude local MCP plugin, run:
```bash
python3 scripts/diagnose_local_mcp.py
```
It reports runtime bootstrap, protected config, transport, project/media roots, and host-neutral data paths without printing credentials. Common MCP errors are actionable: `auth_required` includes the private setup command, `source_path_not_allowed` identifies the trusted-root problem, and `browser_session_lost` explains when a browser job cannot survive a restart.
For Hermes, run:
```bash
python3 scripts/diagnose_install.py
```
The diagnostic checks:
- plugin install path (symlink installs and `hermes plugins install` git clones are both recognized);
- whether the plugin is enabled in Hermes;
- `~/.hermes/.env` values, with secrets redacted;
- `ffmpeg` and `ffprobe`;
- Playwright in the Hermes Python environment;
- Cassette URL reachability;
- Cassette login credentials by opening the Agent page in Chromium;
- Hermes gateway status.
If incoming media fails with `transcoder_missing`, run the installer again so it records explicit `CASSETTE_FFMPEG_BIN` and `CASSETTE_FFPROBE_BIN` paths:
```bash
python3 scripts/install_plugin.py \
--skip-plugin-enable \
--skip-cassette-url \
--skip-cassette-auth \
--skip-jamendo-auth \
--skip-playwright-install
```
## Configuration
Codex and Claude share the platform-standard Oh My Cassette config and data directories. Their credentials and job state are separate from Hermes. The active host project is trusted automatically; add any other media directory explicitly with `setup_local_mcp.py --allowed-root`. The web demo reads only its process environment and does not use either plugin's stored credentials.
The installer writes normal runtime settings to `~/.hermes/.env`. You can also edit that file manually.
<details>
<summary>Click here to expand configuration details</summary>
Minimum useful values:
```bash
CASSETTE_URL=https://sg.trycassette.online/agent
CASSETTE_AUTH_EMAIL=you@example.com
CASSETTE_AUTH_PASSWORD=your-generated-cassette-password
CASSETTE_ASSET_ROOT=$HOME/.hermes/cassette
CASSETTE_HEADLESS=true
CASSETTE_FORCE_H264=true
```
Default media source roots:
```text
~/.hermes/qqbot
~/.hermes/telegram
~/.hermes/weixin
~/.hermes/cache
~/.hermes/tmp
```
If your gateway stores media elsewhere:
```bash
CASSETTE_ALLOWED_SOURCE_ROOTS="$HOME/.hermes/qqbot:$HOME/.hermes/telegram:$HOME/.hermes/cache:$HOME/.hermes/tmp:/path/to/media"
```
Optional Jamendo smart BGM configuration:
```bash
JAMENDO_CLIENT_ID=your_client_id
JAMENDO_CLIENT_SECRET=your_client_secret
```
`JAMENDO_CLIENT_SECRET` is reserved for future use. It is not sent to Jamendo and is not written to job metadata.
Transparency and direct-edit flags (API transport):
| Flag | Default | Effect |
|---|---|---|
| `CASSETTE_API_STREAM` | on | SSE run-event listener feeding `timeline_delta`/`plan_progress`; `0` = pure polling |
| `CASSETTE_PLAN_REVIEW` | `user` on MCP, `auto` on gateway | surface `edit_plan_review` as a question vs auto-approve |
| `CASSETTE_UNATTENDED` | off | `1` restores fully headless auto-approve semantics |
| `CASSETTE_DIRECT_EDIT` | off | enable the `cassette_edit` surgical no-LLM lane |
| `CASSETTE_AUTH_TOKEN` | unset | pre-issued bearer, skips `/api/agent-auth/verify` (local dev/CI) |
| `CASSETTE_WEB_URL` | origin of `CASSETTE_URL` | web origin used for `editor_url` deep links |
</details>
## Development
<details>
<summary>Click here to expand development details</summary>
Create a local test environment:
```bash
uv venv .venv
uv pip install --python .venv/bin/python pytest playwright
.venv/bin/python -m playwright install chromium
```
Run checks:
```bash
python3 -m compileall -q .
.venv/bin/python -m pytest -q
```
Run the real stdio MCP process against the development environment:
```bash
CASSETTE_MCP_SKIP_BOOTSTRAP=1 \
CASSETTE_MCP_PYTHON="$PWD/.venv/bin/python" \
.venv/bin/python scripts/run_local_mcp.py
```
The deterministic test suite covers core parity, all 11 tools, real stdio protocol calls, long-polling, restart/resume behavior, state transitions, resource links, auth and filesystem security, both plugin manifests, the existing Hermes/web suite, and frontend builds. Maintainer-triggered live E2E uses repository secrets through ephemeral environment variables; PR CI stays credential-free.
Run the local Cassette E2E harness:
```bash
.venv/bin/python scripts/e2e_local_cassette.py \
--media tests/fixtures/sample.mp4 \
--instruction "Make a short captioned video under 10 seconds."
```
#### Cassette transport
The plugin can reach Cassette two ways, selected by `CASSETTE_TRANSPORT`, and **both are fully supported** — pick whichever fits your deployment:
- **`api` (default)** — calls the Cassette server APIs directly (auth → media upload → LangGraph agent run → render-from-stored-project export), no browser. This is the default because it avoids the reliability weakness of DOM scraping and needs no Playwright/Chromium. It reuses your existing `CASSETTE_AUTH_EMAIL`/`CASSETTE_AUTH_PASSWORD`; the API origin defaults to the deployed Cassette (override with `CASSETTE_API_URL` only for self-hosted). **Requirement:** the account must have full API access (for `/api/projects` and `/api/export`) — a `403`/`forbidden` error reported by the transport indicates it does not, in which case set `CASSETTE_TRANSPORT=browser`.
- **`browser`** — drives the Cassette web UI with Playwright (the original, battle-tested path). Set `CASSETTE_TRANSPORT=browser` to use it. Requires Playwright/Chromium installed. Behavior is byte-identical to the pre-transport-seam plugin.
Switching is a single env var and nothing downstream changes: both transports return the identical job-result dict, so notifications, delivery, and reporting are the same either way.
Uploaded media is linked to the agent run by session id (the upload `x-session-id` equals the run's `mediaSessionId`), and the run carries the same full session/project/run context the editor sends. Before starting the run, the transport waits for uploaded media to be fully processed — analysis evidence/embeddings (which the agent reads) and the render-source derivative (which the export needs) — so it never commits an empty edit or hits an "render-source is missing" export (tunable via `CASSETTE_API_MEDIA_READY_TIMEOUT_SEC`). Cancellation (`/cut`) is honored mid-run, agent timeouts report `timed_out`, and a run whose queue never starts fails fast as `agent_run_not_started` (tunable via `CASSETTE_API_RUN_START_TIMEOUT_SEC`) instead of hanging until the job timeout. The transport requires the Cassette backend's LangGraph run queue to be draining runs and its media render-source pipeline to be healthy.
The API path aims for behavioral parity with the browser path: it honors the user's model choice (mapping the UI label to a model id, and failing loudly under `CASSETTE_REQUIRE_MODEL_SELECTION` if unmappable) and `CASSETTE_DEFAULT_THINKING_LEVEL`; sends the model-selection notice; records live stage progress (`current_stage`, `stage_timings`, `progress_events`) and delivers a periodic **text** progress heartbeat (there is no browser to screenshot); classifies Cassette questions so routine ones auto-continue while genuine choices/missing-assets return `needs_user`; dedupes uploads across a reused session; and, like the browser path, routes a completed edit through the Hermes supervisor completion review (`cassette_review_completion`) before exporting — set `CASSETTE_API_AUTO_EXPORT=1` to export directly on agent success instead. The one irreducible difference is `final_screenshot`: with no browser, the transport substitutes a still frame extracted from the exported mp4 (`CASSETTE_API_EXPORT_THUMBNAIL`).
The same E2E flow can run on either transport, and a parity harness diffs the two outcomes (terminal status, deliverable-output count, error-code set):
```bash
# single transport
.venv/bin/python scripts/e2e_local_cassette.py --transport api \
--media tests/fixtures/sample.mp4 --instruction "Make a short captioned video."
# browser-vs-api parity (requires a full-access account; the render-from-project export endpoint
# must be live on the Cassette server)
.venv/bin/python scripts/e2e_transport_parity.py \
--media tests/fixtures/sample.mp4 --instruction "Make a short captioned video."
```
Run the web demo service:
```bash
uv venv .venv-web
uv pip install --python .venv-web/bin/python -r requirements-web.txt
.venv-web/bin/python -m playwright install chromium
# Build the browser UI (Vite/React -> web_demo/frontend/dist); requires Node.js + npm.
./web_demo/build_frontend.sh
set -a
. ./oh-my-cassette-web.env
set +a
.venv-web/bin/python -m web_demo.server
```
The browser UI is a Vite + React + TypeScript app under `web_demo/frontend`; `web_demo/build_frontend.sh` compiles it to `web_demo/frontend/dist`, which the server serves under `/static`. The build output is not committed, so build it on each deploy (and re-run it after pulling frontend changes). For live frontend iteration, `cd web_demo/frontend && npm run dev` runs Vite with `/api` proxied to `http://127.0.0.1:8088`, so run the FastAPI server alongside it.
The web demo reads `CASSETTE_*`, `DEEPSEEK_*`, and `OMC_WEB_*` from process environment variables. Users can also enter a temporary DeepSeek API key in the browser settings; it is sent only with requests to this server and is not written to the repository or server disk. A systemd template lives at `deploy/oh-my-cassette-web.service.example`, with an environment template at `deploy/oh-my-cassette-web.env.example`.
Real gateway E2E tests are opt-in only and are skipped by default:
```bash
RUN_CASSETTE_E2E=1 .venv/bin/python -m pytest -q -m e2e
```
## Public Repository Safety
Do not commit:
- `.env` or `.env.e2e`;
- real gateway tokens, account IDs, chat IDs, or raw `wxid` values;
- Cassette credentials;
- Jamendo credentials;
- downloaded media, exports, job state, browser traces, or local runtime cache.
Hermes runtime state belongs under `~/.hermes/cassette`; Codex and Claude runtime state belongs under the platform-standard Oh My Cassette data directory. Neither belongs in this repository.
</details>
## License
MIT. See [LICENSE](LICENSE).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available