clipbait 1.5.0 → 1.7.0
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 +76 -19
- package/index.js +101 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,34 +1,91 @@
|
|
|
1
1
|
# clipbait
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official CLI + MCP server for [Clipbait](https://clipbait.ai) — the AI video clipping tool for streamers and clippers.
|
|
4
|
+
|
|
5
|
+
Let Claude (or any MCP-capable agent) turn long videos into viral vertical clips, check your credits, auto-clip live Twitch streams, and download finished clips to disk — on your Clipbait account. Also works as a plain terminal CLI.
|
|
6
|
+
|
|
7
|
+
## Requires
|
|
8
|
+
|
|
9
|
+
- **Node.js 18 or higher.**
|
|
10
|
+
- **A paid Clipbait account.** Generate a key at [app.clipbait.ai/me](https://app.clipbait.ai/me) → **Developers / API**. Keys are prefixed `cbk_live_`.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
### Fastest — one command (auto-configures Claude Code + Claude Desktop)
|
|
4
15
|
|
|
5
|
-
## Fastest start — one command
|
|
6
16
|
```bash
|
|
7
17
|
npx clipbait@latest
|
|
8
18
|
```
|
|
9
|
-
|
|
19
|
+
|
|
20
|
+
Opens Clipbait so you can copy your key, then installs a `/clipbait` slash command and wires the MCP server into Claude Code and Claude Desktop for you — no JSON to hand-write. Pass the key inline to skip the prompt: `npx clipbait@latest cbk_live_yourkey`. Restart Claude, then type `/clipbait <video url>`.
|
|
21
|
+
|
|
22
|
+
### Claude Code
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add --transport http clipbait https://app.clipbait.ai/api/mcp/cbk_live_your_key_here
|
|
10
26
|
```
|
|
11
|
-
|
|
27
|
+
|
|
28
|
+
### Cursor / Cline / Windsurf / manual
|
|
29
|
+
|
|
30
|
+
Drop this into the client's MCP config file (runs the local stdio server):
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"clipbait": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "clipbait@latest", "mcp"],
|
|
38
|
+
"env": { "CLIPBAIT_API_KEY": "cbk_live_your_key_here" }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
12
42
|
```
|
|
13
43
|
|
|
14
|
-
|
|
44
|
+
> **ChatGPT / claude.ai** take a remote URL instead of a local process — point them at `https://app.clipbait.ai/api/mcp/<your_api_key>` (nothing runs on your machine, nothing to update).
|
|
45
|
+
|
|
46
|
+
## Tools
|
|
47
|
+
|
|
48
|
+
Seven tools are available to any agent:
|
|
49
|
+
|
|
50
|
+
| Tool | What it does |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `generate_clips` | Start a clipping job (`videoUrl`, optional `aspectRatio` 9:16\|16:9, `maxClips` 1–20). Returns a job id. |
|
|
53
|
+
| `get_job` | Check a job's status and get finished clip URLs (`jobId`). |
|
|
54
|
+
| `list_recent_clips` | List your recent jobs and clip counts. |
|
|
55
|
+
| `get_credits` | Show credits remaining and your plan. No inputs. |
|
|
56
|
+
| `start_live_autoclip` | Auto-clip a **live** Twitch stream continuously (`channelUrl`, optional `cadenceMin`). Pro plan. |
|
|
57
|
+
| `probe_video` | Get a video's duration before clipping (`url`). |
|
|
58
|
+
| `download_clip` | Save a finished clip to your `~/Downloads/` folder and return the path (`jobId`, `clipIndex` 1-based). Max 500 MB. |
|
|
59
|
+
|
|
60
|
+
## Terminal usage
|
|
61
|
+
|
|
15
62
|
```bash
|
|
16
|
-
npx clipbait login
|
|
63
|
+
npx clipbait login cbk_live_your_key
|
|
17
64
|
npx clipbait generate "https://youtube.com/watch?v=..." --aspect 9:16 --max 9
|
|
18
|
-
npx clipbait status <jobId>
|
|
65
|
+
npx clipbait status <jobId> # get finished clip URLs
|
|
66
|
+
npx clipbait jobs # list recent jobs
|
|
67
|
+
npx clipbait live "https://twitch.tv/somestreamer"
|
|
68
|
+
npx clipbait mcp # run the stdio MCP server (used in configs above)
|
|
19
69
|
```
|
|
20
70
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
71
|
+
Auth via the `CLIPBAIT_API_KEY` env var or `~/.clipbait.json`.
|
|
72
|
+
|
|
73
|
+
## Security
|
|
74
|
+
|
|
75
|
+
- **Never commit your `CLIPBAIT_API_KEY`.** Your agent's config file contains a secret — add it to `.gitignore`, or use a secrets manager (1Password CLI, direnv) instead of inline env vars.
|
|
76
|
+
- If you leak your key, rotate it at [app.clipbait.ai/me](https://app.clipbait.ai/me) immediately — the old key stops working at once.
|
|
77
|
+
- `download_clip` links are presigned and expire in ~1 hour. Save clips promptly.
|
|
78
|
+
|
|
79
|
+
## Pricing
|
|
80
|
+
|
|
81
|
+
Requires a **paid Clipbait account**. Clip generation spends credits (1 credit ≈ 1 minute of source video). See [clipbait.ai](https://clipbait.ai).
|
|
82
|
+
|
|
83
|
+
## Links
|
|
84
|
+
|
|
85
|
+
- Clipbait: https://clipbait.ai
|
|
86
|
+
- API docs: https://app.clipbait.ai/docs
|
|
87
|
+
- Get an API key: https://app.clipbait.ai/me
|
|
31
88
|
|
|
32
|
-
|
|
89
|
+
## License
|
|
33
90
|
|
|
34
|
-
|
|
91
|
+
MIT
|
package/index.js
CHANGED
|
@@ -114,9 +114,35 @@ Do this, conversationally and fast:
|
|
|
114
114
|
5. Then offer follow-ups and act on them by calling the tools again: regenerate at a different aspect ratio, pull more clips, or probe another video with \`probe_video\`.
|
|
115
115
|
`;
|
|
116
116
|
|
|
117
|
+
// Claude Desktop config path per-OS.
|
|
118
|
+
function desktopConfigPath() {
|
|
119
|
+
const home = os.homedir();
|
|
120
|
+
if (process.platform === "darwin") return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
121
|
+
if (process.platform === "win32") return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
122
|
+
return path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Configure Claude Desktop (stdio) if it's installed. Desktop can't use the
|
|
126
|
+
// remote http MCP, and the local stdio server is what enables download_clip.
|
|
127
|
+
// Returns false (silently) when Claude Desktop isn't present.
|
|
128
|
+
function writeDesktopConfig(key) {
|
|
129
|
+
const cfgPath = desktopConfigPath();
|
|
130
|
+
try {
|
|
131
|
+
if (!fs.existsSync(path.dirname(cfgPath))) return false; // Desktop not installed
|
|
132
|
+
let cfg = {};
|
|
133
|
+
try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch { /* new file */ }
|
|
134
|
+
cfg.mcpServers = cfg.mcpServers || {};
|
|
135
|
+
cfg.mcpServers.clipbait = { command: "npx", args: ["-y", "clipbait@latest", "mcp"], env: { CLIPBAIT_API_KEY: key } };
|
|
136
|
+
const tmp = cfgPath + ".clipbait.tmp";
|
|
137
|
+
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
|
|
138
|
+
fs.renameSync(tmp, cfgPath);
|
|
139
|
+
return true;
|
|
140
|
+
} catch { return false; }
|
|
141
|
+
}
|
|
142
|
+
|
|
117
143
|
// Interactive one-command setup. Grabs the key (from arg / saved / by opening
|
|
118
144
|
// the browser and prompting), installs the /clipbait command, and connects the
|
|
119
|
-
// MCP server to Claude Code.
|
|
145
|
+
// MCP server to Claude Code and Claude Desktop.
|
|
120
146
|
async function runSetup(providedKey) {
|
|
121
147
|
let key = providedKey && providedKey.startsWith("cbk_") ? providedKey : loadKey();
|
|
122
148
|
|
|
@@ -163,12 +189,85 @@ async function runSetup(providedKey) {
|
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
191
|
|
|
192
|
+
// 3) Also configure Claude Desktop if installed (stdio server → download_clip).
|
|
193
|
+
if (writeDesktopConfig(key)) {
|
|
194
|
+
console.log("✓ Connected Clipbait to Claude Desktop (quit + reopen it to load)");
|
|
195
|
+
}
|
|
196
|
+
|
|
166
197
|
console.log("\n🎬 Done! Restart Claude Code, then type:\n /clipbait https://youtube.com/watch?v=…\n");
|
|
167
198
|
}
|
|
168
199
|
|
|
200
|
+
// Download a clip URL to ~/Downloads and return the saved path. Presigned
|
|
201
|
+
// storage URLs need no auth; our own proxy URLs need the key — only send the
|
|
202
|
+
// key to clipbait.ai hosts so we never leak it to third-party storage.
|
|
203
|
+
async function downloadToFile(url, filename) {
|
|
204
|
+
const dir = path.join(os.homedir(), "Downloads");
|
|
205
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch { /* ignore */ }
|
|
206
|
+
const dest = path.join(dir, filename);
|
|
207
|
+
const headers = /(^|\.)clipbait\.ai/i.test(new URL(url).host) ? { "X-API-Key": loadKey() } : {};
|
|
208
|
+
const res = await fetch(url, { headers });
|
|
209
|
+
if (!res.ok) throw new Error(`download failed: HTTP ${res.status}`);
|
|
210
|
+
const len = Number(res.headers.get("content-length") || 0);
|
|
211
|
+
if (len > 500 * 1024 * 1024) throw new Error("clip is larger than 500 MB");
|
|
212
|
+
fs.writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
|
|
213
|
+
return dest;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Local stdio MCP server. Lets Claude Desktop, Cursor, Cline, Windsurf, etc.
|
|
217
|
+
// drive Clipbait via `npx clipbait mcp`. Tools proxy the REST API; download_clip
|
|
218
|
+
// runs locally so it can save to disk (a remote MCP can't). NEVER write to
|
|
219
|
+
// stdout here — it's the MCP transport; diagnostics go to stderr only.
|
|
220
|
+
async function runMcpServer() {
|
|
221
|
+
if (!loadKey()) { console.error("No API key. Set CLIPBAIT_API_KEY or run: npx clipbait <cbk_live_...>"); process.exit(1); }
|
|
222
|
+
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
223
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
224
|
+
const { z } = require("zod");
|
|
225
|
+
const server = new McpServer({ name: "clipbait", version: require("./package.json").version });
|
|
226
|
+
const ok = (t) => ({ content: [{ type: "text", text: t }] });
|
|
227
|
+
const fail = (e) => ({ content: [{ type: "text", text: "Error: " + (e.message || String(e)) }], isError: true });
|
|
228
|
+
|
|
229
|
+
server.tool("generate_clips", "Generate short vertical viral clips from a long video (YouTube, Twitch VOD, Rumble, Ganjing). Returns a job id; poll get_job.",
|
|
230
|
+
{ videoUrl: z.string(), aspectRatio: z.enum(["9:16", "16:9"]).optional(), maxClips: z.number().int().min(1).max(20).optional() },
|
|
231
|
+
async (a) => { try { const d = await api("POST", "/clips/generate", { videoUrl: a.videoUrl, aspectRatio: a.aspectRatio || "9:16", maxClips: a.maxClips || 9 }); return ok(`Clip job started: ${d.jobId}${d.duplicate ? " (duplicate of a recent job — no extra credits)" : ""}. Poll get_job("${d.jobId}").`); } catch (e) { return fail(e); } });
|
|
232
|
+
|
|
233
|
+
server.tool("get_job", "Check a clip job's status and get finished clip URLs.", { jobId: z.string() },
|
|
234
|
+
async (a) => { try { const d = await api("GET", "/clips/status/" + a.jobId); const clips = (d.clips || []).map((x, i) => `#${i + 1} ${x.hook || ""} — ${x.url || "(rendering)"}`).join("\n"); return ok(`Status: ${d.status} (${d.progress ?? 0}%)\n${clips || "No clips yet."}`); } catch (e) { return fail(e); } });
|
|
235
|
+
|
|
236
|
+
server.tool("list_recent_clips", "List your recent clip jobs and how many clips each produced.", {},
|
|
237
|
+
async () => { try { const d = await api("GET", "/clips/jobs"); const jobs = (d.jobs || d || []).slice(0, 10).map((j) => `${j._id} · ${j.status} · ${(j.clips || []).length} clips · ${j.videoTitle || j.streamerName || ""}`).join("\n"); return ok(jobs || "No jobs yet."); } catch (e) { return fail(e); } });
|
|
238
|
+
|
|
239
|
+
server.tool("get_credits", "Check remaining Clipbait credits and plan before starting a job (1 credit ≈ 1 minute of source video).", {},
|
|
240
|
+
async () => { try { const d = await api("GET", "/clips/credits"); return ok(`Plan: ${d.plan}. Credits remaining: ${d.credits_remaining}${d.credits_total ? ` of ${d.credits_total}` : ""}.`); } catch (e) { return fail(e); } });
|
|
241
|
+
|
|
242
|
+
server.tool("start_live_autoclip", "Start continuous auto-clipping of a LIVE Twitch stream (Pro). Clips are generated automatically as viral moments happen.",
|
|
243
|
+
{ channelUrl: z.string(), cadenceMin: z.number().int().optional() },
|
|
244
|
+
async (a) => { try { const d = await api("POST", "/live/start", { videoUrl: a.channelUrl, cadenceMin: a.cadenceMin || 7 }); return ok(`Live auto-clipping started (${d.liveSessionId || d.session?._id || "?"}).`); } catch (e) { return fail(e); } });
|
|
245
|
+
|
|
246
|
+
server.tool("probe_video", "Get a video's duration before clipping.", { url: z.string() },
|
|
247
|
+
async (a) => { try { const d = await api("POST", "/clips/probe-url", { url: a.url }); return ok(d.durationSeconds != null ? `Duration: ${d.durationSeconds}s (~${Math.round(d.durationSeconds / 60)} min)` : "Duration unknown for this platform."); } catch (e) { return fail(e); } });
|
|
248
|
+
|
|
249
|
+
server.tool("download_clip", "Save a finished clip to the user's ~/Downloads folder and return the file path. Call after get_job shows the job is complete.",
|
|
250
|
+
{ jobId: z.string(), clipIndex: z.number().int().min(1).optional().describe("1-based, default 1") },
|
|
251
|
+
async (a) => {
|
|
252
|
+
try {
|
|
253
|
+
const d = await api("GET", "/clips/status/" + a.jobId);
|
|
254
|
+
const clips = d.clips || [];
|
|
255
|
+
if (!clips.length) return fail(new Error("No clips are ready for this job yet."));
|
|
256
|
+
const idx = (a.clipIndex || 1) - 1;
|
|
257
|
+
const clip = clips[idx];
|
|
258
|
+
if (!clip || !clip.url) return fail(new Error(`Clip #${a.clipIndex || 1} isn't available.`));
|
|
259
|
+
const p = await downloadToFile(clip.url, `clipbait-${a.jobId}-${idx + 1}.mp4`);
|
|
260
|
+
return ok(`Saved to ${p}`);
|
|
261
|
+
} catch (e) { return fail(e); }
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
await server.connect(new StdioServerTransport());
|
|
265
|
+
}
|
|
266
|
+
|
|
169
267
|
const HELP = `clipbait — AI clip generation
|
|
170
268
|
|
|
171
269
|
npx clipbait@latest set up Claude Code (recommended)
|
|
270
|
+
clipbait mcp run the stdio MCP server (for Claude Desktop/Cursor/Cline)
|
|
172
271
|
clipbait login <apiKey> just save your API key
|
|
173
272
|
clipbait generate <url> [--aspect 9:16] [--type talking_head] [--max 9]
|
|
174
273
|
clipbait status <jobId> check a job + get clip URLs
|
|
@@ -193,6 +292,7 @@ Auth: CLIPBAIT_API_KEY env var or ~/.clipbait.json (via 'clipbait login').`;
|
|
|
193
292
|
console.log("✓ Saved API key to " + CFG);
|
|
194
293
|
return;
|
|
195
294
|
}
|
|
295
|
+
if (cmd === "mcp") { await runMcpServer(); return; } // stdio MCP server for local clients
|
|
196
296
|
if (cmd === "help" || cmd === "--help" || cmd === "-h") { console.log(HELP); return; }
|
|
197
297
|
|
|
198
298
|
if (!loadKey()) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clipbait",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Clipbait CLI — turn any video into viral clips, and auto-clip live streams, from your terminal or AI agent.",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Clipbait CLI + MCP server — turn any video into viral clips, and auto-clip live streams, from your terminal or any AI agent.",
|
|
5
5
|
"bin": { "clipbait": "index.js" },
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"engines": { "node": ">=18" },
|
|
8
|
-
"keywords": ["clipbait", "clips", "video", "ai", "agent", "cli", "twitch", "shorts"],
|
|
8
|
+
"keywords": ["clipbait", "clips", "video", "ai", "agent", "mcp", "cli", "twitch", "shorts"],
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
11
|
+
"zod": "^3.25.0"
|
|
12
|
+
},
|
|
9
13
|
"license": "MIT"
|
|
10
14
|
}
|