Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Each tool lives in `src/tools/<name>.ts` and exports:
Rules:
- One tool per file.
- Zod schema is the single source of input truth.
- Handlers return a typed result — `{ path, ... }` for tools that write a file (`ingest.ts`, `inspect.ts`, `split.ts`, `undo.ts` and `delete-op.ts` return other shapes) — never raw FFmpeg output.
- Handlers return a typed result — `{ path, ... }` for tools that write a file (`ingest.ts`, `inspect.ts`, `split.ts`, `undo.ts`, `delete-op.ts` and `doctor.ts` return other shapes) — never raw FFmpeg output.
- Handlers must be idempotent given the same input + workspace state.

## FFmpeg
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ clip ui # opens http://127.0.0.1:5573
}
```

The Claude Code skill auto-discovers triggers and shells out via `npx -y` on demand. The MCP server exposes the timeline tools (show / edit / check / transcribe / undo / redo / export) over stdio; media stays confined to `MAKEMYCLIP_WORKSPACE`, and `MAKEMYCLIP_WHISPER_MODEL` is only needed for transcription and captions. Everything runs locally and offline — no API key.
The Claude Code skill auto-discovers triggers and shells out via `npx -y` on demand. The MCP server exposes the timeline tools (show / edit / check / transcribe / undo / redo / export) and a `doctor` setup check over stdio; media stays confined to `MAKEMYCLIP_WORKSPACE`, and `MAKEMYCLIP_WHISPER_MODEL` is only needed for transcription and captions. Everything runs locally and offline — no API key.

**Optional: local transcription** (for editing by transcript, removing retakes, and captions):

Expand All @@ -95,6 +95,14 @@ export MAKEMYCLIP_WHISPER_MODEL=/path/to/ggml-base.en.bin

Download a model such as `ggml-base.en.bin` from [huggingface.co/ggerganov/whisper.cpp](https://huggingface.co/ggerganov/whisper.cpp), and set `MAKEMYCLIP_WHISPER_PATH` if `whisper-cli` isn't on your `PATH`. The editor never downloads a model itself.

**Check your setup:**

```bash
npx -y @makemyclip/editor doctor # add --json for a machine-readable report
```

It checks Node, which FFmpeg runs and whether it can draw text and captions, the workspace, a caption font, and the optional whisper.cpp and model, and prints the fix for anything missing. In Claude Desktop or another MCP client, ask "is my setup OK?" and Claude runs the same check.

---

## Quick start: clean up a talking-head recording
Expand Down
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ These are the recovery loop the council called must-haves. Snapshot before any u
npx skills add MakeMyClip/editor
```

That's it.
That's it. If a command fails with a setup error (FFmpeg, its drawtext filter, whisper.cpp, a font, the workspace), or the user asks whether their setup works, run `npx -y @makemyclip/editor doctor`: it lists each check with the fix for anything missing.

## License

Expand Down
1 change: 1 addition & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- Browser UI: `clip ui` (opens http://127.0.0.1:5573)
- MCP server: `npx -y @makemyclip/editor mcp` (stdio), with `MAKEMYCLIP_WORKSPACE` set to the media folder
- Transcription (optional): install whisper.cpp (macOS: `brew install whisper-cpp`), download a model such as `ggml-base.en.bin`, set `MAKEMYCLIP_WHISPER_MODEL` to its path
- Setup check: `npx -y @makemyclip/editor doctor` (or the MCP `doctor` tool) lists what's missing and how to fix it

## Tool catalog (19 tools)

Expand Down
15 changes: 15 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { adjust } from './tools/adjust.js';
import { chromaKey } from './tools/chroma-key.js';
import { concat } from './tools/concat.js';
import { deleteOp } from './tools/delete-op.js';
import { doctor, formatDoctorReport } from './tools/doctor.js';
import { highlightReel } from './tools/highlight-reel.js';
import { ingest } from './tools/ingest.js';
import { inspect } from './tools/inspect.js';
Expand Down Expand Up @@ -74,6 +75,11 @@ MCP (drive the editor from Claude Desktop / any MCP client — no API key):
client's MCP config; it exposes the timeline tools
(show / edit / undo / redo / export).

Setup:
clip doctor [--json] Check Node, FFmpeg and its filters, the workspace, the
caption font and the optional whisper.cpp; print a fix
for each problem. Exits 1 when the editor can't run.

Session safety (these tools do not log themselves):
clip snapshot [<label>] Save the current session as a named snapshot.
clip undo [<snapshotLabel>] Pop the last op, or restore a named snapshot.
Expand Down Expand Up @@ -558,6 +564,15 @@ async function main(argv: string[]): Promise<void> {
return;
}

if (command === 'doctor') {
const result = await doctor();
process.stdout.write(
args.includes('--json') ? `${JSON.stringify(result, null, 2)}\n` : formatDoctorReport(result),
);
if (!result.ok) process.exitCode = 1;
return;
}

// ─── UI ──────────────────────────────────────────────────────────────

if (command === 'ui') {
Expand Down
28 changes: 24 additions & 4 deletions src/ffmpeg/run.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import { execa } from 'execa';
import ffmpegStatic from 'ffmpeg-static';

// Resolution order: explicit override → bundled binary → system PATH.
// The override exists so legally-cautious users can point at an LGPL-only
// or system-managed FFmpeg; see the License section in the README.
const ffmpegPath = process.env.MAKEMYCLIP_FFMPEG_PATH ?? ffmpegStatic ?? 'ffmpeg';
export const FFMPEG_PATH_ENV = 'MAKEMYCLIP_FFMPEG_PATH';

export interface FfmpegResolution {
path: string;
/** `env`: MAKEMYCLIP_FFMPEG_PATH; `bundled`: ffmpeg-static; `system`: `ffmpeg` on PATH. */
source: 'env' | 'bundled' | 'system';
}

/**
* Resolution order: explicit override → bundled binary → system PATH. The
* override exists so legally-cautious users can point at an LGPL-only or
* system-managed FFmpeg; see the License section in the README.
*/
export function resolveFfmpeg(
env: Record<string, string | undefined> = process.env,
bundled: string | null = ffmpegStatic,
): FfmpegResolution {
const override = env[FFMPEG_PATH_ENV];
if (override !== undefined) return { path: override, source: 'env' };
if (bundled !== null) return { path: bundled, source: 'bundled' };
return { path: 'ffmpeg', source: 'system' };
}

const ffmpegPath = resolveFfmpeg().path;

export interface FfmpegResult {
stdout: string;
Expand Down
20 changes: 19 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ export {
probe,
type VideoStream,
} from './ffmpeg/probe.js';
export { MissingFilterError, runFfmpeg } from './ffmpeg/run.js';
export {
FFMPEG_PATH_ENV,
type FfmpegResolution,
MissingFilterError,
resolveFfmpeg,
runFfmpeg,
} from './ffmpeg/run.js';
export {
appendOp,
atomicWriteFile,
Expand Down Expand Up @@ -272,6 +278,18 @@ export {
type DeleteOpResult,
deleteOp,
} from './tools/delete-op.js';
export {
type DoctorCheck,
type DoctorCheckId,
type DoctorDeps,
DoctorInput,
type DoctorInputType,
type DoctorResult,
type DoctorStatus,
defaultDoctorDeps,
doctor,
formatDoctorReport,
} from './tools/doctor.js';
export {
HighlightReelInput,
type HighlightReelInputType,
Expand Down
11 changes: 11 additions & 0 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { reviewCuts } from '../timeline/review.js';
import { runPlan } from '../timeline/run-plan.js';
import { transcribeClip } from '../timeline/transcript.js';
import { EditableVerbSchema } from '../timeline/verbs.js';
import { doctor } from '../tools/doctor.js';
import { WhisperLanguageSchema } from '../transcribe/whisper.js';
import {
type CompositionSummary,
Expand Down Expand Up @@ -294,6 +295,16 @@ export function buildMcpServer(): McpServer {
},
);

server.registerTool(
'doctor',
{
title: 'Check the setup',
description:
'Check that this computer has what the editor needs: Node, FFmpeg and its text and stabilization filters, a writable workspace and a caption font, plus the optional local whisper.cpp and model for transcription. Each problem comes with its fix; settings named MAKEMYCLIP_* go in the env of this server in the MCP client config. Run it when the user asks whether their setup works, or when a tool fails with a setup error.',
},
async () => jsonResult(await doctor()),
);

return server;
}

Expand Down
Loading
Loading