manim-math-video-studio 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manim_math_video_studio-0.4.0/LICENSE +21 -0
- manim_math_video_studio-0.4.0/MANIFEST.in +11 -0
- manim_math_video_studio-0.4.0/PKG-INFO +68 -0
- manim_math_video_studio-0.4.0/README-pypi.md +41 -0
- manim_math_video_studio-0.4.0/README.md +64 -0
- manim_math_video_studio-0.4.0/VERSION +1 -0
- manim_math_video_studio-0.4.0/install.py +637 -0
- manim_math_video_studio-0.4.0/manim_math_video_studio/__init__.py +14 -0
- manim_math_video_studio-0.4.0/manim_math_video_studio/__main__.py +10 -0
- manim_math_video_studio-0.4.0/manim_math_video_studio.egg-info/PKG-INFO +68 -0
- manim_math_video_studio-0.4.0/manim_math_video_studio.egg-info/SOURCES.txt +14 -0
- manim_math_video_studio-0.4.0/manim_math_video_studio.egg-info/dependency_links.txt +1 -0
- manim_math_video_studio-0.4.0/manim_math_video_studio.egg-info/entry_points.txt +3 -0
- manim_math_video_studio-0.4.0/manim_math_video_studio.egg-info/top_level.txt +2 -0
- manim_math_video_studio-0.4.0/pyproject.toml +46 -0
- manim_math_video_studio-0.4.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Path of Knowledge contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: manim-math-video-studio
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Installer for the manim-math-video Agent Skill
|
|
5
|
+
Author: kappa77
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kappa77/manim-math-video-studio
|
|
8
|
+
Project-URL: Repository, https://github.com/kappa77/manim-math-video-studio
|
|
9
|
+
Project-URL: Documentation, https://github.com/kappa77/manim-math-video-studio/blob/main/INSTALL.md
|
|
10
|
+
Project-URL: Changelog, https://github.com/kappa77/manim-math-video-studio/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: agent-skills,manim,video,claude-code,codex,copilot,pi
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia :: Video
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Manim Math Video Studio
|
|
29
|
+
|
|
30
|
+
Installer for the [manim-math-video Agent Skill](https://github.com/kappa77/manim-math-video-studio) — a portable skill that turns a topic into a storyboarded, narrated Manim video with subtitles.
|
|
31
|
+
|
|
32
|
+
This package is the **thin installer**: it detects the agent harness, installs the prebuilt skill tree, and prepares one shared Python environment with Manim. The skill files themselves are downloaded from the matching GitHub release tag.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uvx manim-math-video-studio --platform claude
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
or with `pipx`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pipx run --spec manim-math-video-studio manim-math-video-studio --platform claude
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Replace `claude` with `codex`, `copilot`, or `pi`. Use `--platform all` to install every harness explicitly.
|
|
47
|
+
|
|
48
|
+
Supported harnesses:
|
|
49
|
+
|
|
50
|
+
| Harness | Invocation |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Claude Code | `/manim-math-video` |
|
|
53
|
+
| Codex | `$manim-math-video` |
|
|
54
|
+
| GitHub Copilot | `/manim-math-video` |
|
|
55
|
+
| Pi | `/skill:manim-math-video` |
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
Python 3.10+. LaTeX and `dvisvgm` are optional and only needed for equation beats. The installer prints the platform-specific install commands when either is missing.
|
|
60
|
+
|
|
61
|
+
## Links
|
|
62
|
+
|
|
63
|
+
- [Install guide](https://github.com/kappa77/manim-math-video-studio/blob/main/INSTALL.md)
|
|
64
|
+
- [Usage](https://github.com/kappa77/manim-math-video-studio/blob/main/USAGE.md)
|
|
65
|
+
- [Source](https://github.com/kappa77/manim-math-video-studio)
|
|
66
|
+
- [Changelog](https://github.com/kappa77/manim-math-video-studio/blob/main/CHANGELOG.md)
|
|
67
|
+
|
|
68
|
+
MIT licensed.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Manim Math Video Studio
|
|
2
|
+
|
|
3
|
+
Installer for the [manim-math-video Agent Skill](https://github.com/kappa77/manim-math-video-studio) — a portable skill that turns a topic into a storyboarded, narrated Manim video with subtitles.
|
|
4
|
+
|
|
5
|
+
This package is the **thin installer**: it detects the agent harness, installs the prebuilt skill tree, and prepares one shared Python environment with Manim. The skill files themselves are downloaded from the matching GitHub release tag.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uvx manim-math-video-studio --platform claude
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
or with `pipx`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pipx run --spec manim-math-video-studio manim-math-video-studio --platform claude
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Replace `claude` with `codex`, `copilot`, or `pi`. Use `--platform all` to install every harness explicitly.
|
|
20
|
+
|
|
21
|
+
Supported harnesses:
|
|
22
|
+
|
|
23
|
+
| Harness | Invocation |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Claude Code | `/manim-math-video` |
|
|
26
|
+
| Codex | `$manim-math-video` |
|
|
27
|
+
| GitHub Copilot | `/manim-math-video` |
|
|
28
|
+
| Pi | `/skill:manim-math-video` |
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
Python 3.10+. LaTeX and `dvisvgm` are optional and only needed for equation beats. The installer prints the platform-specific install commands when either is missing.
|
|
33
|
+
|
|
34
|
+
## Links
|
|
35
|
+
|
|
36
|
+
- [Install guide](https://github.com/kappa77/manim-math-video-studio/blob/main/INSTALL.md)
|
|
37
|
+
- [Usage](https://github.com/kappa77/manim-math-video-studio/blob/main/USAGE.md)
|
|
38
|
+
- [Source](https://github.com/kappa77/manim-math-video-studio)
|
|
39
|
+
- [Changelog](https://github.com/kappa77/manim-math-video-studio/blob/main/CHANGELOG.md)
|
|
40
|
+
|
|
41
|
+
MIT licensed.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Manim Math Video Studio
|
|
2
|
+
|
|
3
|
+
[](https://github.com/kappa77/manim-math-video-studio/releases/latest)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](INSTALL.md)
|
|
6
|
+
[](INSTALL.md)
|
|
7
|
+
|
|
8
|
+
A portable [Agent Skill](https://agentskills.io/specification) that turns a topic into a storyboarded, narrated Manim video — and into a consistent series. Prebuilt for Claude Code, Codex, GitHub Copilot, and Pi.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
python -c "import urllib.request as u;exec(u.urlopen('https://raw.githubusercontent.com/kappa77/manim-math-video-studio/main/install.py').read().decode())"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
It finds the agents on your machine, installs the skill for each, prepares one shared environment at `~/.manim-math-video/venv` (with `uv` when available), and verifies it. Same command on macOS, Linux, and Windows; needs Python 3.10+.
|
|
15
|
+
|
|
16
|
+
Standard Python package (v0.4.0+): `uvx manim-math-video-studio --platform claude` — one agent at a time, no clone.
|
|
17
|
+
|
|
18
|
+
Other routes — `curl -fsSL …/install.sh | bash`, `irm …/install.ps1 | iex`, or `git clone` + `python install.py` — are in [INSTALL.md](INSTALL.md).
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
*"Why 23 people are enough for a shared birthday" — 2½ minutes, produced end to end by the skill: brief → storyboard → narration → render → QA. [Watch the full episode](https://github.com/kappa77/manim-math-video-studio/releases/download/v0.2.0/manim-math-video-demo-23-people-1080p.mp4) (1080p, subtitles included).*
|
|
23
|
+
|
|
24
|
+
## What it does
|
|
25
|
+
|
|
26
|
+
- **Interview first.** It asks about the content — central question, audience, crux, visual approach, takeaway — then locks quality, voice, language, and template. A theme is not a brief.
|
|
27
|
+
- **Narration drives the clock.** Each beat gets a measured TTS duration and the animation follows it, so nothing is cut off mid-sentence and subtitles stay in sync.
|
|
28
|
+
- **Beats and visuals.** `title`, `section`, `bullets`, `compare`, `quote`, `image`, `recap`, `outro`, plus custom beats, graphs, equations, camera, and 3D.
|
|
29
|
+
- **Built for series.** A series registry keeps template, palette, typography, and narration identity stable, so episode 12 still looks like episode 1.
|
|
30
|
+
- **Verified, not vibes.** JSON-schema validation for brief and storyboard, environment checks, contact-sheet QA, and a self-test.
|
|
31
|
+
- **Any subject.** History, science, technology, literature, philosophy, art, tutorials — mathematics is one supported domain, not the whole product.
|
|
32
|
+
|
|
33
|
+
## Documentation
|
|
34
|
+
|
|
35
|
+
| Doc | Covers |
|
|
36
|
+
|---|---|
|
|
37
|
+
| [INSTALL.md](INSTALL.md) | one-command install, options, LaTeX, manual install, runtime resolution, update, uninstall, troubleshooting |
|
|
38
|
+
| [USAGE.md](USAGE.md) | quick start, command reference, pipeline, beats, templates, graphs, narration, series, validation |
|
|
39
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | repository layout, source of truth vs `dist/`, adding beats and templates, versioning |
|
|
40
|
+
| [tests/README.md](tests/README.md) | Docker-based install verification for every supported agent harness |
|
|
41
|
+
| [CHANGELOG.md](CHANGELOG.md) | what changed, release by release |
|
|
42
|
+
| [manim-math-video/SKILL.md](manim-math-video/SKILL.md) | the workflow the agent follows; the detailed manual is `manim-math-video/references/` |
|
|
43
|
+
| [Landing page](https://kappa77.github.io/manim-math-video-studio/) | one-page overview with the copy-paste install |
|
|
44
|
+
|
|
45
|
+
## Invoke
|
|
46
|
+
|
|
47
|
+
| Agent | Command |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Claude Code | `/manim-math-video` |
|
|
50
|
+
| Codex | `$manim-math-video` |
|
|
51
|
+
| GitHub Copilot | `/manim-math-video` |
|
|
52
|
+
| Pi | `/skill:manim-math-video` |
|
|
53
|
+
|
|
54
|
+
Then ask for a video, for example: *"Make a 90-second video on why the derivative of x² is 2x."*
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
Python 3.10+, Manim Community 0.19+ (0.22+ recommended), `edge-tts`, PyAV, Pillow, and NumPy. The installer provides all of them. LaTeX plus `dvisvgm` is needed only for equation beats (`--with-latex` automates MiKTeX on Windows and BasicTeX on macOS).
|
|
59
|
+
|
|
60
|
+
## License and notes
|
|
61
|
+
|
|
62
|
+
- MIT — see [LICENSE](LICENSE).
|
|
63
|
+
- Not affiliated with Manim Community or Microsoft `edge-tts`. The `dark-board` template is an original visual style, not a reproduction of any channel's branding.
|
|
64
|
+
- Verify the mathematics, and the rights to any images, music, fonts, and quotations you use.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.4.0
|
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""One-command installer for the manim-math-video agent skill.
|
|
3
|
+
|
|
4
|
+
Installs the skill tree for the agents found on this machine and prepares one
|
|
5
|
+
shared Python environment at ~/.manim-math-video/venv. Standard library only:
|
|
6
|
+
this file must run before any dependency exists.
|
|
7
|
+
|
|
8
|
+
Typical use (see install.sh / install.ps1 for the piped one-liners):
|
|
9
|
+
|
|
10
|
+
python install.py # auto-detect agents, install deps
|
|
11
|
+
python install.py --platform claude # one agent only
|
|
12
|
+
python install.py --with-latex # also install a LaTeX engine
|
|
13
|
+
python install.py --uninstall # remove skill trees and the venv
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import platform as platform_module
|
|
22
|
+
import shlex
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
import tarfile
|
|
27
|
+
import tempfile
|
|
28
|
+
import urllib.request
|
|
29
|
+
import zipfile
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
ARCHIVE_URL = os.environ.get("MMS_ARCHIVE_URL", "").strip() # optional mirror or file:// URL
|
|
34
|
+
REPO = os.environ.get("MMS_REPO", "kappa77/manim-math-video-studio")
|
|
35
|
+
PACKAGE_NAME = "manim-math-video-studio"
|
|
36
|
+
SKILL_NAME = "manim-math-video"
|
|
37
|
+
PLATFORMS = ("claude", "codex", "copilot", "pi")
|
|
38
|
+
MIN_PYTHON = (3, 10)
|
|
39
|
+
STATE_DIR = Path.home() / ".manim-math-video"
|
|
40
|
+
DEFAULT_VENV = STATE_DIR / "venv"
|
|
41
|
+
STATE_FILE = STATE_DIR / "install.json"
|
|
42
|
+
|
|
43
|
+
INVOKE = {
|
|
44
|
+
"claude": "/manim-math-video",
|
|
45
|
+
"codex": "$manim-math-video",
|
|
46
|
+
"copilot": "/manim-math-video",
|
|
47
|
+
"pi": "/skill:manim-math-video",
|
|
48
|
+
}
|
|
49
|
+
CLI = {"claude": "claude", "codex": "codex", "copilot": "copilot", "pi": "pi"}
|
|
50
|
+
|
|
51
|
+
IS_WINDOWS = os.name == "nt"
|
|
52
|
+
ECHO = True # set by --quiet, controls command echoing
|
|
53
|
+
VERBOSE = False # set by --verbose, controls pip output
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# --------------------------------------------------------------------------- #
|
|
57
|
+
# output helpers
|
|
58
|
+
# --------------------------------------------------------------------------- #
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def say(message: str = "") -> None:
|
|
62
|
+
print(message, flush=True)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def step(message: str) -> None:
|
|
66
|
+
say(f"\n==> {message}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def note(message: str) -> None:
|
|
70
|
+
say(f" {message}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def warn(message: str) -> None:
|
|
74
|
+
say(f" ! {message}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def die(message: str, code: int = 1):
|
|
78
|
+
print(f"\nERROR: {message}", file=sys.stderr, flush=True)
|
|
79
|
+
raise SystemExit(code)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --------------------------------------------------------------------------- #
|
|
83
|
+
# source tree: local checkout or download
|
|
84
|
+
# --------------------------------------------------------------------------- #
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def looks_like_source(root: Path) -> bool:
|
|
88
|
+
return (root / "platforms" / "manifest.json").is_file() and (root / "dist").is_dir()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _package_version() -> str | None:
|
|
92
|
+
"""Return the installed package version, when this is a wheel/sdist install."""
|
|
93
|
+
try:
|
|
94
|
+
from importlib.metadata import version
|
|
95
|
+
|
|
96
|
+
return version(PACKAGE_NAME)
|
|
97
|
+
except Exception: # noqa: BLE001 - running standalone from a URL or checkout
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def default_ref() -> str:
|
|
102
|
+
"""Prefer the release tag matching the installed package, then main."""
|
|
103
|
+
override = os.environ.get("MMS_REF", "").strip()
|
|
104
|
+
if override:
|
|
105
|
+
return override
|
|
106
|
+
package_version = _package_version()
|
|
107
|
+
if package_version and not any(
|
|
108
|
+
marker in package_version for marker in ("dev", "a", "b", "rc")
|
|
109
|
+
):
|
|
110
|
+
return f"v{package_version}"
|
|
111
|
+
return "main"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def installer_version() -> str:
|
|
115
|
+
"""Version to show in --version; works from a checkout and from a wheel."""
|
|
116
|
+
if "__file__" in globals():
|
|
117
|
+
local_version = Path(globals()["__file__"]).resolve().parent / "VERSION"
|
|
118
|
+
if local_version.is_file():
|
|
119
|
+
return local_version.read_text(encoding="utf-8").strip()
|
|
120
|
+
return _package_version() or "development"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
DEFAULT_REF = default_ref()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def download_source(ref: str, destination: Path) -> Path:
|
|
127
|
+
"""Download the repository archive for `ref` and return the extracted root."""
|
|
128
|
+
if ARCHIVE_URL:
|
|
129
|
+
url = ARCHIVE_URL
|
|
130
|
+
say(f" fetching {url}")
|
|
131
|
+
else:
|
|
132
|
+
kind = "tags" if ref.startswith("v") else "heads"
|
|
133
|
+
url = f"https://codeload.github.com/{REPO}/tar.gz/refs/{kind}/{ref}"
|
|
134
|
+
say(f" fetching {REPO}@{ref}")
|
|
135
|
+
archive = destination / "repo.archive"
|
|
136
|
+
try:
|
|
137
|
+
with urllib.request.urlopen(url, timeout=120) as response, archive.open("wb") as handle:
|
|
138
|
+
shutil.copyfileobj(response, handle)
|
|
139
|
+
except Exception as exc: # noqa: BLE001 - surfaced to the user as a hint
|
|
140
|
+
die(
|
|
141
|
+
f"could not download {url}: {exc}\n"
|
|
142
|
+
" Check your network, or clone the repository and run install.py from it."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
extract = destination / "src"
|
|
146
|
+
extract.mkdir()
|
|
147
|
+
try:
|
|
148
|
+
if zipfile.is_zipfile(archive):
|
|
149
|
+
with zipfile.ZipFile(archive) as bundle:
|
|
150
|
+
bundle.extractall(extract)
|
|
151
|
+
else:
|
|
152
|
+
with tarfile.open(archive) as bundle:
|
|
153
|
+
try:
|
|
154
|
+
bundle.extractall(extract, filter="data")
|
|
155
|
+
except TypeError: # Python < 3.12 has no filter argument
|
|
156
|
+
bundle.extractall(extract)
|
|
157
|
+
except Exception as exc: # noqa: BLE001
|
|
158
|
+
die(f"could not unpack the archive: {exc}")
|
|
159
|
+
|
|
160
|
+
entries = [path for path in extract.iterdir() if path.is_dir()]
|
|
161
|
+
if len(entries) != 1:
|
|
162
|
+
die("unexpected archive layout; please report this issue")
|
|
163
|
+
return entries[0]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def resolve_source(explicit: str | None, ref: str = DEFAULT_REF) -> tuple[Path, "tempfile.TemporaryDirectory | None"]:
|
|
167
|
+
if explicit:
|
|
168
|
+
root = Path(explicit).expanduser().resolve()
|
|
169
|
+
if not looks_like_source(root):
|
|
170
|
+
die(f"--source {root} is not a checkout of this repository")
|
|
171
|
+
return root, None
|
|
172
|
+
|
|
173
|
+
# When exec'd from a URL (`python -c "...exec(urlopen(...))"`) there is no
|
|
174
|
+
# __file__, so fall through to the download path.
|
|
175
|
+
here = Path(globals()["__file__"]).resolve().parent if "__file__" in globals() else None
|
|
176
|
+
if here is not None and looks_like_source(here):
|
|
177
|
+
return here, None
|
|
178
|
+
|
|
179
|
+
holder = tempfile.TemporaryDirectory(prefix="mms-install-")
|
|
180
|
+
return download_source(ref, Path(holder.name)), holder
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# --------------------------------------------------------------------------- #
|
|
184
|
+
# platform detection
|
|
185
|
+
# --------------------------------------------------------------------------- #
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def load_manifest(root: Path) -> dict:
|
|
189
|
+
try:
|
|
190
|
+
return json.loads((root / "platforms" / "manifest.json").read_text(encoding="utf-8"))
|
|
191
|
+
except Exception as exc: # noqa: BLE001
|
|
192
|
+
die(f"could not read platforms/manifest.json: {exc}")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def signals_for(platform: str) -> list[Path | None]:
|
|
196
|
+
home = Path.home()
|
|
197
|
+
found: list[Path | None] = [Path(p) if (p := shutil.which(CLI[platform])) else None]
|
|
198
|
+
if platform == "claude":
|
|
199
|
+
found.append(home / ".claude")
|
|
200
|
+
elif platform == "codex":
|
|
201
|
+
found.extend((home / ".codex", home / ".agents"))
|
|
202
|
+
elif platform == "copilot":
|
|
203
|
+
found.append(home / ".copilot")
|
|
204
|
+
elif platform == "pi":
|
|
205
|
+
found.append(home / ".pi")
|
|
206
|
+
return [item for item in found if item is not None]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def detect_platforms() -> list[str]:
|
|
210
|
+
detected = [name for name in PLATFORMS if any(item.exists() for item in signals_for(name))]
|
|
211
|
+
return detected
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def resolve_platforms(spec: str | None) -> list[str]:
|
|
215
|
+
if not spec or spec == "auto":
|
|
216
|
+
detected = detect_platforms()
|
|
217
|
+
if detected:
|
|
218
|
+
return detected
|
|
219
|
+
warn("no agent installation detected; defaulting to Claude Code")
|
|
220
|
+
note("use --platform codex,copilot,pi or --platform all to choose")
|
|
221
|
+
return ["claude"]
|
|
222
|
+
if spec == "all":
|
|
223
|
+
return list(PLATFORMS)
|
|
224
|
+
requested = [item.strip().lower() for item in spec.split(",") if item.strip()]
|
|
225
|
+
unknown = [item for item in requested if item not in PLATFORMS]
|
|
226
|
+
if unknown:
|
|
227
|
+
die(f"unknown platform(s): {', '.join(unknown)}; choose from {', '.join(PLATFORMS)}")
|
|
228
|
+
return requested
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def target_dir(platform: str, manifest: dict, root_override: str | None) -> Path:
|
|
232
|
+
config = manifest["platforms"][platform]
|
|
233
|
+
if root_override:
|
|
234
|
+
return Path(root_override).expanduser().resolve() / SKILL_NAME
|
|
235
|
+
if platform == "codex" and not (Path.home() / ".codex").exists() and (Path.home() / ".agents").exists():
|
|
236
|
+
return (Path.home() / ".agents" / "skills" / SKILL_NAME).resolve()
|
|
237
|
+
return Path(config["install_root"]).expanduser().resolve() / SKILL_NAME
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# --------------------------------------------------------------------------- #
|
|
241
|
+
# python / environment
|
|
242
|
+
# --------------------------------------------------------------------------- #
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def version_of(command: list[str]) -> tuple[int, int] | None:
|
|
246
|
+
try:
|
|
247
|
+
result = subprocess.run(
|
|
248
|
+
[*command, "-c", "import sys; print('%d.%d' % sys.version_info[:2])"],
|
|
249
|
+
capture_output=True,
|
|
250
|
+
text=True,
|
|
251
|
+
timeout=30,
|
|
252
|
+
check=False,
|
|
253
|
+
)
|
|
254
|
+
except Exception: # noqa: BLE001
|
|
255
|
+
return None
|
|
256
|
+
text = (result.stdout or "").strip()
|
|
257
|
+
if result.returncode != 0 or "." not in text:
|
|
258
|
+
return None
|
|
259
|
+
major, _, minor = text.partition(".")
|
|
260
|
+
return int(major), int(minor)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def find_interpreter() -> list[str]:
|
|
264
|
+
if tuple(sys.version_info[:2]) >= MIN_PYTHON:
|
|
265
|
+
return [sys.executable]
|
|
266
|
+
candidates: list[list[str]] = []
|
|
267
|
+
if IS_WINDOWS:
|
|
268
|
+
candidates.extend([["py", f"-{major}.{minor}"] for major in (3,) for minor in (13, 12, 11, 10)])
|
|
269
|
+
candidates.extend([[f"python3.{minor}"] for minor in (13, 12, 11, 10)] + [["python3"], ["python"]])
|
|
270
|
+
for candidate in candidates:
|
|
271
|
+
if not shutil.which(candidate[0]):
|
|
272
|
+
continue
|
|
273
|
+
found = version_of(candidate)
|
|
274
|
+
if found and found >= MIN_PYTHON:
|
|
275
|
+
return candidate
|
|
276
|
+
available = "%d.%d" % sys.version_info[:2]
|
|
277
|
+
die(
|
|
278
|
+
f"Python {MIN_PYTHON[0]}.{MIN_PYTHON[1]}+ is required, found {available}.\n"
|
|
279
|
+
" Install a newer Python (https://www.python.org/downloads/) and rerun."
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def venv_python(venv: Path) -> Path:
|
|
284
|
+
return venv / ("Scripts/python.exe" if IS_WINDOWS else "bin/python")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def run(command: list[str], *, echo: bool | None = None, capture: bool = False) -> int:
|
|
288
|
+
"""Run a command; `capture` hides its output unless it fails."""
|
|
289
|
+
if (ECHO if echo is None else echo) and not (ECHO and not VERBOSE and capture):
|
|
290
|
+
note("$ " + " ".join(str(part) for part in command))
|
|
291
|
+
if capture and not VERBOSE:
|
|
292
|
+
result = subprocess.run(
|
|
293
|
+
command, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
|
294
|
+
)
|
|
295
|
+
if result.returncode != 0 and result.stdout:
|
|
296
|
+
tail = result.stdout.strip().splitlines()[-25:]
|
|
297
|
+
for line in tail:
|
|
298
|
+
say(f" | {line}")
|
|
299
|
+
return result.returncode
|
|
300
|
+
return subprocess.run(command, check=False).returncode
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def create_venv(venv: Path, use_uv: bool) -> None:
|
|
304
|
+
if venv_python(venv).is_file():
|
|
305
|
+
note(f"reusing environment: {venv}")
|
|
306
|
+
return
|
|
307
|
+
venv.parent.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
interpreter = find_interpreter()
|
|
309
|
+
if use_uv and shutil.which("uv"):
|
|
310
|
+
note("using uv for a faster install")
|
|
311
|
+
uv_python = " ".join(interpreter) if len(interpreter) == 1 else None
|
|
312
|
+
if uv_python:
|
|
313
|
+
code = run(["uv", "venv", "--python", uv_python, str(venv)])
|
|
314
|
+
if code == 0 and venv_python(venv).is_file():
|
|
315
|
+
return
|
|
316
|
+
warn("uv could not create the environment; falling back to venv")
|
|
317
|
+
shutil.rmtree(venv, ignore_errors=True)
|
|
318
|
+
code = run([*interpreter, "-m", "venv", str(venv)])
|
|
319
|
+
if code != 0 or not venv_python(venv).is_file():
|
|
320
|
+
die("could not create the Python environment")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def install_dependencies(venv: Path, requirements: Path, use_uv: bool) -> None:
|
|
324
|
+
python = str(venv_python(venv))
|
|
325
|
+
flags = ["-q"] if not VERBOSE else []
|
|
326
|
+
if use_uv and shutil.which("uv"):
|
|
327
|
+
code = run(["uv", "pip", "install", *flags, "--python", python, "-r", str(requirements)], capture=True)
|
|
328
|
+
if code == 0:
|
|
329
|
+
return
|
|
330
|
+
warn("uv install failed; falling back to pip")
|
|
331
|
+
run([python, "-m", "pip", "install", "-q", "--upgrade", "--disable-pip-version-check", "--no-input", "pip"], capture=True)
|
|
332
|
+
code = run(
|
|
333
|
+
[
|
|
334
|
+
python,
|
|
335
|
+
"-m",
|
|
336
|
+
"pip",
|
|
337
|
+
"install",
|
|
338
|
+
*flags,
|
|
339
|
+
"--disable-pip-version-check",
|
|
340
|
+
"--no-input",
|
|
341
|
+
"-r",
|
|
342
|
+
str(requirements),
|
|
343
|
+
],
|
|
344
|
+
capture=True,
|
|
345
|
+
)
|
|
346
|
+
if code != 0:
|
|
347
|
+
die("dependency installation failed; rerun with --verbose to see the full output")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# --------------------------------------------------------------------------- #
|
|
351
|
+
# latex (optional)
|
|
352
|
+
# --------------------------------------------------------------------------- #
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def latex_ready() -> bool:
|
|
356
|
+
return bool(shutil.which("latex")) and bool(shutil.which("dvisvgm"))
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
LATEX_HINTS = {
|
|
360
|
+
"nt": [
|
|
361
|
+
"winget install --id MiKTeX.MiKTeX -e --accept-package-agreements --accept-source-agreements",
|
|
362
|
+
],
|
|
363
|
+
"darwin": [
|
|
364
|
+
"brew install --cask basictex",
|
|
365
|
+
"sudo tlmgr install dvisvgm standalone amsmath amsfonts",
|
|
366
|
+
],
|
|
367
|
+
"linux": [
|
|
368
|
+
"sudo apt install texlive texlive-latex-extra dvisvgm # Debian / Ubuntu",
|
|
369
|
+
"sudo dnf install texlive-scheme-medium dvisvgm # Fedora",
|
|
370
|
+
],
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def install_latex(enable: bool, dry_run: bool) -> None:
|
|
375
|
+
if latex_ready():
|
|
376
|
+
note("LaTeX + dvisvgm already available (equation beats enabled)")
|
|
377
|
+
return
|
|
378
|
+
key = "nt" if IS_WINDOWS else platform_module.system().lower()
|
|
379
|
+
hints = LATEX_HINTS.get(key, LATEX_HINTS["linux"])
|
|
380
|
+
if not enable:
|
|
381
|
+
note("LaTeX not installed: equation beats are unavailable, everything else works")
|
|
382
|
+
note("enable later with: python install.py --with-latex")
|
|
383
|
+
return
|
|
384
|
+
step("Installing LaTeX (dvisvgm + basic packages)")
|
|
385
|
+
if dry_run:
|
|
386
|
+
note("would run: " + "; ".join(hints))
|
|
387
|
+
return
|
|
388
|
+
if IS_WINDOWS and shutil.which("winget"):
|
|
389
|
+
code = run(hints[0].split())
|
|
390
|
+
if code != 0:
|
|
391
|
+
warn("winget could not install MiKTeX; install it manually:")
|
|
392
|
+
for hint in hints:
|
|
393
|
+
note(hint)
|
|
394
|
+
else:
|
|
395
|
+
note("MiKTeX installed; set 'Always install missing packages on the fly' in MiKTeX Console")
|
|
396
|
+
return
|
|
397
|
+
if platform_module.system().lower() == "darwin" and shutil.which("brew"):
|
|
398
|
+
run(hints[0].split())
|
|
399
|
+
run(hints[1].split())
|
|
400
|
+
return
|
|
401
|
+
warn("automatic LaTeX install is not supported here; run:")
|
|
402
|
+
for hint in hints:
|
|
403
|
+
note(hint)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
# --------------------------------------------------------------------------- #
|
|
407
|
+
# install / uninstall
|
|
408
|
+
# --------------------------------------------------------------------------- #
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def copy_skill(source: Path, target: Path) -> None:
|
|
412
|
+
preserve = target / ".venv"
|
|
413
|
+
if target.exists():
|
|
414
|
+
if preserve.exists():
|
|
415
|
+
stash = target.parent / f".{SKILL_NAME}-venv-stash"
|
|
416
|
+
shutil.rmtree(stash, ignore_errors=True)
|
|
417
|
+
preserve.rename(stash)
|
|
418
|
+
else:
|
|
419
|
+
stash = None
|
|
420
|
+
shutil.rmtree(target, ignore_errors=True)
|
|
421
|
+
if stash is not None:
|
|
422
|
+
shutil.copytree(source, target)
|
|
423
|
+
stash.rename(target / ".venv")
|
|
424
|
+
else:
|
|
425
|
+
shutil.copytree(source, target)
|
|
426
|
+
else:
|
|
427
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
428
|
+
shutil.copytree(source, target)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def read_state() -> dict:
|
|
432
|
+
try:
|
|
433
|
+
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
|
|
434
|
+
except Exception: # noqa: BLE001
|
|
435
|
+
return {}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def write_state(platforms: dict[str, str], venv: Path, version: str) -> None:
|
|
439
|
+
previous = read_state()
|
|
440
|
+
merged = dict(previous.get("platforms", {}))
|
|
441
|
+
merged.update(platforms)
|
|
442
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
443
|
+
payload = {
|
|
444
|
+
"version": version,
|
|
445
|
+
"installed_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
446
|
+
"venv": str(venv),
|
|
447
|
+
"platforms": merged,
|
|
448
|
+
}
|
|
449
|
+
STATE_FILE.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def read_version(root: Path) -> str:
|
|
453
|
+
try:
|
|
454
|
+
return (root / "VERSION").read_text(encoding="utf-8").strip() or "0.0.0"
|
|
455
|
+
except Exception: # noqa: BLE001
|
|
456
|
+
return "0.0.0"
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def verify(skill_dir: Path, venv: Path) -> int:
|
|
460
|
+
python = venv_python(venv)
|
|
461
|
+
check = skill_dir / "scripts" / "check_env.py"
|
|
462
|
+
if not python.is_file() or not check.is_file():
|
|
463
|
+
return 0
|
|
464
|
+
step("Verifying the environment")
|
|
465
|
+
environment = dict(os.environ, PYTHONWARNINGS="ignore::SyntaxWarning")
|
|
466
|
+
result = subprocess.run([str(python), str(check)], check=False, env=environment)
|
|
467
|
+
return result.returncode
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def uninstall(dry_run: bool) -> int:
|
|
471
|
+
state = read_state()
|
|
472
|
+
targets = dict(state.get("platforms", {}))
|
|
473
|
+
if not targets:
|
|
474
|
+
manifest = load_manifest(resolve_source(None)[0])
|
|
475
|
+
for name in PLATFORMS:
|
|
476
|
+
targets[name] = str(target_dir(name, manifest, None))
|
|
477
|
+
venv = Path(state.get("venv", DEFAULT_VENV)).expanduser()
|
|
478
|
+
step("Removing installed skills" + (" (dry run)" if dry_run else ""))
|
|
479
|
+
for name, path in targets.items():
|
|
480
|
+
directory = Path(path)
|
|
481
|
+
if not directory.exists():
|
|
482
|
+
continue
|
|
483
|
+
if directory.name != SKILL_NAME:
|
|
484
|
+
warn(f"skipping {directory} (unexpected name; remove it manually)")
|
|
485
|
+
continue
|
|
486
|
+
note(f"{name}: {directory}")
|
|
487
|
+
if not dry_run:
|
|
488
|
+
shutil.rmtree(directory, ignore_errors=True)
|
|
489
|
+
if venv.exists():
|
|
490
|
+
# Normalize symlinked HOME paths (macOS /var -> /private/var, Windows
|
|
491
|
+
# short paths) before deciding whether this installer owns the venv.
|
|
492
|
+
try:
|
|
493
|
+
managed_venv = venv.resolve()
|
|
494
|
+
default_venv = DEFAULT_VENV.expanduser().resolve()
|
|
495
|
+
state_root = STATE_DIR.expanduser().resolve()
|
|
496
|
+
except OSError:
|
|
497
|
+
managed_venv, default_venv, state_root = venv, DEFAULT_VENV.expanduser(), STATE_DIR.expanduser()
|
|
498
|
+
managed = managed_venv == default_venv or state_root in managed_venv.parents
|
|
499
|
+
if not managed:
|
|
500
|
+
warn(f"leaving {venv} in place (not created by this installer)")
|
|
501
|
+
else:
|
|
502
|
+
note(f"environment: {venv}")
|
|
503
|
+
if not dry_run:
|
|
504
|
+
shutil.rmtree(managed_venv, ignore_errors=True)
|
|
505
|
+
if not dry_run:
|
|
506
|
+
STATE_FILE.unlink(missing_ok=True)
|
|
507
|
+
say("\nDone." if not dry_run else "\nDry run: nothing was removed.")
|
|
508
|
+
return 0
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# --------------------------------------------------------------------------- #
|
|
512
|
+
# main
|
|
513
|
+
# --------------------------------------------------------------------------- #
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def build_plan(root: Path, manifest: dict, platforms: list[str], root_override: str | None) -> dict[str, Path]:
|
|
517
|
+
plan: dict[str, Path] = {}
|
|
518
|
+
for name in platforms:
|
|
519
|
+
plan[name] = target_dir(name, manifest, root_override)
|
|
520
|
+
return plan
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def main(argv: list[str] | None = None) -> int:
|
|
524
|
+
arguments = list(sys.argv[1:] if argv is None else argv)
|
|
525
|
+
# Flags are awkward to append to the universal `python -c` one-liner, so
|
|
526
|
+
# accept them from the environment too.
|
|
527
|
+
extra = os.environ.get("MMS_ARGS", "").strip()
|
|
528
|
+
if extra:
|
|
529
|
+
arguments = [*shlex.split(extra), *arguments]
|
|
530
|
+
|
|
531
|
+
parser = argparse.ArgumentParser(
|
|
532
|
+
prog=Path(sys.argv[0]).name,
|
|
533
|
+
description="Install the manim-math-video skill for your agents.",
|
|
534
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
535
|
+
epilog=(
|
|
536
|
+
"examples:\n"
|
|
537
|
+
" python install.py auto-detect agents, install everything\n"
|
|
538
|
+
" python install.py --platform codex one agent only\n"
|
|
539
|
+
" python install.py --with-latex also install a LaTeX engine\n"
|
|
540
|
+
" python install.py --uninstall remove skills and environment\n"
|
|
541
|
+
"\n"
|
|
542
|
+
"packaged install:\n"
|
|
543
|
+
" uvx manim-math-video-studio --platform codex\n"
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
parser.add_argument(
|
|
547
|
+
"--version",
|
|
548
|
+
action="version",
|
|
549
|
+
version=f"%(prog)s {installer_version()}",
|
|
550
|
+
help="show the installer version and exit",
|
|
551
|
+
)
|
|
552
|
+
parser.add_argument("--platform", "--platforms", dest="platform", default="auto",
|
|
553
|
+
help=f"auto (default), all, or a comma list of {', '.join(PLATFORMS)}")
|
|
554
|
+
parser.add_argument("--root", help="install into this directory instead of the personal root")
|
|
555
|
+
parser.add_argument("--venv", default=str(DEFAULT_VENV), help=f"environment directory (default: {DEFAULT_VENV})")
|
|
556
|
+
parser.add_argument("--source", help="path to this repository (default: auto-detect or download)")
|
|
557
|
+
parser.add_argument(
|
|
558
|
+
"--ref",
|
|
559
|
+
default=DEFAULT_REF,
|
|
560
|
+
help=f"git ref to download when not installed from a checkout (default: {DEFAULT_REF})",
|
|
561
|
+
)
|
|
562
|
+
parser.add_argument("--with-latex", action="store_true", help="install a LaTeX engine for equation beats")
|
|
563
|
+
parser.add_argument("--no-deps", action="store_true", help="skip Python dependencies")
|
|
564
|
+
parser.add_argument("--no-uv", action="store_true", help="do not use uv even when it is installed")
|
|
565
|
+
parser.add_argument("--dry-run", action="store_true", help="show the plan without changing anything")
|
|
566
|
+
parser.add_argument("--uninstall", action="store_true", help="remove the installed skills and environment")
|
|
567
|
+
parser.add_argument("--quiet", action="store_true", help="hide subprocess commands")
|
|
568
|
+
parser.add_argument("--verbose", action="store_true", help="show full pip output")
|
|
569
|
+
args = parser.parse_args(arguments)
|
|
570
|
+
|
|
571
|
+
global ECHO, VERBOSE
|
|
572
|
+
ECHO = not args.quiet
|
|
573
|
+
VERBOSE = args.verbose
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
if args.uninstall:
|
|
577
|
+
return uninstall(args.dry_run)
|
|
578
|
+
|
|
579
|
+
root, holder = resolve_source(args.source, args.ref or DEFAULT_REF)
|
|
580
|
+
try:
|
|
581
|
+
manifest = load_manifest(root)
|
|
582
|
+
platforms = resolve_platforms(args.platform)
|
|
583
|
+
if args.root and len(platforms) != 1:
|
|
584
|
+
die("--root installs a single platform; combine it with --platform")
|
|
585
|
+
plan = build_plan(root, manifest, platforms, args.root)
|
|
586
|
+
venv = Path(args.venv).expanduser().resolve()
|
|
587
|
+
version = read_version(root)
|
|
588
|
+
|
|
589
|
+
say("manim-math-video installer")
|
|
590
|
+
say(f" version {version}")
|
|
591
|
+
say(f" python {sys.version.split()[0]} ({sys.executable})")
|
|
592
|
+
say(f" env {venv}")
|
|
593
|
+
for name, path in plan.items():
|
|
594
|
+
say(f" {name:<9} {path}")
|
|
595
|
+
|
|
596
|
+
missing = [name for name in plan if not (root / "dist" / name / "skills" / SKILL_NAME).is_dir()]
|
|
597
|
+
if missing:
|
|
598
|
+
die(f"missing prebuilt trees for: {', '.join(missing)}; run: python platforms/build.py")
|
|
599
|
+
|
|
600
|
+
if args.dry_run:
|
|
601
|
+
say("\nDry run: nothing was changed.")
|
|
602
|
+
return 0
|
|
603
|
+
|
|
604
|
+
step("Installing the skill")
|
|
605
|
+
for name, path in plan.items():
|
|
606
|
+
copy_skill(root / "dist" / name / "skills" / SKILL_NAME, path)
|
|
607
|
+
note(f"{name} -> {path}")
|
|
608
|
+
|
|
609
|
+
if args.no_deps:
|
|
610
|
+
warn("skipped Python dependencies (--no-deps)")
|
|
611
|
+
else:
|
|
612
|
+
step("Preparing the Python environment (2-4 min on first run)")
|
|
613
|
+
create_venv(venv, use_uv=not args.no_uv)
|
|
614
|
+
install_dependencies(venv, root / "requirements.txt", use_uv=not args.no_uv)
|
|
615
|
+
for path in plan.values():
|
|
616
|
+
(path / ".venv-path").write_text(str(venv) + "\n", encoding="utf-8", newline="\n")
|
|
617
|
+
|
|
618
|
+
install_latex(args.with_latex, dry_run=False)
|
|
619
|
+
write_state({name: str(path) for name, path in plan.items()}, venv, version)
|
|
620
|
+
|
|
621
|
+
code = verify(next(iter(plan.values())), venv)
|
|
622
|
+
|
|
623
|
+
step("Ready")
|
|
624
|
+
for name in plan:
|
|
625
|
+
say(f" {manifest['platforms'][name]['label']:<16} {INVOKE[name]}")
|
|
626
|
+
say("\n Restart your agent, then ask for a video, for example:")
|
|
627
|
+
say(' "Make a 90 second video on why the derivative of x squared is 2x"')
|
|
628
|
+
if code != 0:
|
|
629
|
+
warn("the environment check reported blocking issues; see the table above")
|
|
630
|
+
return code
|
|
631
|
+
finally:
|
|
632
|
+
if holder is not None:
|
|
633
|
+
holder.cleanup()
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
if __name__ == "__main__":
|
|
637
|
+
sys.exit(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Installer entry point for the manim-math-video Agent Skill."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
from install import main
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("manim-math-video-studio")
|
|
11
|
+
except PackageNotFoundError: # running from a checkout
|
|
12
|
+
__version__ = "development"
|
|
13
|
+
|
|
14
|
+
__all__ = ["main", "__version__"]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: manim-math-video-studio
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Installer for the manim-math-video Agent Skill
|
|
5
|
+
Author: kappa77
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kappa77/manim-math-video-studio
|
|
8
|
+
Project-URL: Repository, https://github.com/kappa77/manim-math-video-studio
|
|
9
|
+
Project-URL: Documentation, https://github.com/kappa77/manim-math-video-studio/blob/main/INSTALL.md
|
|
10
|
+
Project-URL: Changelog, https://github.com/kappa77/manim-math-video-studio/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: agent-skills,manim,video,claude-code,codex,copilot,pi
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia :: Video
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Manim Math Video Studio
|
|
29
|
+
|
|
30
|
+
Installer for the [manim-math-video Agent Skill](https://github.com/kappa77/manim-math-video-studio) — a portable skill that turns a topic into a storyboarded, narrated Manim video with subtitles.
|
|
31
|
+
|
|
32
|
+
This package is the **thin installer**: it detects the agent harness, installs the prebuilt skill tree, and prepares one shared Python environment with Manim. The skill files themselves are downloaded from the matching GitHub release tag.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uvx manim-math-video-studio --platform claude
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
or with `pipx`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pipx run --spec manim-math-video-studio manim-math-video-studio --platform claude
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Replace `claude` with `codex`, `copilot`, or `pi`. Use `--platform all` to install every harness explicitly.
|
|
47
|
+
|
|
48
|
+
Supported harnesses:
|
|
49
|
+
|
|
50
|
+
| Harness | Invocation |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Claude Code | `/manim-math-video` |
|
|
53
|
+
| Codex | `$manim-math-video` |
|
|
54
|
+
| GitHub Copilot | `/manim-math-video` |
|
|
55
|
+
| Pi | `/skill:manim-math-video` |
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
Python 3.10+. LaTeX and `dvisvgm` are optional and only needed for equation beats. The installer prints the platform-specific install commands when either is missing.
|
|
60
|
+
|
|
61
|
+
## Links
|
|
62
|
+
|
|
63
|
+
- [Install guide](https://github.com/kappa77/manim-math-video-studio/blob/main/INSTALL.md)
|
|
64
|
+
- [Usage](https://github.com/kappa77/manim-math-video-studio/blob/main/USAGE.md)
|
|
65
|
+
- [Source](https://github.com/kappa77/manim-math-video-studio)
|
|
66
|
+
- [Changelog](https://github.com/kappa77/manim-math-video-studio/blob/main/CHANGELOG.md)
|
|
67
|
+
|
|
68
|
+
MIT licensed.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README-pypi.md
|
|
4
|
+
README.md
|
|
5
|
+
VERSION
|
|
6
|
+
install.py
|
|
7
|
+
pyproject.toml
|
|
8
|
+
manim_math_video_studio/__init__.py
|
|
9
|
+
manim_math_video_studio/__main__.py
|
|
10
|
+
manim_math_video_studio.egg-info/PKG-INFO
|
|
11
|
+
manim_math_video_studio.egg-info/SOURCES.txt
|
|
12
|
+
manim_math_video_studio.egg-info/dependency_links.txt
|
|
13
|
+
manim_math_video_studio.egg-info/entry_points.txt
|
|
14
|
+
manim_math_video_studio.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "manim-math-video-studio"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Installer for the manim-math-video Agent Skill"
|
|
9
|
+
readme = "README-pypi.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "kappa77" }]
|
|
14
|
+
keywords = ["agent-skills", "manim", "video", "claude-code", "codex", "copilot", "pi"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: End Users/Desktop",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Multimedia :: Video",
|
|
26
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
27
|
+
]
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/kappa77/manim-math-video-studio"
|
|
32
|
+
Repository = "https://github.com/kappa77/manim-math-video-studio"
|
|
33
|
+
Documentation = "https://github.com/kappa77/manim-math-video-studio/blob/main/INSTALL.md"
|
|
34
|
+
Changelog = "https://github.com/kappa77/manim-math-video-studio/blob/main/CHANGELOG.md"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
manim-math-video-studio = "manim_math_video_studio:main"
|
|
38
|
+
manim-math-video-install = "manim_math_video_studio:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools]
|
|
41
|
+
py-modules = ["install"]
|
|
42
|
+
packages = ["manim_math_video_studio"]
|
|
43
|
+
include-package-data = false
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.dynamic]
|
|
46
|
+
version = { file = ["VERSION"] }
|