thumbnailgpt 1.0.5 → 1.0.7

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 CHANGED
@@ -1,137 +1,323 @@
1
- # ThumbnailGPT CLI & MCP Server
1
+ # ThumbnailGPT CLI
2
2
 
3
- The official command-line interface and [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for **[ThumbnailGPT](https://thumbnailgpt.com)**.
3
+ [![npm version](https://img.shields.io/npm/v/thumbnailgpt.svg?color=ff5500)](https://www.npmjs.com/package/thumbnailgpt)
4
+ [![npm downloads](https://img.shields.io/npm/dm/thumbnailgpt.svg?color=blue)](https://www.npmjs.com/package/thumbnailgpt)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![Node Version](https://img.shields.io/node/v/thumbnailgpt.svg)](https://nodejs.org)
4
7
 
5
- Create viral, high-CTR YouTube thumbnails directly from your terminal or AI code assistants (Cursor, Claude Desktop, Windsurf).
8
+ The official command-line interface for **[ThumbnailGPT](https://thumbnailgpt.com)** — the AI-powered YouTube thumbnail generation engine built for creators, growth teams, and automated video workflows.
9
+
10
+ Generate, iterate, download, and manage viral, high-CTR YouTube thumbnails directly from your terminal or CI/CD pipelines.
11
+
12
+ [Web Studio](https://thumbnailgpt.com) • [Hosted MCP Server](https://mcp.thumbnailgpt.com) • [Documentation](https://thumbnailgpt.com/docs) • [Get API Key](https://thumbnailgpt.com/studio?settings=api-key)
13
+
14
+ ---
15
+
16
+ ## Highlights
17
+
18
+ - ⚡ **Zero-Install Execution**: Run any command immediately via `npx thumbnailgpt`.
19
+ - 🎨 **Multi-Resolution Rendering**: Generate in standard **1K** (10 credits), HD **2K** (20 credits), or Ultra-HD **4K** (30 credits).
20
+ - 📐 **Dual Aspect Ratios**: Native support for **16:9** (YouTube standard) and **9:16** (YouTube Shorts / TikTok / Reels).
21
+ - 👤 **Creator Persona Consistency**: Feature your trained creator persona across any generation using `--persona <id>`.
22
+ - 🔄 **Conversational Iteration**: Thread your edits using `--chat <chatId>` to refine text, lighting, facial expression, or background details.
23
+ - 💬 **Interactive Terminal REPL**: Full interactive terminal chat session with live preview links (`thumbnailgpt chat`).
24
+ - 🤖 **Scripting & Automation Ready**: Output raw image URLs with `--url-only` or machine-readable JSON with `--json` for shell pipes, webhooks, and GitHub Actions.
25
+ - 🛡️ **Account Deletion Guardrails**: Enforces production security; destructive deletions are restricted to the web dashboard to prevent accidental loss from automated scripts or autonomous AI agents.
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ### Run Directly via `npx` (Recommended)
32
+ You do not need to install anything globally. Simply run:
33
+ ```bash
34
+ npx thumbnailgpt <command>
35
+ ```
36
+
37
+ ### Or Install Globally
38
+ ```bash
39
+ # npm
40
+ npm install -g thumbnailgpt
41
+
42
+ # pnpm
43
+ pnpm add -g thumbnailgpt
44
+
45
+ # bun
46
+ bun add -g thumbnailgpt
47
+ ```
6
48
 
7
49
  ---
8
50
 
9
- ## Quick Start (Zero Install)
51
+ ## Quickstart
10
52
 
11
- You can run commands directly using `npx`:
53
+ ### 1. Authenticate
54
+ Obtain your Personal API Key from your **[ThumbnailGPT Settings](https://thumbnailgpt.com/studio?settings=api-key)** (`tgpt_live_...`):
12
55
 
13
56
  ```bash
14
- # 1. Log in with your Personal API Key (get one from https://thumbnailgpt.com/settings)
15
57
  npx thumbnailgpt login
58
+ ```
59
+
60
+ *Or authenticate non-interactively:*
61
+ ```bash
62
+ npx thumbnailgpt login --key tgpt_live_YOUR_API_KEY
63
+ ```
16
64
 
17
- # 2. Check your credit balance & user identity
65
+ ### 2. Verify Your Account & Credit Balance
66
+ ```bash
18
67
  npx thumbnailgpt whoami
68
+ ```
69
+ *Output:*
70
+ ```text
71
+ Logged in as: user@example.com
72
+ Subscription: active
73
+ Available Credits: 583 credits
74
+
75
+ Pricing Tiers:
76
+ 1K (Standard): 10 credits
77
+ 2K (HD): 20 credits
78
+ 4K (Ultra HD): 30 credits
79
+ ```
19
80
 
20
- # 3. Generate a thumbnail
21
- npx thumbnailgpt generate "Surviving 24 Hours in Arctic Snow" --resolution 2K --output ./thumb.webp
81
+ ### 3. Generate a Thumbnail
82
+ ```bash
83
+ npx thumbnailgpt generate "Extreme 100 Days Hardcore Survival in Minecraft" --res 2K
84
+ ```
85
+ *Output:*
86
+ ```text
87
+ ✔ Generation complete! (14s)
88
+
89
+ Thumbnail URL: https://assets.thumbnailgpt.com/generations/.../thumbnail.png
90
+ Chat Thread: https://thumbnailgpt.com/studio/3c9d1e5f-...
91
+ Chat ID: 3c9d1e5f-c648-4b3c-a6f0-811d073c22c2
92
+ Thumbnail ID: e97b4f21-12a8-48b2-89f4-5d9c2e0b1a4f
22
93
  ```
23
94
 
24
95
  ---
25
96
 
26
- ## MCP Server Setup (Cursor & Claude Desktop)
97
+ ## Command Reference
27
98
 
28
- The package includes a built-in stdio MCP server.
99
+ ### `thumbnailgpt generate <prompt>`
29
100
 
30
- ### 1. Cursor IDE Setup
31
- Add to your project's `.cursor/mcp.json` or your global Cursor settings:
101
+ Generate a new YouTube thumbnail from visual instructions, or iterate on an existing thread.
32
102
 
33
- ```json
34
- {
35
- "mcpServers": {
36
- "thumbnailgpt": {
37
- "command": "npx",
38
- "args": ["-y", "thumbnailgpt", "mcp"],
39
- "env": {
40
- "THUMBNAILGPT_API_KEY": "tgpt_live_YOUR_KEY_HERE"
41
- }
42
- }
43
- }
44
- }
103
+ ```bash
104
+ npx thumbnailgpt generate [options] <prompt>
45
105
  ```
46
106
 
47
- ### 2. Claude Desktop Setup
48
- Add to `claude_desktop_config.json`:
107
+ #### Options
49
108
 
50
- ```json
51
- {
52
- "mcpServers": {
53
- "thumbnailgpt": {
54
- "command": "npx",
55
- "args": ["-y", "thumbnailgpt", "mcp"],
56
- "env": {
57
- "THUMBNAILGPT_API_KEY": "tgpt_live_YOUR_KEY_HERE"
58
- }
59
- }
60
- }
61
- }
109
+ | Flag | Short | Default | Description |
110
+ | :--- | :---: | :---: | :--- |
111
+ | `--ratio <ratio>` | `-r` | `16:9` | Aspect ratio: `16:9` (YouTube standard) or `9:16` (Shorts / Reels) |
112
+ | `--res <resolution>` | `-q` | `1K` | Resolution quality: `1K` (10 credits), `2K` (20 credits), or `4K` (30 credits) |
113
+ | `--ref <urls...>` | | | Up to 5 public image URLs or base64 data URIs as visual references |
114
+ | `--yt <url>` | | | YouTube video URL to extract title, context, and current thumbnail |
115
+ | `--persona <personaId>` | `-p` | | Creator Persona UUID to feature in the generated thumbnail |
116
+ | `--chat <chatId>` | `-c` | | Existing chat conversation UUID to continue and iterate upon |
117
+ | `--url-only` | `-u` | `false` | Print strictly the hosted CDN image URL (ideal for shell scripts) |
118
+ | `--json` | | `false` | Output the full response as formatted JSON |
119
+ | `--async` | | `false` | Dispatch generation job and exit immediately without waiting |
120
+
121
+ #### Examples
122
+
123
+ **Ultra-HD 4K Thumbnail:**
124
+ ```bash
125
+ npx thumbnailgpt generate "Cyberpunk Underground Hacker Room neon cyan and magenta" --res 4K
126
+ ```
127
+
128
+ **YouTube Shorts (9:16) with Reference Image:**
129
+ ```bash
130
+ npx thumbnailgpt generate "Shocked screaming expression with money flying" \
131
+ --ratio 9:16 \
132
+ --ref "https://example.com/my-face.png"
133
+ ```
134
+
135
+ **Feature Your Creator Persona:**
136
+ ```bash
137
+ # First, list your active personas to grab your persona UUID:
138
+ npx thumbnailgpt personas list
139
+
140
+ # Generate featuring that persona:
141
+ npx thumbnailgpt generate "Standing in front of a $10,000,000 superyacht" --persona <PERSONA_UUID>
142
+ ```
143
+
144
+ **Recreate & Upgrade an Existing YouTube Video Thumbnail:**
145
+ ```bash
146
+ npx thumbnailgpt generate "Make the facial expression more dramatic and double the contrast" \
147
+ --yt "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
62
148
  ```
63
149
 
64
- ### Exposed MCP Tools:
65
- - `thumbnailgpt_generate`: Synthesize YouTube thumbnails with custom prompts, aspect ratios (`16:9`, `9:16`), resolutions (`1K`, `2K`, `4K`), reference images, and creator persona IDs (`personaId`).
66
- - `thumbnailgpt_get_personas`: Retrieve verified active creator personas (`id`, `name`). Raw image assets and unready personas are strictly protected and never exposed.
67
- - `thumbnailgpt_get_credits`: Query available credits and subscription status.
68
- - `thumbnailgpt_list_chats`: List recent Studio conversation sessions.
69
- - `thumbnailgpt_get_chat`: Retrieve full message history and generated thumbnail URLs.
150
+ **Iterative Refinement (Conversational Chaining):**
151
+ ```bash
152
+ # Initial generation:
153
+ npx thumbnailgpt generate "Cozy cabin in snowy mountains during sunset"
154
+ # Output returns Chat ID: 3c9d1e5f-c648-4b3c-a6f0-811d073c22c2
155
+
156
+ # Tweak the existing thumbnail:
157
+ npx thumbnailgpt generate "Add smoke rising from chimney and warm glowing windows" --chat 3c9d1e5f-c648-4b3c-a6f0-811d073c22c2
158
+ ```
70
159
 
71
160
  ---
72
161
 
73
- ## CLI Commands Reference
162
+ ### `thumbnailgpt chat [chatId]`
163
+
164
+ Launch an interactive conversational session directly in your terminal. Perfect for creative brainstorming and rapid multi-turn iterations.
74
165
 
75
- ### Authentication
76
166
  ```bash
77
- # Interactive login
78
- npx thumbnailgpt login
167
+ # Start a fresh creative session:
168
+ npx thumbnailgpt chat
169
+
170
+ # Or resume an existing conversation thread:
171
+ npx thumbnailgpt chat 3c9d1e5f-c648-4b3c-a6f0-811d073c22c2
172
+ ```
79
173
 
80
- # Clear saved credentials
81
- npx thumbnailgpt logout
174
+ Commands available inside interactive chat mode:
175
+ - Type your prompt prompt to iterate.
176
+ - `/url` — Display the latest generated thumbnail URL.
177
+ - `/open` — Open the current thumbnail in your default web browser.
178
+ - `/cost` — View credit cost tiers.
179
+ - `/help` — Display interactive mode shortcuts.
180
+ - `/exit` or `exit` — Exit interactive session.
82
181
 
83
- # Print active profile & balance
182
+ ---
183
+
184
+ ### `thumbnailgpt whoami`
185
+
186
+ Display your authenticated email, subscription tier, available credit balance, and generation pricing tiers.
187
+
188
+ ```bash
84
189
  npx thumbnailgpt whoami
85
190
  ```
86
191
 
87
- ### Creator Personas
192
+ ---
193
+
194
+ ### `thumbnailgpt chats`
195
+
196
+ Inspect and manage your generation conversation history.
197
+
88
198
  ```bash
89
- # List your active creator personas (only ready personas with safe IDs/names are shown)
90
- npx thumbnailgpt personas list
199
+ # List recent conversation threads
200
+ npx thumbnailgpt chats list
91
201
 
92
- # New personas can only be trained in the Web UI
93
- npx thumbnailgpt personas add
202
+ # View messages and generated thumbnails for a specific thread
203
+ npx thumbnailgpt chats show <chatId>
204
+
205
+ # Deletion guidance
206
+ npx thumbnailgpt chats delete <chatId>
94
207
  ```
95
208
 
96
- ### Generate Thumbnails
209
+ ---
210
+
211
+ ### `thumbnailgpt thumbnails`
212
+
213
+ Inspect metadata and retrieve URLs for generated thumbnails.
214
+
97
215
  ```bash
98
- # Standard 16:9 1K thumbnail
99
- npx thumbnailgpt generate "Extreme Minecraft Hardcore Survival"
216
+ # Retrieve full metadata (prompt, dimensions, resolution, creation date)
217
+ npx thumbnailgpt thumbnails get <thumbnailId>
218
+
219
+ # Output hosted CDN image URL directly
220
+ npx thumbnailgpt thumbnails url <thumbnailId>
221
+
222
+ # Deletion guidance
223
+ npx thumbnailgpt thumbnails delete <thumbnailId>
224
+ ```
225
+
226
+ ---
100
227
 
101
- # Generate thumbnail featuring your trained creator persona
102
- npx thumbnailgpt generate "24-Hour Survival Challenge" --persona <personaId>
228
+ ### `thumbnailgpt personas`
103
229
 
104
- # 4K resolution saved to local file
105
- npx thumbnailgpt generate "Cyberpunk Underground Hacker Room" --resolution 4K --output ./thumb.webp
230
+ Manage your custom trained AI creator personas.
106
231
 
107
- # 9:16 Shorts format with reference image URL
108
- npx thumbnailgpt generate "Shocked reaction expression" --aspect 9:16 --ref "https://example.com/face.png"
232
+ ```bash
233
+ # List all active personas with their IDs and avatar URLs
234
+ npx thumbnailgpt personas list
109
235
 
110
- # Recreate thumbnail from YouTube video URL
111
- npx thumbnailgpt generate "Upgrade lighting and contrast" --youtube "https://youtube.com/watch?v=VIDEO_ID"
236
+ # Instructions for training new creator personas
237
+ npx thumbnailgpt personas add
112
238
  ```
113
239
 
114
- ### Interactive Studio Mode
240
+ ---
241
+
242
+ ## Automation & Scripting Recipes
243
+
244
+ ### Download Thumbnail Directly to Disk
245
+ Combine `--url-only` with `curl` or `wget` to generate and download in a single line:
246
+
115
247
  ```bash
116
- # Launch interactive conversation session in terminal
117
- npx thumbnailgpt studio
248
+ curl -s -o thumbnail.png $(npx thumbnailgpt generate "Surviving 24 Hours on a Desert Island" --url-only)
249
+ ```
118
250
 
119
- # List all past studio conversations
120
- npx thumbnailgpt chats list
251
+ ### Headless CI / CD & Environment Authentication
252
+ In automated runners, GitHub Actions, or Docker containers, authenticate by setting the `THUMBNAILGPT_API_KEY` environment variable instead of running `thumbnailgpt login`:
121
253
 
122
- # View messages and image URLs of a specific conversation
123
- npx thumbnailgpt chats show <chatId>
254
+ ```bash
255
+ export THUMBNAILGPT_API_KEY="tgpt_live_YOUR_KEY"
256
+ npx thumbnailgpt generate "Weekly Tech News Round-up" --res 2K --url-only
257
+ ```
258
+
259
+ ### JSON Processing with `jq`
260
+ Extract the thumbnail URL, chat ID, and generation time programmatically:
261
+
262
+ ```bash
263
+ npx thumbnailgpt generate "MrBeast Style $1,000,000 Challenge" --json | jq '{url: .imageUrl, chatId: .chatId}'
124
264
  ```
125
265
 
126
266
  ---
127
267
 
128
268
  ## Environment Variables
129
269
 
130
- - `THUMBNAILGPT_API_KEY`: Your Personal API Key (`tgpt_live_...`). If set, overrides the stored login credentials.
131
- - `THUMBNAILGPT_API_URL`: Custom backend gateway URL (defaults to `https://thumbnailgpt.com`).
270
+ | Variable | Description | Default |
271
+ | :--- | :--- | :--- |
272
+ | `THUMBNAILGPT_API_KEY` | Personal API Key (`tgpt_live_...`). Takes precedence over saved credentials. | Stored in system keychain |
273
+ | `THUMBNAILGPT_API_URL` | Backend endpoint for enterprise or staging instances. | `https://thumbnailgpt.com` |
274
+
275
+ ---
276
+
277
+ ## Model Context Protocol (MCP) Companion
278
+
279
+ Are you using an AI coding assistant like **Cursor**, **Claude Desktop**, or **Windsurf**?
280
+
281
+ ThumbnailGPT provides a hosted, remote Model Context Protocol (MCP) server running at **`https://mcp.thumbnailgpt.com`**.
282
+
283
+ Add this to your Cursor (`~/.cursor/mcp.json`) or Claude Desktop configuration:
284
+
285
+ ```json
286
+ {
287
+ "mcpServers": {
288
+ "thumbnailgpt": {
289
+ "url": "https://mcp.thumbnailgpt.com",
290
+ "headers": {
291
+ "Authorization": "Bearer tgpt_live_YOUR_API_KEY"
292
+ }
293
+ }
294
+ }
295
+ }
296
+ ```
297
+
298
+ Now you can instruct Claude or Cursor:
299
+ > *"Create a 2K high-CTR YouTube thumbnail for our new podcast episode about Next.js 16"*
300
+
301
+ ---
302
+
303
+ ## Security & Account Protection
304
+
305
+ To prevent accidental data loss or unauthorized bulk deletions by third-party scripts and autonomous AI agents:
306
+ - Destructive deletions (`DELETE` operations) are disabled over Personal API Keys, the CLI, and the MCP server.
307
+ - Resource creation, iteration, credit checks, persona styling, and asset retrievals are fully enabled.
308
+ - Deletions can be performed safely by the account owner inside the **[ThumbnailGPT Web Studio](https://thumbnailgpt.com/studio)**.
309
+
310
+ ---
311
+
312
+ ## Support & Resources
313
+
314
+ - 🌐 **Web Studio**: [https://thumbnailgpt.com](https://thumbnailgpt.com)
315
+ - 📖 **API & CLI Docs**: [https://thumbnailgpt.com/docs](https://thumbnailgpt.com/docs)
316
+ - 🔑 **API Key Management**: [https://thumbnailgpt.com/studio?settings=api-key](https://thumbnailgpt.com/studio?settings=api-key)
317
+ - 💬 **Support Email**: [support@thumbnailgpt.com](mailto:support@thumbnailgpt.com)
132
318
 
133
319
  ---
134
320
 
135
321
  ## License
136
322
 
137
- MIT © [ThumbnailGPT](https://thumbnailgpt.com)
323
+ [MIT](LICENSE) © [ThumbnailGPT](https://thumbnailgpt.com)
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env node
2
-
3
- import("../dist/index.js").catch((err) => {
4
- console.error("Failed to run thumbnailgpt CLI:", err);
5
- process.exit(1);
6
- });
1
+ #!/usr/bin/env node
2
+
3
+ import("../dist/index.js").catch((err) => {
4
+ console.error("Failed to run thumbnailgpt CLI:", err);
5
+ process.exit(1);
6
+ });
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CreditBalanceResponse, StudioGenerateOptions, StudioDispatchResult, StudioConversationResponse, GenerationSettledResult, PersonaItem } from "./types.js";
1
+ import type { CreditBalanceResponse, StudioGenerateOptions, StudioDispatchResult, StudioConversationResponse, GenerationSettledResult, PersonaItem, ThumbnailItem } from "./types.js";
2
2
  export declare class ThumbnailGPTClient {
3
3
  private apiKey;
4
4
  private apiUrl;
@@ -30,6 +30,18 @@ export declare class ThumbnailGPTClient {
30
30
  * Asynchronously dispatch and poll until completion
31
31
  */
32
32
  generateAndWait(options: StudioGenerateOptions, onProgress?: (elapsedSec: number, status: string) => void, timeoutSec?: number): Promise<GenerationSettledResult>;
33
+ /**
34
+ * Delete a chat conversation and all associated messages
35
+ */
36
+ deleteChat(chatId: string): Promise<void>;
37
+ /**
38
+ * Get thumbnail details by ID
39
+ */
40
+ getThumbnail(thumbnailId: string): Promise<ThumbnailItem>;
41
+ /**
42
+ * Delete a specific thumbnail by ID
43
+ */
44
+ deleteThumbnail(thumbnailId: string): Promise<void>;
33
45
  /**
34
46
  * Download image to local filesystem
35
47
  */
package/dist/client.js CHANGED
@@ -8,7 +8,7 @@ export class ThumbnailGPTClient {
8
8
  this.apiKey = (apiKey || getApiKey() || "").trim();
9
9
  this.apiUrl = (apiUrl || getApiUrl() || "").replace(/\/$/, "");
10
10
  if (!this.apiKey) {
11
- throw new Error("Missing ThumbnailGPT API Key. Run 'thumbnailgpt login' or set the THUMBNAILGPT_API_KEY environment variable.");
11
+ throw new Error("Missing ThumbnailGPT API Key. Run 'thumbnailgpt login' or set the THUMBNAILGPT_API_KEY environment variable. Get your API key at https://thumbnailgpt.com/studio?settings=api-key");
12
12
  }
13
13
  }
14
14
  async request(endpoint, options = {}) {
@@ -16,6 +16,8 @@ export class ThumbnailGPTClient {
16
16
  const headers = {
17
17
  "Content-Type": "application/json",
18
18
  Authorization: `Bearer ${this.apiKey}`,
19
+ Origin: this.apiUrl,
20
+ "User-Agent": "thumbnailgpt-cli/1.0.7",
19
21
  ...options.headers,
20
22
  };
21
23
  const res = await fetch(url, { ...options, headers });
@@ -41,6 +43,34 @@ export class ThumbnailGPTClient {
41
43
  * Dispatch a studio generation job
42
44
  */
43
45
  async dispatchStudio(options) {
46
+ if (options.referenceImages && options.referenceImages.length > 5) {
47
+ throw new Error("A maximum of 5 reference images is allowed.");
48
+ }
49
+ const rawRefImages = (options.referenceImages || []).slice(0, 5);
50
+ const processedRefImages = [];
51
+ for (const img of rawRefImages) {
52
+ if (typeof img === "string") {
53
+ if (img.startsWith("http://") || img.startsWith("https://") || img.startsWith("data:")) {
54
+ processedRefImages.push(img);
55
+ }
56
+ else if (fs.existsSync(img)) {
57
+ const ext = path.extname(img).toLowerCase().replace(".", "");
58
+ const mimeType = ext === "jpg" || ext === "jpeg"
59
+ ? "image/jpeg"
60
+ : ext === "png"
61
+ ? "image/png"
62
+ : ext === "webp"
63
+ ? "image/webp"
64
+ : "image/jpeg";
65
+ const buffer = fs.readFileSync(img);
66
+ const base64 = buffer.toString("base64");
67
+ processedRefImages.push(`data:${mimeType};base64,${base64}`);
68
+ }
69
+ else {
70
+ processedRefImages.push(img);
71
+ }
72
+ }
73
+ }
44
74
  return this.request("/api/studio", {
45
75
  method: "POST",
46
76
  body: JSON.stringify({
@@ -49,9 +79,9 @@ export class ThumbnailGPTClient {
49
79
  prompt: options.prompt,
50
80
  aspectRatio: options.aspectRatio || "16:9",
51
81
  resolution: options.resolution || "1K",
52
- referenceImages: options.referenceImages || [],
53
- youtubeUrl: options.youtubeUrl,
54
- personaId: options.personaId,
82
+ referenceImages: processedRefImages,
83
+ youtubeUrl: options.youtubeUrl || undefined,
84
+ personaId: options.personaId || undefined,
55
85
  baseImage: options.baseImage,
56
86
  }),
57
87
  });
@@ -149,6 +179,29 @@ export class ThumbnailGPTClient {
149
179
  error: `Generation timed out after ${timeoutSec}s. Check status later with 'thumbnailgpt chats show ${chatId}'`,
150
180
  };
151
181
  }
182
+ /**
183
+ * Delete a chat conversation and all associated messages
184
+ */
185
+ async deleteChat(chatId) {
186
+ await this.request(`/api/studio/${chatId}`, {
187
+ method: "DELETE",
188
+ });
189
+ }
190
+ /**
191
+ * Get thumbnail details by ID
192
+ */
193
+ async getThumbnail(thumbnailId) {
194
+ const res = await this.request(`/api/thumbnails/${thumbnailId}`);
195
+ return res.thumbnail;
196
+ }
197
+ /**
198
+ * Delete a specific thumbnail by ID
199
+ */
200
+ async deleteThumbnail(thumbnailId) {
201
+ await this.request(`/api/thumbnails/${thumbnailId}`, {
202
+ method: "DELETE",
203
+ });
204
+ }
152
205
  /**
153
206
  * Download image to local filesystem
154
207
  */
package/dist/index.js CHANGED
@@ -4,12 +4,58 @@ import ora from "ora";
4
4
  import pc from "picocolors";
5
5
  import { setApiKey, clearApiKey, setApiUrl } from "./config.js";
6
6
  import { ThumbnailGPTClient } from "./client.js";
7
- import { runMcpServer } from "./mcp.js";
7
+ /**
8
+ * Safely handle errors in CLI commands without crashing libuv timers or exposing internal server details
9
+ */
10
+ function handleError(err, spinner) {
11
+ if (spinner) {
12
+ try {
13
+ spinner.stop();
14
+ }
15
+ catch { }
16
+ }
17
+ const rawMsg = String(err?.message || err || "").trim();
18
+ const status = Number(err?.status || 0);
19
+ let friendlyMessage = rawMsg;
20
+ // Mask database, internal, or raw HTML errors
21
+ if (status === 401 ||
22
+ rawMsg.toLowerCase().includes("api key") ||
23
+ rawMsg.toLowerCase().includes("unauthorized") ||
24
+ rawMsg.toLowerCase().includes("revoked")) {
25
+ friendlyMessage = "Authentication failed: Invalid, expired, or revoked API key. Please run 'thumbnailgpt login' to update your key.";
26
+ }
27
+ else if (status === 403 || rawMsg.toLowerCase().includes("forbidden")) {
28
+ friendlyMessage = "Access restricted: You do not have permission to access this resource.";
29
+ }
30
+ else if (status === 429 ||
31
+ rawMsg.toLowerCase().includes("rate limit") ||
32
+ rawMsg.toLowerCase().includes("too many requests")) {
33
+ friendlyMessage = "Request rate limit reached. Please wait a moment before trying again.";
34
+ }
35
+ else if (status >= 500 ||
36
+ rawMsg.toLowerCase().includes("internal server error") ||
37
+ rawMsg.toLowerCase().includes("service unavailable")) {
38
+ friendlyMessage = "ThumbnailGPT services are temporarily busy. Please try again in a few moments.";
39
+ }
40
+ else if (rawMsg.includes("ECONNREFUSED") ||
41
+ rawMsg.includes("ENOTFOUND") ||
42
+ rawMsg.includes("fetch failed")) {
43
+ friendlyMessage = "Unable to connect to ThumbnailGPT. Please check your internet connection.";
44
+ }
45
+ else if (rawMsg.includes("<!DOCTYPE") ||
46
+ rawMsg.includes("<html") ||
47
+ rawMsg.includes("SQL") ||
48
+ rawMsg.includes("Postgres")) {
49
+ friendlyMessage = "An unexpected error occurred. Please try again or visit https://thumbnailgpt.com for status updates.";
50
+ }
51
+ console.error(pc.red(`\nError: ${friendlyMessage}\n`));
52
+ process.exitCode = 1;
53
+ }
8
54
  const program = new Command();
9
55
  program
10
56
  .name("thumbnailgpt")
11
- .description("Official CLI and MCP server for ThumbnailGPT Studio")
12
- .version("1.0.5");
57
+ .description("Official CLI for ThumbnailGPT — Generate, iterate, and manage thumbnails from your terminal")
58
+ .version("1.0.7");
13
59
  // 1. Auth Login
14
60
  program
15
61
  .command("login")
@@ -22,7 +68,7 @@ program
22
68
  }
23
69
  if (options.key) {
24
70
  setApiKey(options.key);
25
- console.log(pc.green("✔ API key saved successfully!"));
71
+ console.log(pc.green("API key saved successfully."));
26
72
  return;
27
73
  }
28
74
  const rl = readline.createInterface({
@@ -30,16 +76,16 @@ program
30
76
  output: process.stdout,
31
77
  });
32
78
  console.log(pc.bold("\nThumbnailGPT Authentication"));
33
- console.log(pc.gray("Generate an API key in your Web Studio at: Settings -> Developers -> API Keys & MCP\n"));
79
+ console.log(pc.gray("Get your API key at: ") + pc.cyan("https://thumbnailgpt.com/studio?settings=api-key\n"));
34
80
  rl.question(pc.cyan("Enter your Personal API Key (tgpt_live_...): "), (key) => {
35
81
  rl.close();
36
82
  const trimmed = key.trim();
37
83
  if (!trimmed.startsWith("tgpt_live_")) {
38
- console.error(pc.red("\n❌ Invalid format. API keys begin with 'tgpt_live_'"));
84
+ console.error(pc.red("\nInvalid format. API keys begin with 'tgpt_live_'"));
39
85
  process.exit(1);
40
86
  }
41
87
  setApiKey(trimmed);
42
- console.log(pc.green("\n✔ Logged in successfully! Key saved to local config."));
88
+ console.log(pc.green("\nLogged in successfully. Key saved to local config."));
43
89
  });
44
90
  });
45
91
  // 2. Auth Logout
@@ -48,16 +94,17 @@ program
48
94
  .description("Log out and clear stored API key")
49
95
  .action(() => {
50
96
  clearApiKey();
51
- console.log(pc.green("✔ Logged out. Stored credentials cleared."));
97
+ console.log(pc.green("Logged out. Stored credentials cleared."));
52
98
  });
53
99
  // 3. Whoami / Credits
54
100
  program
55
101
  .command("whoami")
56
102
  .description("Display your account and available credit balance")
57
103
  .action(async () => {
104
+ let spinner = null;
58
105
  try {
59
106
  const client = new ThumbnailGPTClient();
60
- const spinner = ora("Fetching account details...").start();
107
+ spinner = ora("Fetching account details...").start();
61
108
  const credits = await client.getCredits();
62
109
  spinner.stop();
63
110
  console.log(pc.bold("\nThumbnailGPT Account"));
@@ -66,8 +113,7 @@ program
66
113
  console.log(`${pc.gray("Cost Tiers:")} 1K = 10 cr | 2K = 20 cr | 4K = 30 cr\n`);
67
114
  }
68
115
  catch (err) {
69
- console.error(pc.red(`\n❌ Error: ${err.message}`));
70
- process.exit(1);
116
+ handleError(err, spinner);
71
117
  }
72
118
  });
73
119
  // 4. One-shot Generate
@@ -78,15 +124,31 @@ program
78
124
  .option("-q, --res <resolution>", "Resolution: 1K, 2K, or 4K", "1K")
79
125
  .option("--ref <urls...>", "Reference image file paths or URLs (max 5)")
80
126
  .option("--yt <url>", "YouTube video URL for reference")
81
- .option("-c, --chat <chatId>", "Continue an existing studio conversation thread")
82
- .option("-p, --persona <personaId>", "Creator Persona UUID to feature in the thumbnail")
83
- .option("-o, --out <filepath>", "Download result directly to file (e.g. ./thumb.png)")
127
+ .option("-p, --persona <personaId>", "Persona UUID to feature in the thumbnail")
128
+ .option("-c, --chat <chatId>", "Continue an existing chat conversation thread")
129
+ .option("-u, --url-only", "Output only the hosted thumbnail URL")
130
+ .option("--json", "Output response as raw JSON")
84
131
  .option("--async", "Dispatch job and return immediately without waiting")
85
132
  .action(async (prompt, options) => {
133
+ let spinner = null;
86
134
  try {
135
+ if (options.ref && options.ref.length > 5) {
136
+ console.error(pc.red("\nError: A maximum of 5 reference images is allowed."));
137
+ process.exit(1);
138
+ }
139
+ const ratio = (options.ratio || "16:9").trim();
140
+ if (ratio !== "16:9" && ratio !== "9:16") {
141
+ console.error(pc.red(`\nError: Invalid aspect ratio "${ratio}". Supported ratios: 16:9, 9:16`));
142
+ process.exit(1);
143
+ }
144
+ const res = (options.res || "1K").toUpperCase().trim();
145
+ if (res !== "1K" && res !== "2K" && res !== "4K") {
146
+ console.error(pc.red(`\nError: Invalid resolution "${res}". Supported resolutions: 1K (10 credits), 2K (20 credits), 4K (30 credits)`));
147
+ process.exit(1);
148
+ }
87
149
  const client = new ThumbnailGPTClient();
88
150
  if (options.async) {
89
- const spinner = ora("Dispatching to ThumbnailGPT Studio...").start();
151
+ spinner = !options.json && !options.urlOnly ? ora("Dispatching to ThumbnailGPT...").start() : null;
90
152
  const dispatch = await client.dispatchStudio({
91
153
  prompt,
92
154
  chatId: options.chat,
@@ -96,13 +158,18 @@ program
96
158
  youtubeUrl: options.yt,
97
159
  personaId: options.persona,
98
160
  });
99
- spinner.succeed("Generation dispatched successfully!");
161
+ if (options.json) {
162
+ console.log(JSON.stringify(dispatch, null, 2));
163
+ return;
164
+ }
165
+ if (spinner)
166
+ spinner.succeed("Generation dispatched successfully.");
100
167
  console.log(`\n${pc.bold("Chat ID:")} ${dispatch.chatId}`);
101
168
  console.log(`${pc.bold("Thumbnail ID:")} ${dispatch.thumbnailId || "Pending"}`);
102
169
  console.log(`Check status with: ${pc.cyan(`thumbnailgpt chats show ${dispatch.chatId}`)}\n`);
103
170
  return;
104
171
  }
105
- const spinner = ora("Validating credits & dispatching...").start();
172
+ spinner = !options.json && !options.urlOnly ? ora("Validating credits & dispatching...").start() : null;
106
173
  const result = await client.generateAndWait({
107
174
  prompt,
108
175
  chatId: options.chat,
@@ -112,43 +179,61 @@ program
112
179
  youtubeUrl: options.yt,
113
180
  personaId: options.persona,
114
181
  }, (elapsedSec) => {
115
- spinner.text = `Synthesizing thumbnail with AI [${elapsedSec}s elapsed]...`;
182
+ if (spinner) {
183
+ spinner.text = `Synthesizing thumbnail with AI [${elapsedSec}s elapsed]...`;
184
+ }
116
185
  });
117
186
  if (!result.success || !result.imageUrl) {
118
- spinner.fail(`Generation failed: ${result.error}`);
187
+ if (spinner)
188
+ spinner.fail(`Generation failed: ${result.error}`);
189
+ else
190
+ console.error(pc.red(`Generation failed: ${result.error}`));
119
191
  process.exit(1);
120
192
  }
121
- spinner.succeed(pc.green("Thumbnail generated successfully!"));
122
- console.log(`\n${pc.bold("Image URL:")} ${pc.cyan(result.imageUrl)}`);
123
- console.log(`${pc.bold("Chat ID:")} ${result.chatId}`);
124
- console.log(`${pc.bold("Credits Left:")} ${result.creditsRemaining ?? "N/A"}`);
125
- console.log(`${pc.bold("Web Studio:")} https://thumbnailgpt.com/studio/${result.chatId}`);
126
- if (options.out) {
127
- const downloadSpinner = ora(`Downloading to ${options.out}...`).start();
128
- const savedPath = await client.downloadImage(result.imageUrl, options.out);
129
- downloadSpinner.succeed(`Saved to ${pc.green(savedPath)}`);
193
+ if (options.urlOnly) {
194
+ console.log(result.imageUrl);
195
+ return;
130
196
  }
131
- console.log();
197
+ if (options.json) {
198
+ console.log(JSON.stringify({
199
+ success: true,
200
+ imageUrl: result.imageUrl,
201
+ chatId: result.chatId,
202
+ thumbnailId: result.thumbnailId,
203
+ creditsRemaining: result.creditsRemaining,
204
+ }, null, 2));
205
+ return;
206
+ }
207
+ if (spinner)
208
+ spinner.succeed(pc.green("Thumbnail generated successfully."));
209
+ console.log(`\n${pc.bold("Hosted Image URL:")} ${pc.cyan(result.imageUrl)}`);
210
+ console.log(`${pc.bold("Chat ID:")} ${result.chatId}`);
211
+ if (result.thumbnailId) {
212
+ console.log(`${pc.bold("Thumbnail ID:")} ${result.thumbnailId}`);
213
+ }
214
+ console.log(`${pc.bold("Credits Left:")} ${result.creditsRemaining ?? "N/A"}`);
215
+ console.log(`${pc.bold("Web App:")} https://thumbnailgpt.com/studio/${result.chatId}\n`);
132
216
  }
133
217
  catch (err) {
134
- console.error(pc.red(`\n❌ Error: ${err.message}`));
135
- process.exit(1);
218
+ handleError(err, spinner);
136
219
  }
137
220
  });
138
- // 5. Interactive Studio TUI REPL
221
+ // 5. Interactive Chat REPL
139
222
  program
140
- .command("studio [initialChatId]")
141
- .description("Launch an interactive conversation Studio session in your terminal")
223
+ .command("chat [initialChatId]")
224
+ .alias("interactive")
225
+ .alias("studio")
226
+ .description("Launch an interactive chat session in your terminal")
142
227
  .action(async (initialChatId) => {
143
228
  try {
144
229
  const client = new ThumbnailGPTClient();
145
230
  let currentChatId = initialChatId;
146
231
  console.clear();
147
- console.log(pc.bold(pc.yellow("✦ ThumbnailGPT Studio CLI ✦")));
232
+ console.log(pc.bold("ThumbnailGPT CLI"));
148
233
  const credits = await client.getCredits();
149
234
  console.log(`${pc.gray("Credits:")} ${pc.green(String(credits.credits))} • ${pc.gray("Plan:")} ${credits.subscription_status}`);
150
235
  if (currentChatId) {
151
- console.log(`${pc.gray("Resuming thread:")} ${currentChatId}`);
236
+ console.log(`${pc.gray("Resuming chat:")} ${currentChatId}`);
152
237
  }
153
238
  console.log(pc.gray("Type your prompt or visual tweak. Press Ctrl+C anytime to exit.\n"));
154
239
  const rl = readline.createInterface({
@@ -156,7 +241,7 @@ program
156
241
  output: process.stdout,
157
242
  });
158
243
  const askPrompt = () => {
159
- rl.question(pc.cyan("\nstudio > "), async (input) => {
244
+ rl.question(pc.cyan("\nthumbnailgpt > "), async (input) => {
160
245
  const text = input.trim();
161
246
  if (!text) {
162
247
  askPrompt();
@@ -177,10 +262,10 @@ program
177
262
  }
178
263
  else {
179
264
  currentChatId = result.chatId;
180
- spinner.succeed(pc.green("Thumbnail updated!"));
265
+ spinner.succeed(pc.green("Thumbnail updated."));
181
266
  console.log(`${pc.bold("Image:")} ${pc.cyan(result.imageUrl)}`);
182
267
  console.log(`${pc.bold("Remaining:")} ${result.creditsRemaining} credits`);
183
- console.log(`${pc.gray("Web Studio:")} https://thumbnailgpt.com/studio/${currentChatId}`);
268
+ console.log(`${pc.gray("Web App:")} https://thumbnailgpt.com/studio/${currentChatId}`);
184
269
  }
185
270
  }
186
271
  catch (err) {
@@ -192,19 +277,19 @@ program
192
277
  askPrompt();
193
278
  }
194
279
  catch (err) {
195
- console.error(pc.red(`\n❌ Error: ${err.message}`));
196
- process.exit(1);
280
+ handleError(err);
197
281
  }
198
282
  });
199
283
  // 6. Chats Subcommand
200
- const chats = program.command("chats").description("Manage studio conversations");
284
+ const chats = program.command("chats").description("Manage chat conversations");
201
285
  chats
202
286
  .command("list")
203
- .description("List recent studio conversations")
287
+ .description("List recent chat conversations")
204
288
  .action(async () => {
289
+ let spinner = null;
205
290
  try {
206
291
  const client = new ThumbnailGPTClient();
207
- const spinner = ora("Fetching conversations...").start();
292
+ spinner = ora("Fetching conversations...").start();
208
293
  const res = await client.listChats();
209
294
  spinner.stop();
210
295
  const chatList = res.chats || [];
@@ -212,7 +297,7 @@ chats
212
297
  console.log(pc.gray("No conversations found."));
213
298
  return;
214
299
  }
215
- console.log(pc.bold("\nRecent Studio Conversations:\n"));
300
+ console.log(pc.bold("\nRecent Conversations:\n"));
216
301
  chatList.forEach((c) => {
217
302
  console.log(`• ${pc.bold(c.title || "Untitled")} ${pc.gray(`(${c.id})`)}`);
218
303
  console.log(` ${pc.gray("Updated:")} ${new Date(c.updated_at).toLocaleString()}`);
@@ -220,17 +305,17 @@ chats
220
305
  console.log();
221
306
  }
222
307
  catch (err) {
223
- console.error(pc.red(`\n❌ Error: ${err.message}`));
224
- process.exit(1);
308
+ handleError(err, spinner);
225
309
  }
226
310
  });
227
311
  chats
228
312
  .command("show <chatId>")
229
313
  .description("View conversation history and thumbnails")
230
314
  .action(async (chatId) => {
315
+ let spinner = null;
231
316
  try {
232
317
  const client = new ThumbnailGPTClient();
233
- const spinner = ora("Fetching thread...").start();
318
+ spinner = ora("Fetching thread...").start();
234
319
  const res = await client.getChat(chatId);
235
320
  spinner.stop();
236
321
  console.log(pc.bold(`\nThread: ${res.chat?.title || chatId}\n`));
@@ -247,11 +332,79 @@ chats
247
332
  console.log();
248
333
  }
249
334
  catch (err) {
250
- console.error(pc.red(`\n❌ Error: ${err.message}`));
251
- process.exit(1);
335
+ handleError(err, spinner);
336
+ }
337
+ });
338
+ chats
339
+ .command("delete <chatId>")
340
+ .description("Permanently delete a chat conversation thread (restricted to web app)")
341
+ .action((chatId) => {
342
+ console.log(pc.yellow("\nSecurity Notice: Destructive deletions are disabled via the CLI to protect your account and generation history."));
343
+ console.log(`Please delete conversations directly in the ThumbnailGPT web application at:\n${pc.cyan(`https://thumbnailgpt.com/studio/${chatId}`)}\n`);
344
+ });
345
+ // 7. Thumbnails Management
346
+ const thumbnailsCmd = program
347
+ .command("thumbnails")
348
+ .description("Manage and download generated thumbnails");
349
+ thumbnailsCmd
350
+ .command("get <thumbnailId>")
351
+ .description("Get detailed information about a thumbnail by ID")
352
+ .action(async (thumbnailId) => {
353
+ let spinner = null;
354
+ try {
355
+ const client = new ThumbnailGPTClient();
356
+ spinner = ora("Fetching thumbnail details...").start();
357
+ const thumb = await client.getThumbnail(thumbnailId);
358
+ spinner.stop();
359
+ console.log(pc.bold("\nThumbnail Details:"));
360
+ console.log(pc.dim("--------------------------------------------------------------------------------"));
361
+ console.log(`${pc.bold("ID:")} ${thumb.id}`);
362
+ console.log(`${pc.bold("Status:")} ${thumb.status === "completed" || thumb.status === "success" ? pc.green(thumb.status) : pc.yellow(thumb.status || "unknown")}`);
363
+ console.log(`${pc.bold("Prompt:")} ${thumb.prompt || "(no prompt)"}`);
364
+ console.log(`${pc.bold("Ratio:")} ${thumb.aspect_ratio || "16:9"}`);
365
+ console.log(`${pc.bold("Resolution:")} ${thumb.resolution || "1K"}`);
366
+ if (thumb.chat_id) {
367
+ console.log(`${pc.bold("Chat ID:")} ${thumb.chat_id}`);
368
+ }
369
+ if (thumb.thumbnail_url) {
370
+ console.log(`${pc.bold("Image URL:")} ${pc.cyan(thumb.thumbnail_url)}`);
371
+ }
372
+ if (thumb.error_reason) {
373
+ console.log(`${pc.bold("Error:")} ${pc.red(thumb.error_reason)}`);
374
+ }
375
+ console.log(`${pc.bold("Created:")} ${new Date(thumb.created_at).toLocaleString()}`);
376
+ console.log(pc.dim("--------------------------------------------------------------------------------\n"));
377
+ }
378
+ catch (err) {
379
+ handleError(err, spinner);
380
+ }
381
+ });
382
+ thumbnailsCmd
383
+ .command("url <thumbnailId>")
384
+ .description("Get the hosted image URL for a thumbnail")
385
+ .action(async (thumbnailId) => {
386
+ try {
387
+ const client = new ThumbnailGPTClient();
388
+ const thumb = await client.getThumbnail(thumbnailId);
389
+ if (!thumb.thumbnail_url) {
390
+ console.error(pc.yellow(`Thumbnail is not ready yet (status: ${thumb.status || "processing"})`));
391
+ process.exitCode = 1;
392
+ return;
393
+ }
394
+ console.log(thumb.thumbnail_url);
395
+ }
396
+ catch (err) {
397
+ handleError(err);
252
398
  }
253
399
  });
254
- // 7. Creator Personas
400
+ thumbnailsCmd
401
+ .command("delete <thumbnailId>")
402
+ .description("Permanently delete a thumbnail by ID (restricted to web app)")
403
+ .action((thumbnailId) => {
404
+ console.log(pc.yellow("\nSecurity Notice: Destructive deletions are disabled via the CLI to protect your account."));
405
+ console.log(`Please manage or delete your thumbnails directly in the ThumbnailGPT web application at:\n${pc.cyan("https://thumbnailgpt.com/studio")}\n`);
406
+ });
407
+ // 8. Creator Personas
255
408
  const personasCmd = program
256
409
  .command("personas")
257
410
  .description("Inspect active creator personas for thumbnail generation");
@@ -259,14 +412,15 @@ personasCmd
259
412
  .command("list")
260
413
  .description("List your active creator personas (only ready personas are shown)")
261
414
  .action(async () => {
415
+ let spinner = null;
262
416
  try {
263
417
  const client = new ThumbnailGPTClient();
264
- const spinner = ora("Fetching active personas...").start();
418
+ spinner = ora("Fetching active personas...").start();
265
419
  const personas = await client.getPersonas();
266
420
  spinner.stop();
267
421
  if (personas.length === 0) {
268
422
  console.log(pc.yellow("\nNo active personas found."));
269
- console.log(`Train your first persona in the Web Studio at: ${pc.cyan("https://thumbnailgpt.com/studio")}\n`);
423
+ console.log(`Create your first persona in ThumbnailGPT at: ${pc.cyan("https://thumbnailgpt.com/studio")}\n`);
270
424
  return;
271
425
  }
272
426
  console.log(pc.bold("\nActive Creator Personas:"));
@@ -279,8 +433,7 @@ personasCmd
279
433
  console.log(`Use in generation: ${pc.green('npx thumbnailgpt generate "..." --persona <id>')}\n`);
280
434
  }
281
435
  catch (err) {
282
- console.error(pc.red(`\n❌ Error: ${err.message}`));
283
- process.exit(1);
436
+ handleError(err, spinner);
284
437
  }
285
438
  });
286
439
  personasCmd
@@ -289,14 +442,7 @@ personasCmd
289
442
  .action(() => {
290
443
  console.log(pc.bold("\nTraining New Creator Personas:"));
291
444
  console.log("For quality and privacy, persona creation requires interactive multi-angle photo upload");
292
- console.log("and face consistency training, which is exclusively supported in the Web Studio.");
293
- console.log(`\n👉 Create your persona at: ${pc.cyan("https://thumbnailgpt.com/studio")}\n`);
294
- });
295
- // 8. MCP Server Mode
296
- program
297
- .command("mcp")
298
- .description("Run the ThumbnailGPT MCP server via standard I/O (stdio)")
299
- .action(async () => {
300
- await runMcpServer();
445
+ console.log("and face consistency training, supported in the ThumbnailGPT web application.");
446
+ console.log(`\nCreate your persona at: ${pc.cyan("https://thumbnailgpt.com/studio")}\n`);
301
447
  });
302
448
  program.parse(process.argv);
package/dist/types.d.ts CHANGED
@@ -33,6 +33,8 @@ export interface ThumbnailItem {
33
33
  status?: string;
34
34
  error_reason?: string;
35
35
  prompt?: string;
36
+ aspect_ratio?: string;
37
+ resolution?: string;
36
38
  created_at: string;
37
39
  }
38
40
  export interface ChatMessageItem {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thumbnailgpt",
3
- "version": "1.0.5",
4
- "description": "Official CLI and Model Context Protocol (MCP) server for ThumbnailGPT",
3
+ "version": "1.0.7",
4
+ "description": "Official CLI for ThumbnailGPT — Generate, iterate, and manage viral YouTube thumbnails directly from your terminal",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -17,22 +17,33 @@
17
17
  "build": "tsc",
18
18
  "prepublishOnly": "npm run build",
19
19
  "start": "node dist/index.js",
20
- "mcp": "node dist/index.js mcp"
20
+ "test": "tsx test-production.js"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/vivekrao007/thumbnailgpt-v3.git",
25
+ "directory": "cli"
26
+ },
27
+ "homepage": "https://thumbnailgpt.com",
28
+ "bugs": {
29
+ "url": "https://thumbnailgpt.com"
21
30
  },
22
31
  "keywords": [
23
32
  "thumbnailgpt",
24
- "mcp",
25
- "model-context-protocol",
33
+ "youtube-thumbnails",
26
34
  "youtube",
27
- "thumbnails",
35
+ "thumbnail",
36
+ "cli",
28
37
  "ai",
29
- "claude-desktop",
30
- "cursor"
38
+ "flux",
39
+ "image-generation",
40
+ "content-creator",
41
+ "terminal",
42
+ "mcp"
31
43
  ],
32
- "author": "ThumbnailGPT",
44
+ "author": "ThumbnailGPT <support@thumbnailgpt.com>",
33
45
  "license": "MIT",
34
46
  "dependencies": {
35
- "@modelcontextprotocol/sdk": "^1.6.1",
36
47
  "commander": "^13.1.0",
37
48
  "conf": "^13.1.0",
38
49
  "ora": "^8.2.0",
package/dist/mcp.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function runMcpServer(): Promise<void>;
package/dist/mcp.js DELETED
@@ -1,231 +0,0 @@
1
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
- import { ThumbnailGPTClient } from "./client.js";
5
- import { getApiKey } from "./config.js";
6
- export async function runMcpServer() {
7
- const apiKey = getApiKey();
8
- if (!apiKey) {
9
- console.error(JSON.stringify({
10
- error: "Missing THUMBNAILGPT_API_KEY. Set it in your environment or run 'thumbnailgpt login'.",
11
- }));
12
- process.exit(1);
13
- }
14
- const client = new ThumbnailGPTClient(apiKey);
15
- const server = new Server({
16
- name: "thumbnailgpt-mcp",
17
- version: "1.0.5",
18
- }, {
19
- capabilities: {
20
- tools: {},
21
- },
22
- });
23
- // 1. List available tools
24
- server.setRequestHandler(ListToolsRequestSchema, async () => {
25
- return {
26
- tools: [
27
- {
28
- name: "thumbnailgpt_generate",
29
- description: "Create or iteratively refine high-CTR YouTube thumbnails using ThumbnailGPT Studio. Supports aspect ratios, reference images, creator personas, and resolution tiers.",
30
- inputSchema: {
31
- type: "object",
32
- properties: {
33
- prompt: {
34
- type: "string",
35
- description: "Detailed instructions for thumbnail creation, style direction, text overlays, or visual tweaks.",
36
- },
37
- chatId: {
38
- type: "string",
39
- description: "Optional UUID of an existing conversation thread to iterate on. Omit to start a new chat.",
40
- },
41
- aspectRatio: {
42
- type: "string",
43
- enum: ["16:9", "9:16"],
44
- default: "16:9",
45
- description: "16:9 for YouTube standard videos, 9:16 for Shorts / Reels.",
46
- },
47
- resolution: {
48
- type: "string",
49
- enum: ["1K", "2K", "4K"],
50
- default: "1K",
51
- description: "1K (10 credits), 2K (20 credits), 4K (30 credits).",
52
- },
53
- referenceImages: {
54
- type: "array",
55
- items: { type: "string" },
56
- description: "Up to 5 reference image URLs or base64 strings.",
57
- },
58
- youtubeUrl: {
59
- type: "string",
60
- description: "Optional YouTube video URL for context.",
61
- },
62
- baseImage: {
63
- type: "string",
64
- description: "Base image URL for inpainting / iterations.",
65
- },
66
- personaId: {
67
- type: "string",
68
- description: "Optional UUID of an active Creator Persona to feature in the thumbnail. Get available persona IDs with thumbnailgpt_get_personas.",
69
- },
70
- },
71
- required: ["prompt"],
72
- },
73
- },
74
- {
75
- name: "thumbnailgpt_get_personas",
76
- description: "Retrieve active, ready-to-use creator personas. Returns safe identifiers (id, name, created_at). Image URLs, internal storage paths, and failed personas are strictly omitted for privacy and security. (Note: New personas can only be trained in the Web UI at https://thumbnailgpt.com/studio).",
77
- inputSchema: {
78
- type: "object",
79
- properties: {},
80
- },
81
- },
82
- {
83
- name: "thumbnailgpt_get_credits",
84
- description: "Retrieve current ThumbnailGPT available credit balance, subscription status, and resolution cost tiers.",
85
- inputSchema: {
86
- type: "object",
87
- properties: {},
88
- },
89
- },
90
- {
91
- name: "thumbnailgpt_list_chats",
92
- description: "List recent ThumbnailGPT studio conversations and thumbnail sessions.",
93
- inputSchema: {
94
- type: "object",
95
- properties: {},
96
- },
97
- },
98
- {
99
- name: "thumbnailgpt_get_chat",
100
- description: "Retrieve full message history and generated thumbnails for a studio conversation.",
101
- inputSchema: {
102
- type: "object",
103
- properties: {
104
- chatId: {
105
- type: "string",
106
- description: "UUID of the conversation thread to inspect.",
107
- },
108
- },
109
- required: ["chatId"],
110
- },
111
- },
112
- ],
113
- };
114
- });
115
- // 2. Handle tool execution
116
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
117
- const { name, arguments: args } = request.params;
118
- try {
119
- if (name === "thumbnailgpt_get_credits") {
120
- const credits = await client.getCredits();
121
- return {
122
- content: [
123
- {
124
- type: "text",
125
- text: JSON.stringify({
126
- availableCredits: credits.credits,
127
- subscriptionStatus: credits.subscription_status,
128
- costs: { "1K": "10 credits", "2K": "20 credits", "4K": "30 credits" },
129
- }, null, 2),
130
- },
131
- ],
132
- };
133
- }
134
- if (name === "thumbnailgpt_get_personas") {
135
- const personas = await client.getPersonas();
136
- return {
137
- content: [
138
- {
139
- type: "text",
140
- text: JSON.stringify({
141
- personas,
142
- count: personas.length,
143
- guidance: "Pass the persona 'id' to thumbnailgpt_generate as 'personaId' to render the creator in the thumbnail. To create new personas, use the Web UI at https://thumbnailgpt.com/studio.",
144
- }, null, 2),
145
- },
146
- ],
147
- };
148
- }
149
- if (name === "thumbnailgpt_generate") {
150
- const prompt = args?.prompt || "";
151
- const chatId = args?.chatId;
152
- const aspectRatio = args?.aspectRatio || "16:9";
153
- const resolution = args?.resolution || "1K";
154
- const referenceImages = args?.referenceImages;
155
- const youtubeUrl = args?.youtubeUrl;
156
- const baseImage = args?.baseImage;
157
- const personaId = args?.personaId;
158
- const result = await client.generateAndWait({
159
- prompt,
160
- chatId,
161
- aspectRatio,
162
- resolution,
163
- referenceImages,
164
- youtubeUrl,
165
- baseImage,
166
- personaId,
167
- });
168
- if (!result.success || !result.imageUrl) {
169
- return {
170
- isError: true,
171
- content: [
172
- {
173
- type: "text",
174
- text: `❌ Thumbnail generation failed: ${result.error || "Unknown error"}\nChat ID: ${result.chatId}`,
175
- },
176
- ],
177
- };
178
- }
179
- return {
180
- content: [
181
- {
182
- type: "text",
183
- text: `✅ Thumbnail successfully generated!\n\n` +
184
- `**Preview**: ![Generated Thumbnail](${result.imageUrl})\n\n` +
185
- `**Direct Image URL**: ${result.imageUrl}\n` +
186
- `**Conversation Thread ID**: \`${result.chatId}\` (pass this \`chatId\` to iterate or refine)\n` +
187
- `**Remaining Credits**: ${result.creditsRemaining ?? "N/A"}\n` +
188
- `**Web Studio Link**: https://thumbnailgpt.com/studio/${result.chatId}`,
189
- },
190
- ],
191
- };
192
- }
193
- if (name === "thumbnailgpt_list_chats") {
194
- const chats = await client.listChats();
195
- return {
196
- content: [
197
- {
198
- type: "text",
199
- text: JSON.stringify(chats, null, 2),
200
- },
201
- ],
202
- };
203
- }
204
- if (name === "thumbnailgpt_get_chat") {
205
- const chatId = args?.chatId;
206
- const thread = await client.getChat(chatId);
207
- return {
208
- content: [
209
- {
210
- type: "text",
211
- text: JSON.stringify(thread, null, 2),
212
- },
213
- ],
214
- };
215
- }
216
- return {
217
- isError: true,
218
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
219
- };
220
- }
221
- catch (err) {
222
- return {
223
- isError: true,
224
- content: [{ type: "text", text: `Error: ${err.message || String(err)}` }],
225
- };
226
- }
227
- });
228
- const transport = new StdioServerTransport();
229
- await server.connect(transport);
230
- console.error("ThumbnailGPT MCP server running on stdio");
231
- }