clipbait 1.7.0 → 1.9.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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/index.js +64 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -22,7 +22,7 @@ Opens Clipbait so you can copy your key, then installs a `/clipbait` slash comma
22
22
  ### Claude Code
23
23
 
24
24
  ```bash
25
- claude mcp add --transport http clipbait https://app.clipbait.ai/api/mcp/cbk_live_your_key_here
25
+ claude mcp add --transport http clipbait https://app.clipbait.ai/api/mcp --header "X-API-Key: cbk_live_your_key_here"
26
26
  ```
27
27
 
28
28
  ### Cursor / Cline / Windsurf / manual
@@ -56,6 +56,9 @@ Seven tools are available to any agent:
56
56
  | `start_live_autoclip` | Auto-clip a **live** Twitch stream continuously (`channelUrl`, optional `cadenceMin`). Pro plan. |
57
57
  | `probe_video` | Get a video's duration before clipping (`url`). |
58
58
  | `download_clip` | Save a finished clip to your `~/Downloads/` folder and return the path (`jobId`, `clipIndex` 1-based). Max 500 MB. |
59
+ | `list_social_accounts` | Show which accounts are connected for posting (X, TikTok, YouTube, Facebook). |
60
+ | `schedule_post` | Schedule a clip to post on connected socials at a future time (`clipUrl`, `platforms`, `scheduledTime`, `caption`). |
61
+ | `list_scheduled_posts` / `cancel_scheduled_post` | Review or cancel scheduled posts. |
59
62
 
60
63
  ## Terminal usage
61
64
 
package/index.js CHANGED
@@ -13,6 +13,7 @@ const path = require("path");
13
13
  const CFG = path.join(os.homedir(), ".clipbait.json");
14
14
  const BASE = (process.env.CLIPBAIT_API_URL || "https://app.clipbait.ai/api").replace(/\/$/, "");
15
15
  const KEY_PAGE = "https://app.clipbait.ai/me";
16
+ const MCP_URL = "https://app.clipbait.ai/api/mcp";
16
17
 
17
18
  // Shown whenever there's no usable key. Written so an AI agent can relay it
18
19
  // verbatim to the user and they'll know exactly what to do.
@@ -74,12 +75,14 @@ function prompt(question) {
74
75
  // reads MCP servers from ~/.claude.json under projects[cwd].mcpServers (local
75
76
  // scope) and, when present, a top-level mcpServers (user scope) — we write
76
77
  // both so it shows up regardless of which directory Claude opens in.
77
- function writeMcpConfig(mcpUrl) {
78
+ function writeMcpConfig(key) {
78
79
  const cfgPath = path.join(os.homedir(), ".claude.json");
79
80
  let cfg;
80
81
  try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8")); }
81
82
  catch { return false; } // no/unreadable config → caller prints manual steps
82
- const entry = { type: "http", url: mcpUrl };
83
+ // Key travels in an X-API-Key header, not the URL path — keeps it out of the
84
+ // URL (and request logs). It still lives in the config file, same as any MCP.
85
+ const entry = { type: "http", url: MCP_URL, headers: { "X-API-Key": key } };
83
86
  cfg.mcpServers = cfg.mcpServers || {};
84
87
  cfg.mcpServers.clipbait = entry;
85
88
  const cwd = process.cwd();
@@ -104,14 +107,18 @@ argument-hint: <video-url> [what you want]
104
107
  ---
105
108
  You have the \`clipbait\` MCP tools connected. The user's request: $ARGUMENTS
106
109
 
107
- Do this, conversationally and fast:
108
- 1. If there is no video URL in the request, ask for one (YouTube, Twitch VOD, Rumble, or Ganjing World). Otherwise don't ask for confirmation, just go.
109
- 2. Read the intent and set params for \`generate_clips\`:
110
- - vertical / shorts / tiktok / reels, or nothing specified => aspectRatio "9:16". landscape / widescreen / youtube => "16:9".
111
- - if they mention a number of clips, use it as maxClips (1 to 20), otherwise 9.
112
- 3. Call \`generate_clips\`, then poll \`get_job\` with the returned jobId every 30 seconds until status is "complete". Report progress between polls.
113
- 4. When complete, list each finished clip as a numbered list: its hook and its URL.
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\`.
110
+ Guide them like a friendly assistant, conversationally and fast:
111
+ 1. Need a video URL if none was given, ask for one (YouTube, Twitch VOD, Rumble, or Ganjing World).
112
+ 2. Set params for \`generate_clips\` from their intent (offer the choice if unclear, don't over-ask):
113
+ - aspect ratio: 9:16 (vertical / TikTok / Reels) is the default; 16:9 for YouTube / landscape.
114
+ - number of clips: use a number if they mention one (120), otherwise 9.
115
+ 3. Call \`generate_clips\`, then poll \`get_job\` with the returned jobId every ~30 seconds until status is "complete". Report progress between polls.
116
+ 4. When complete, list each finished clip as a numbered list: hook and URL.
117
+ 5. Then offer next steps and act on them by calling the tools:
118
+ - \`download_clip\` — save a clip to ~/Downloads.
119
+ - Post to social: call \`list_social_accounts\` to see connected platforms, then ask which platform(s), when to post (offer Today / Tomorrow / This weekend / a specific time), and the caption (offer to draft one). Show the plan (clip · platform · time · caption) and CONFIRM before calling \`schedule_post\` for each clip. Use \`list_scheduled_posts\` / \`cancel_scheduled_post\` to review or undo.
120
+ - Regenerate at a different aspect ratio, pull more clips, or \`probe_video\` another URL.
121
+ Keep it snappy. Don't ask for confirmation to start clipping unless the URL is missing — but always confirm before posting to social.
115
122
  `;
116
123
 
117
124
  // Claude Desktop config path per-OS.
@@ -143,55 +150,66 @@ function writeDesktopConfig(key) {
143
150
  // Interactive one-command setup. Grabs the key (from arg / saved / by opening
144
151
  // the browser and prompting), installs the /clipbait command, and connects the
145
152
  // MCP server to Claude Code and Claude Desktop.
153
+ // Print the exact config for the user/agent to apply, WITHOUT touching any
154
+ // files. Used in non-interactive contexts (an agent ran this): silently
155
+ // rewriting global AI-tool config there reads as a supply-chain attack, so we
156
+ // stay transparent and let the caller apply it with full visibility.
157
+ function printManualConfig(key) {
158
+ console.log("\nTo connect Clipbait, apply one of these (nothing was written — you're not in an interactive terminal):\n");
159
+ console.log("Claude Code:");
160
+ console.log(` claude mcp add --transport http clipbait ${MCP_URL} --header "X-API-Key: ${key}"\n`);
161
+ console.log("Claude Desktop / Cursor / Cline / Windsurf — add to the MCP config:");
162
+ console.log(` ${JSON.stringify({ mcpServers: { clipbait: { command: "npx", args: ["-y", "clipbait@latest", "mcp"], env: { CLIPBAIT_API_KEY: key } } } })}\n`);
163
+ console.log(`Tip: run \`npx clipbait@latest\` in a real terminal to apply this automatically.\n`);
164
+ }
165
+
146
166
  async function runSetup(providedKey) {
167
+ const interactive = !!process.stdin.isTTY;
147
168
  let key = providedKey && providedKey.startsWith("cbk_") ? providedKey : loadKey();
148
169
 
149
170
  if (!key) {
171
+ if (!interactive) { console.error(NEED_KEY); process.exit(1); }
150
172
  console.log("\n⚡ Let's connect Clipbait to Claude.");
151
- if (!process.stdin.isTTY) {
152
- // No interactive terminal (e.g. an AI agent ran this). Print the full
153
- // guidance so the agent can pass it straight to the user.
154
- console.error(NEED_KEY);
155
- process.exit(1);
156
- }
157
173
  console.log(`\nOpening ${KEY_PAGE} so you can copy your API key…`);
158
174
  openBrowser(KEY_PAGE);
159
175
  key = (await prompt("\nPaste your API key (starts with cbk_) and press Enter:\n> ")).trim();
160
176
  }
177
+ if (!key || !key.startsWith("cbk_")) { console.error(NEED_KEY); process.exit(1); }
161
178
 
162
- if (!key || !key.startsWith("cbk_")) {
163
- console.error(NEED_KEY);
164
- process.exit(1);
165
- }
179
+ // Non-interactive (agent / pipe): don't silently modify global config. Print
180
+ // it and let the caller apply it with consent.
181
+ if (!interactive) { printManualConfig(key); return; }
182
+
183
+ // Interactive: announce exactly what will change BEFORE writing anything, so
184
+ // it never looks like a package quietly registering itself.
185
+ console.log("\n⚡ Clipbait setup will make these changes on this machine:");
186
+ console.log(" • save your API key to ~/.clipbait.json");
187
+ console.log(" • install a /clipbait slash command (~/.claude/commands/clipbait.md)");
188
+ console.log(" • register the Clipbait MCP server in Claude Code (~/.claude.json)");
189
+ console.log(" • register it in Claude Desktop, if installed\n");
166
190
 
167
191
  fs.writeFileSync(CFG, JSON.stringify({ apiKey: key }, null, 2));
168
- const mcpUrl = "https://app.clipbait.ai/api/mcp/" + key;
169
192
 
170
- // 1) Install the /clipbait slash command for Claude Code.
171
193
  const cmdDir = path.join(os.homedir(), ".claude", "commands");
172
194
  fs.mkdirSync(cmdDir, { recursive: true });
173
195
  fs.writeFileSync(path.join(cmdDir, "clipbait.md"), SLASH_COMMAND);
174
196
  console.log("✓ Installed the /clipbait command for Claude Code");
175
197
 
176
- // 2) Register the MCP server with Claude Code. Prefer editing the config
177
- // file directly (works everywhere); fall back to the `claude` CLI, then to
178
- // printing manual steps.
179
- if (writeMcpConfig(mcpUrl)) {
180
- console.log("✓ Connected the Clipbait MCP server to Claude Code");
198
+ if (writeMcpConfig(key)) {
199
+ console.log("✓ Registered the Clipbait MCP server in Claude Code");
181
200
  } else {
182
201
  const { spawnSync } = require("child_process");
183
- const r = spawnSync("claude", ["mcp", "add", "--transport", "http", "clipbait", mcpUrl], { stdio: "ignore" });
202
+ const r = spawnSync("claude", ["mcp", "add", "--transport", "http", "clipbait", MCP_URL, "--header", `X-API-Key: ${key}`], { stdio: "ignore" });
184
203
  if (!r.error && r.status === 0) {
185
- console.log("✓ Connected the Clipbait MCP server to Claude Code");
204
+ console.log("✓ Registered the Clipbait MCP server in Claude Code");
186
205
  } else {
187
- console.log("• Couldn't auto-connect the MCP server. Add it manually:");
188
- console.log(" claude mcp add --transport http clipbait " + mcpUrl);
206
+ console.log("• Couldn't auto-register the MCP server. Add it manually:");
207
+ console.log(` claude mcp add --transport http clipbait ${MCP_URL} --header "X-API-Key: ${key}"`);
189
208
  }
190
209
  }
191
210
 
192
- // 3) Also configure Claude Desktop if installed (stdio server → download_clip).
193
211
  if (writeDesktopConfig(key)) {
194
- console.log("✓ Connected Clipbait to Claude Desktop (quit + reopen it to load)");
212
+ console.log("✓ Registered Clipbait in Claude Desktop (quit + reopen it to load)");
195
213
  }
196
214
 
197
215
  console.log("\n🎬 Done! Restart Claude Code, then type:\n /clipbait https://youtube.com/watch?v=…\n");
@@ -261,6 +279,19 @@ async function runMcpServer() {
261
279
  } catch (e) { return fail(e); }
262
280
  });
263
281
 
282
+ server.tool("list_social_accounts", "List which social accounts are connected for posting (X, TikTok, YouTube, Facebook). Check before scheduling.", {},
283
+ async () => { try { const d = await api("GET", "/auth/social/status"); const map = { twitter: "x", tiktok: "tiktok", youtube: "youtube", facebook: "facebook" }; const c = Object.entries(d || {}).filter(([, v]) => v && v.connected).map(([k]) => map[k] || k); return ok(c.length ? `Connected for posting: ${c.join(", ")}` : "No social accounts connected yet. Connect them at https://app.clipbait.ai/me."); } catch (e) { return fail(e); } });
284
+
285
+ server.tool("schedule_post", "Schedule a finished clip to post on connected social accounts at a future time. Get clip URLs from get_job first; confirm the platform is connected via list_social_accounts.",
286
+ { clipUrl: z.string(), platforms: z.array(z.enum(["x", "tiktok", "youtube", "facebook"])).min(1), scheduledTime: z.string().describe("Future ISO 8601 time"), caption: z.string().optional(), clipTitle: z.string().optional(), clipJobId: z.string().optional(), clipIndex: z.number().int().optional() },
287
+ async (a) => { try { const d = await api("POST", "/scheduled-posts", { clipUrl: a.clipUrl, platforms: a.platforms, scheduledTime: a.scheduledTime, caption: a.caption || "", clipTitle: a.clipTitle, clipJobId: a.clipJobId, clipIndex: a.clipIndex }); return ok(`Scheduled to ${a.platforms.join(", ")} for ${a.scheduledTime} (post ${d.scheduledPost?._id || "?"}).`); } catch (e) { return fail(e); } });
288
+
289
+ server.tool("list_scheduled_posts", "List the user's scheduled/posted social posts.", { status: z.enum(["pending", "posted", "failed", "cancelled"]).optional() },
290
+ async (a) => { try { const d = await api("GET", "/scheduled-posts" + (a.status ? `?status=${a.status}` : "")); const posts = (d.posts || []).slice(0, 20).map((p) => `${p._id} · ${p.status} · ${(p.platforms || []).map((x) => x.name).join(",")} · ${new Date(p.scheduledTime).toISOString()} · ${p.clipTitle || ""}`).join("\n"); return ok(posts || "No scheduled posts."); } catch (e) { return fail(e); } });
291
+
292
+ server.tool("cancel_scheduled_post", "Cancel a scheduled post before it goes live.", { postId: z.string() },
293
+ async (a) => { try { await api("DELETE", "/scheduled-posts/" + a.postId); return ok(`Cancelled scheduled post ${a.postId}.`); } catch (e) { return fail(e); } });
294
+
264
295
  await server.connect(new StdioServerTransport());
265
296
  }
266
297
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clipbait",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
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",