ffmpeg-skill 1.0.2 → 1.0.4
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.
- package/README.md +3 -3
- package/SKILL.md +1 -1
- package/bin/install.js +11 -2
- package/package.json +1 -1
- package/references/process-pitfalls.md +59 -11
package/README.md
CHANGED
|
@@ -349,11 +349,11 @@ Benchmarks live in `tests/bench_*.py`, agent evals in [evals/](evals/), results
|
|
|
349
349
|
```bash
|
|
350
350
|
npx ffmpeg-skill # Claude Code → ~/.claude/skills/ffmpeg-skill
|
|
351
351
|
npx ffmpeg-skill --cursor # Cursor → ~/.cursor/skills/ffmpeg-skill
|
|
352
|
-
npx ffmpeg-skill --codex # Codex → ~/.
|
|
352
|
+
npx ffmpeg-skill --codex # Codex → ~/.agents/skills/ffmpeg-skill (Cursor reads this location too)
|
|
353
353
|
npx ffmpeg-skill --all # all three
|
|
354
354
|
npx ffmpeg-skill --project # this project → ./.claude/skills/ffmpeg-skill
|
|
355
355
|
npx ffmpeg-skill --dir ./my-skills
|
|
356
|
-
npx ffmpeg-skill --uninstall # remove from the selected targets
|
|
356
|
+
npx ffmpeg-skill --uninstall # remove from the selected targets (--codex also clears the older ~/.codex/skills location)
|
|
357
357
|
```
|
|
358
358
|
|
|
359
359
|
Without Node: clone this repository and copy `SKILL.md`, `scripts/`, `references/` and `mcp/` into your agent's skills directory.
|
|
@@ -395,7 +395,7 @@ CI (`.github/workflows/ci.yml`) runs on every pull request and on pushes to `mai
|
|
|
395
395
|
|
|
396
396
|
`tests/test_contract.py` runs on all three OSes, but a handful of its tests build a fake `ffmpeg` as a `#!/bin/sh` script on a PATH shim to force specific FFmpeg 6/7/8/9 fixture layouts through `doctor`'s parser — that technique isn't portable to Windows, so `test_dry_run_never_runs_ffmpeg_and_writes_nothing` and the whole `DoctorDetectionTests` class (fixture-driven layout parsing) are individually `skipIf`'d there and show as `skipped`, not silently absent, in the Windows job's log. Everything else — contract schema, `reencodes_*`, `doctor.tools`, MCP derivation, and every tool exercised through the contract, including `cut.py`'s provenance fields — runs against the real Windows `ffmpeg` on every PR. See [references/ci-platform-pitfalls.md](references/ci-platform-pitfalls.md) for this and other per-OS behaviour differences already diagnosed, before spending a CI cycle re-diagnosing a platform-only failure.
|
|
397
397
|
|
|
398
|
-
**Releasing** is fully automated end to end, including the version number itself — a PR doesn't need to touch `package.json`, `docs/contract.md`, or `CHANGELOG.md` at all. Once a PR merges to `main`, `.github/workflows/release.yml` takes it from there: if nobody bumped the version by hand, it asks [release-drafter](.github/release-drafter.yml) to resolve the next version from the labels on every PR merged since the last tag (`
|
|
398
|
+
**Releasing** is fully automated end to end, including the version number itself — a PR doesn't need to touch `package.json`, `docs/contract.md`, or `CHANGELOG.md` at all. Once a PR merges to `main`, `.github/workflows/release.yml` takes it from there: if nobody bumped the version by hand, it asks [release-drafter](.github/release-drafter.yml) to resolve the next version from the labels on every PR merged since the last tag (`minor`/`feature`/`enhancement` → minor, `fix`/`bug`/`patch` → patch, an unlabeled PR defaults to `patch`; PRs labelled `chore`, `ci`, `docs` or `dependencies` are excluded, so a merge that changes no shipped file releases nothing). Most PRs don't need a label added by hand: `release-drafter.yml`'s `autolabel` job applies one automatically from the PR's title/changed files (`Fix ...` → `fix`, `Add ...`/`feat ...` → `feature`, `docs`/`.md`/`.github/`/`build(deps)` changes → `chore`) as soon as it's opened — add a label yourself only to override that. **A major version is never chosen automatically**: no label rule produces `major`, and the workflow refuses to auto-bump across a major boundary even if someone applies that label — a real major release is a deliberate `package.json` bump in a PR, which the manual path below already handles. It bumps `package.json` and `docs/contract.md`, writes a `CHANGELOG.md` section listing those PRs (and any issues they closed), and pushes that commit to `main` itself. Either way — auto-bumped or hand-bumped in the PR — it then creates the `vX.Y.Z` tag, publishes a GitHub Release with notes extracted from `CHANGELOG.md`'s matching section, and publishes the package to npm. A PR that still wants to write its own version bump and `CHANGELOG.md` prose (e.g. to explain the "why" of a release by hand) can — the automation only fills in when nobody made that call already. A push to `main` with nothing new to release is a no-op. npm publishing needs an `NPM_TOKEN` repo secret (an npm access token with publish rights on this package) — without it the tag and GitHub Release still happen, only the npm step is skipped. A repo that depends on this one (an editing skill, an agent) should pin an `ffmpeg-skill` version by tag or npm version, not by tracking `main` — a merged-but-not-yet-released commit on `main` can be ahead of the last published npm version for the few minutes between merge and this workflow completing.
|
|
399
399
|
|
|
400
400
|
Contributing a change: see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
401
401
|
|
package/SKILL.md
CHANGED
|
@@ -118,7 +118,7 @@ This skill cuts, joins, measures, syncs, exports and checks files — it execute
|
|
|
118
118
|
|
|
119
119
|
The line in general: if the same input and the same explicit parameters always produce the same, verifiable output, it belongs here. If the "right" answer depends on taste, content understanding, or what looks or sounds good, it belongs to whichever skill or agent makes that judgement — this skill only ever executes parameters it's given, never infers them from what something looks or sounds like.
|
|
120
120
|
|
|
121
|
-
If a request needs an FFmpeg feature none of the
|
|
121
|
+
If a request needs an FFmpeg feature none of the 40 scripts expose, say so and name the closest built-in option (`--dry-run` to show what would run, or a documented limitation) — never fall back to guessing a raw `ffmpeg`/`ffprobe` invocation or a hand-built filter graph outside `scripts/*.py`. A raw command bypasses every guarantee this skill makes (no shell, typed arguments, verification afterwards); it is exactly the failure mode this skill exists to prevent, so it is never the fallback when a script's flag doesn't cover something.
|
|
122
122
|
|
|
123
123
|
## Request → script
|
|
124
124
|
|
package/bin/install.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* npx ffmpeg-skill # Claude Code
|
|
9
9
|
* npx ffmpeg-skill --cursor # Cursor (~/.cursor/skills/ffmpeg-skill)
|
|
10
|
-
* npx ffmpeg-skill --codex # Codex (~/.
|
|
10
|
+
* npx ffmpeg-skill --codex # Codex (~/.agents/skills/ffmpeg-skill)
|
|
11
11
|
* npx ffmpeg-skill --all # all of the above
|
|
12
12
|
* npx ffmpeg-skill --dir ./skills # custom parent directory
|
|
13
13
|
* npx ffmpeg-skill --project # ./.claude/skills/ffmpeg-skill in the current project
|
|
@@ -69,7 +69,10 @@ if (!want.claude && !want.cursor && !want.codex && !customDir && !project) want.
|
|
|
69
69
|
|
|
70
70
|
if (want.claude) targets.push({ label: 'Claude Code', dir: path.join(home, '.claude', 'skills', SKILL_NAME) });
|
|
71
71
|
if (want.cursor) targets.push({ label: 'Cursor', dir: path.join(home, '.cursor', 'skills', SKILL_NAME) });
|
|
72
|
-
|
|
72
|
+
// Codex reads user-level skills from ~/.agents/skills (its docs list $HOME/.agents/skills,
|
|
73
|
+
// .agents/skills up the repo tree, and /etc/codex/skills), not ~/.codex/skills -- the
|
|
74
|
+
// latter was this installer's original guess and is not a location current Codex scans.
|
|
75
|
+
if (want.codex) targets.push({ label: 'Codex', dir: path.join(home, '.agents', 'skills', SKILL_NAME), legacy: path.join(home, '.codex', 'skills', SKILL_NAME) });
|
|
73
76
|
if (project) targets.push({ label: 'project (.claude/skills)', dir: path.join(process.cwd(), '.claude', 'skills', SKILL_NAME) });
|
|
74
77
|
if (customDir) targets.push({ label: 'custom', dir: path.join(path.resolve(customDir), SKILL_NAME) });
|
|
75
78
|
|
|
@@ -106,6 +109,12 @@ for (const t of targets) {
|
|
|
106
109
|
if (has('--uninstall')) {
|
|
107
110
|
fs.rmSync(t.dir, { recursive: true, force: true });
|
|
108
111
|
console.log(`removed ${t.label}: ${t.dir}`);
|
|
112
|
+
// A copy left by the installer's earlier, wrong guess at this agent's directory (see the
|
|
113
|
+
// Codex target above) would otherwise survive every uninstall from here on.
|
|
114
|
+
if (t.legacy && fs.existsSync(t.legacy)) {
|
|
115
|
+
fs.rmSync(t.legacy, { recursive: true, force: true });
|
|
116
|
+
console.log(`removed ${t.label} (older install location): ${t.legacy}`);
|
|
117
|
+
}
|
|
109
118
|
continue;
|
|
110
119
|
}
|
|
111
120
|
// Copy into a scratch directory next to the real target first, then swap it into place
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ffmpeg-skill",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Agent Skill that gives coding agents (Claude Code, Cursor, Codex) a local video editor: 40 FFmpeg tools with a machine-readable contract, contract-derived MCP server, FFmpeg capability detection, probe-first / verify-last workflow. Cut, join, silence removal, fit, captions and karaoke, overlays, motion graphics, HDR to SDR, LUTs, audio clean-up and typed dynamics, sync with drift correction, multicam, loudness, delivery checks, project rendering, batch. No API keys, no cloud, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ffmpeg",
|
|
@@ -30,23 +30,43 @@ Caught twice while adding capability metadata for new flags (`caption.py --mode
|
|
|
30
30
|
after a test failure revealed it. Do that grep first, every time `required`/`optional`
|
|
31
31
|
changes.
|
|
32
32
|
|
|
33
|
-
## Git tag push and GitHub Release creation are not reachable from this environment
|
|
33
|
+
## Git tag push and GitHub Release creation are not reachable from this environment — so they are not done from it
|
|
34
34
|
|
|
35
35
|
The git credentials available here can push to `refs/heads/*` (branches) but not
|
|
36
36
|
`refs/tags/*` — confirmed by a 403 straight from the git-receive-pack endpoint, not an
|
|
37
37
|
auth failure, meaning it's a deliberate scope restriction, not a bug to route around.
|
|
38
|
-
The GitHub MCP tool surface has no `create_release`/`create_tag` equivalent either
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
is also blocked by the outbound proxy itself (its own 403, pointing at Anthropic's docs,
|
|
42
|
-
not GitHub's).
|
|
38
|
+
The GitHub MCP tool surface has no `create_release`/`create_tag` equivalent either, and a
|
|
39
|
+
direct call to the GitHub REST API's `/releases` endpoint with a raw token is blocked by
|
|
40
|
+
the outbound proxy itself.
|
|
43
41
|
|
|
44
42
|
Confirmed once (retried the tag push a second time "just in case" before accepting it).
|
|
45
|
-
Don't retry either path a second time
|
|
46
|
-
release
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
Don't retry either path a second time. Since 0.16.11 this is moot for releases:
|
|
44
|
+
`.github/workflows/release.yml` creates the tag, the GitHub Release and the npm publish
|
|
45
|
+
from GitHub Actions on every push to `main` (README, "Development" → "Releasing"), so a
|
|
46
|
+
session never needs to push a tag at all — merging the PR is the release. The one thing
|
|
47
|
+
still worth knowing: that workflow's own `git push origin HEAD:main` (the automatic
|
|
48
|
+
version-bump commit) and its tag run under the built-in `GITHUB_TOKEN`, which by design
|
|
49
|
+
triggers no further workflow runs, so a red `tests` run for that bump commit is not
|
|
50
|
+
"missing" — it is never scheduled; the PR run before the merge is the one that counted.
|
|
51
|
+
|
|
52
|
+
## A throwaway probe must be pointed at a copy in a directory you have just verified with `pwd`, never at "the repo, probably"
|
|
53
|
+
|
|
54
|
+
While validating the auto-bump script for `release.yml`, a scratch run was meant to
|
|
55
|
+
execute against a temporary copy of the repo. It ran with the real checkout as its
|
|
56
|
+
working directory instead — a `cd` into the scratch path happened in a shell whose
|
|
57
|
+
working directory was reset between commands — and its `git add -A && git commit`,
|
|
58
|
+
`git tag v0.16.12` and two empty commits landed on the real branch and moved the real
|
|
59
|
+
local `v0.16.12` tag. Nothing was pushed, and `git reflog` plus a `git fetch --force` of
|
|
60
|
+
the tag from `origin` restored everything, but it was only noticed because `git status`
|
|
61
|
+
showed files the session had not edited.
|
|
62
|
+
|
|
63
|
+
Rule: a probe that runs `git commit`, `git tag`, `rm -rf`, or writes into the tree goes
|
|
64
|
+
in a directory created *and* verified in the same command (`cd "$DIR" && pwd && ...`),
|
|
65
|
+
not one assumed from an earlier `cd`; prefer a fixture built from a few `printf` lines
|
|
66
|
+
over a copy of the whole checkout (the copy carries the real `.git`, so a mistake there
|
|
67
|
+
is a mistake in the real history); and run `git status` on the real repo before
|
|
68
|
+
committing anything afterwards. `.claude/skills/destructive-operations/SKILL.md` says the
|
|
69
|
+
same thing for the tools themselves — it applies to the person testing them too.
|
|
50
70
|
|
|
51
71
|
## A quantitative test failing three different ways across fixture redesigns means the platform, not the fixture, is the problem
|
|
52
72
|
|
|
@@ -83,3 +103,31 @@ shallow clone leaves no tag reachable to diff against, which would make the test
|
|
|
83
103
|
skip itself in CI, not fail). If this test ever needs to skip a genuinely changelog-less
|
|
84
104
|
closed issue (a pure process note, a duplicate, a revert of an unreleased change), name the
|
|
85
105
|
exemption in the test itself with a reason — don't just widen the regex or drop the check.
|
|
106
|
+
|
|
107
|
+
## Automation that can publish must not take its "major" cue from text it did not write
|
|
108
|
+
|
|
109
|
+
On 2026-09-11, three routine Dependabot merges (`actions/upload-artifact` 4→7,
|
|
110
|
+
`actions/setup-node` 4→7, `dependabot/fetch-metadata` 2→3) were published to npm as
|
|
111
|
+
**1.0.0, 1.0.1 and 1.0.2**. The release pipeline (`release.yml`, since #129) resolves the next
|
|
112
|
+
version from PR labels; an autolabeler rule added the same day applied `major` to any PR whose
|
|
113
|
+
*body* contained the literal breaking-change marker. Dependabot PR bodies quote the upstream
|
|
114
|
+
project's release notes verbatim, and upload-artifact's v5.0.0 notes contain exactly that
|
|
115
|
+
phrase — about *their* Node runtime, nothing to do with this package. One label, three
|
|
116
|
+
accidental majors, in nine minutes, with every job green.
|
|
117
|
+
|
|
118
|
+
Two things made it worse than one bad rule: every chore merge released at all (so the first
|
|
119
|
+
accident was followed by two more before anyone looked), and nothing in the pipeline treated
|
|
120
|
+
"the major number changed" as different from any other bump.
|
|
121
|
+
|
|
122
|
+
Fixes (this commit): no autolabeler rule produces `major` any more; `chore`/`ci`/`docs`/
|
|
123
|
+
`dependencies` PRs are excluded from version resolution so they release nothing; `release.yml`
|
|
124
|
+
refuses to auto-bump across a major boundary regardless of labels; and the release job is
|
|
125
|
+
serialised (`concurrency`) so back-to-back merges cannot race on the bump push.
|
|
126
|
+
|
|
127
|
+
The general rule: a pipeline that publishes must never derive an irreversible decision (a
|
|
128
|
+
major bump, a publish, a tag) from text it did not author — PR bodies, commit messages and
|
|
129
|
+
release notes are quotations as often as they are statements. Match on labels a person
|
|
130
|
+
applied, or on files changed, and make the irreversible step refuse anything surprising rather
|
|
131
|
+
than assume the surprise was intended. And after wiring any such automation, watch the first
|
|
132
|
+
few real runs' *results* (npm, tags) rather than their exit codes: the three runs here were
|
|
133
|
+
"success" by every check the job had.
|