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 +264 -78
- package/bin/thumbnailgpt.js +6 -6
- package/dist/client.d.ts +13 -1
- package/dist/client.js +57 -4
- package/dist/index.js +210 -64
- package/dist/types.d.ts +2 -0
- package/package.json +21 -10
- package/dist/mcp.d.ts +0 -1
- package/dist/mcp.js +0 -231
package/README.md
CHANGED
|
@@ -1,137 +1,323 @@
|
|
|
1
|
-
# ThumbnailGPT CLI
|
|
1
|
+
# ThumbnailGPT CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/thumbnailgpt)
|
|
4
|
+
[](https://www.npmjs.com/package/thumbnailgpt)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://nodejs.org)
|
|
4
7
|
|
|
5
|
-
|
|
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
|
-
##
|
|
51
|
+
## Quickstart
|
|
10
52
|
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
##
|
|
97
|
+
## Command Reference
|
|
27
98
|
|
|
28
|
-
|
|
99
|
+
### `thumbnailgpt generate <prompt>`
|
|
29
100
|
|
|
30
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
48
|
-
Add to `claude_desktop_config.json`:
|
|
107
|
+
#### Options
|
|
49
108
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
78
|
-
npx thumbnailgpt
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `thumbnailgpt chats`
|
|
195
|
+
|
|
196
|
+
Inspect and manage your generation conversation history.
|
|
197
|
+
|
|
88
198
|
```bash
|
|
89
|
-
# List
|
|
90
|
-
npx thumbnailgpt
|
|
199
|
+
# List recent conversation threads
|
|
200
|
+
npx thumbnailgpt chats list
|
|
91
201
|
|
|
92
|
-
#
|
|
93
|
-
npx thumbnailgpt
|
|
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
|
-
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### `thumbnailgpt thumbnails`
|
|
212
|
+
|
|
213
|
+
Inspect metadata and retrieve URLs for generated thumbnails.
|
|
214
|
+
|
|
97
215
|
```bash
|
|
98
|
-
#
|
|
99
|
-
npx thumbnailgpt
|
|
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
|
-
|
|
102
|
-
npx thumbnailgpt generate "24-Hour Survival Challenge" --persona <personaId>
|
|
228
|
+
### `thumbnailgpt personas`
|
|
103
229
|
|
|
104
|
-
|
|
105
|
-
npx thumbnailgpt generate "Cyberpunk Underground Hacker Room" --resolution 4K --output ./thumb.webp
|
|
230
|
+
Manage your custom trained AI creator personas.
|
|
106
231
|
|
|
107
|
-
|
|
108
|
-
|
|
232
|
+
```bash
|
|
233
|
+
# List all active personas with their IDs and avatar URLs
|
|
234
|
+
npx thumbnailgpt personas list
|
|
109
235
|
|
|
110
|
-
#
|
|
111
|
-
npx thumbnailgpt
|
|
236
|
+
# Instructions for training new creator personas
|
|
237
|
+
npx thumbnailgpt personas add
|
|
112
238
|
```
|
|
113
239
|
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
248
|
+
curl -s -o thumbnail.png $(npx thumbnailgpt generate "Surviving 24 Hours on a Desert Island" --url-only)
|
|
249
|
+
```
|
|
118
250
|
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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)
|
package/bin/thumbnailgpt.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
|
12
|
-
.version("1.0.
|
|
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("
|
|
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("
|
|
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("\
|
|
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("\
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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("-
|
|
82
|
-
.option("-
|
|
83
|
-
.option("-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
process.exit(1);
|
|
218
|
+
handleError(err, spinner);
|
|
136
219
|
}
|
|
137
220
|
});
|
|
138
|
-
// 5. Interactive
|
|
221
|
+
// 5. Interactive Chat REPL
|
|
139
222
|
program
|
|
140
|
-
.command("
|
|
141
|
-
.
|
|
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(
|
|
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
|
|
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("\
|
|
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
|
|
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
|
-
|
|
196
|
-
process.exit(1);
|
|
280
|
+
handleError(err);
|
|
197
281
|
}
|
|
198
282
|
});
|
|
199
283
|
// 6. Chats Subcommand
|
|
200
|
-
const chats = program.command("chats").description("Manage
|
|
284
|
+
const chats = program.command("chats").description("Manage chat conversations");
|
|
201
285
|
chats
|
|
202
286
|
.command("list")
|
|
203
|
-
.description("List recent
|
|
287
|
+
.description("List recent chat conversations")
|
|
204
288
|
.action(async () => {
|
|
289
|
+
let spinner = null;
|
|
205
290
|
try {
|
|
206
291
|
const client = new ThumbnailGPTClient();
|
|
207
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
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,
|
|
293
|
-
console.log(`\
|
|
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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbnailgpt",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Official CLI and
|
|
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
|
-
"
|
|
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
|
-
"
|
|
25
|
-
"model-context-protocol",
|
|
33
|
+
"youtube-thumbnails",
|
|
26
34
|
"youtube",
|
|
27
|
-
"
|
|
35
|
+
"thumbnail",
|
|
36
|
+
"cli",
|
|
28
37
|
"ai",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
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**: \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
|
-
}
|