thumbnailgpt 1.0.6 → 1.0.8

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
@@ -15,14 +15,14 @@ Generate, iterate, download, and manage viral, high-CTR YouTube thumbnails direc
15
15
 
16
16
  ## Highlights
17
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.
18
+ - **Zero-Install Execution**: Run any command immediately via `npx thumbnailgpt`.
19
+ - **Multi-Resolution Rendering**: Generate in standard **1K**, HD **2K**, or Ultra-HD **4K**.
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
26
 
27
27
  ---
28
28
 
@@ -62,20 +62,16 @@ npx thumbnailgpt login
62
62
  npx thumbnailgpt login --key tgpt_live_YOUR_API_KEY
63
63
  ```
64
64
 
65
- ### 2. Verify Your Account & Credit Balance
65
+ ### 2. Verify Your Account Status
66
66
  ```bash
67
67
  npx thumbnailgpt whoami
68
68
  ```
69
69
  *Output:*
70
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
71
+ ThumbnailGPT Account
72
+ Account: user@example.com
73
+ Plan Status: ACTIVE
74
+ Credits: 583 available
79
75
  ```
80
76
 
81
77
  ### 3. Generate a Thumbnail
@@ -84,7 +80,7 @@ npx thumbnailgpt generate "Extreme 100 Days Hardcore Survival in Minecraft" --re
84
80
  ```
85
81
  *Output:*
86
82
  ```text
87
- ✔ Generation complete! (14s)
83
+ Generation complete (14s)
88
84
 
89
85
  Thumbnail URL: https://assets.thumbnailgpt.com/generations/.../thumbnail.png
90
86
  Chat Thread: https://thumbnailgpt.com/studio/3c9d1e5f-...
@@ -109,7 +105,7 @@ npx thumbnailgpt generate [options] <prompt>
109
105
  | Flag | Short | Default | Description |
110
106
  | :--- | :---: | :---: | :--- |
111
107
  | `--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) |
108
+ | `--res <resolution>` | `-q` | `1K` | Resolution quality: `1K`, `2K`, or `4K` |
113
109
  | `--ref <urls...>` | | | Up to 5 public image URLs or base64 data URIs as visual references |
114
110
  | `--yt <url>` | | | YouTube video URL to extract title, context, and current thumbnail |
115
111
  | `--persona <personaId>` | `-p` | | Creator Persona UUID to feature in the generated thumbnail |
@@ -172,10 +168,9 @@ npx thumbnailgpt chat 3c9d1e5f-c648-4b3c-a6f0-811d073c22c2
172
168
  ```
173
169
 
174
170
  Commands available inside interactive chat mode:
175
- - Type your prompt prompt to iterate.
171
+ - Type your prompt to iterate.
176
172
  - `/url` — Display the latest generated thumbnail URL.
177
173
  - `/open` — Open the current thumbnail in your default web browser.
178
- - `/cost` — View credit cost tiers.
179
174
  - `/help` — Display interactive mode shortcuts.
180
175
  - `/exit` or `exit` — Exit interactive session.
181
176
 
@@ -183,7 +178,7 @@ Commands available inside interactive chat mode:
183
178
 
184
179
  ### `thumbnailgpt whoami`
185
180
 
186
- Display your authenticated email, subscription tier, available credit balance, and generation pricing tiers.
181
+ Display your authenticated email, subscription status, and available balance.
187
182
 
188
183
  ```bash
189
184
  npx thumbnailgpt whoami
@@ -304,17 +299,17 @@ Now you can instruct Claude or Cursor:
304
299
 
305
300
  To prevent accidental data loss or unauthorized bulk deletions by third-party scripts and autonomous AI agents:
306
301
  - 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.
302
+ - Resource creation, iteration, account balance checks, persona styling, and asset retrievals are fully enabled.
308
303
  - Deletions can be performed safely by the account owner inside the **[ThumbnailGPT Web Studio](https://thumbnailgpt.com/studio)**.
309
304
 
310
305
  ---
311
306
 
312
307
  ## Support & Resources
313
308
 
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)
309
+ - Web Studio: [https://thumbnailgpt.com](https://thumbnailgpt.com)
310
+ - API & CLI Docs: [https://thumbnailgpt.com/docs](https://thumbnailgpt.com/docs)
311
+ - API Key Management: [https://thumbnailgpt.com/studio?settings=api-key](https://thumbnailgpt.com/studio?settings=api-key)
312
+ - Support Email: [support@thumbnailgpt.com](mailto:support@thumbnailgpt.com)
318
313
 
319
314
  ---
320
315
 
package/dist/client.js CHANGED
@@ -17,7 +17,7 @@ export class ThumbnailGPTClient {
17
17
  "Content-Type": "application/json",
18
18
  Authorization: `Bearer ${this.apiKey}`,
19
19
  Origin: this.apiUrl,
20
- "User-Agent": "thumbnailgpt-cli/1.0.6",
20
+ "User-Agent": "thumbnailgpt-cli/1.0.8",
21
21
  ...options.headers,
22
22
  };
23
23
  const res = await fetch(url, { ...options, headers });
package/dist/index.js CHANGED
@@ -4,11 +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
+ /**
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
+ }
7
54
  const program = new Command();
8
55
  program
9
56
  .name("thumbnailgpt")
10
57
  .description("Official CLI for ThumbnailGPT — Generate, iterate, and manage thumbnails from your terminal")
11
- .version("1.0.6");
58
+ .version("1.0.8");
12
59
  // 1. Auth Login
13
60
  program
14
61
  .command("login")
@@ -54,19 +101,21 @@ program
54
101
  .command("whoami")
55
102
  .description("Display your account and available credit balance")
56
103
  .action(async () => {
104
+ let spinner = null;
57
105
  try {
58
106
  const client = new ThumbnailGPTClient();
59
- const spinner = ora("Fetching account details...").start();
107
+ spinner = ora("Fetching account details...").start();
60
108
  const credits = await client.getCredits();
61
109
  spinner.stop();
62
110
  console.log(pc.bold("\nThumbnailGPT Account"));
111
+ if (credits.email) {
112
+ console.log(`${pc.gray("Account:")} ${pc.white(pc.bold(credits.email))}`);
113
+ }
63
114
  console.log(`${pc.gray("Plan Status:")} ${pc.cyan(credits.subscription_status.toUpperCase())}`);
64
- console.log(`${pc.gray("Credits:")} ${pc.green(pc.bold(String(credits.credits)))} available`);
65
- console.log(`${pc.gray("Cost Tiers:")} 1K = 10 cr | 2K = 20 cr | 4K = 30 cr\n`);
115
+ console.log(`${pc.gray("Credits:")} ${pc.green(pc.bold(String(credits.credits)))} available\n`);
66
116
  }
67
117
  catch (err) {
68
- console.error(pc.red(`\nError: ${err.message}`));
69
- process.exit(1);
118
+ handleError(err, spinner);
70
119
  }
71
120
  });
72
121
  // 4. One-shot Generate
@@ -83,6 +132,7 @@ program
83
132
  .option("--json", "Output response as raw JSON")
84
133
  .option("--async", "Dispatch job and return immediately without waiting")
85
134
  .action(async (prompt, options) => {
135
+ let spinner = null;
86
136
  try {
87
137
  if (options.ref && options.ref.length > 5) {
88
138
  console.error(pc.red("\nError: A maximum of 5 reference images is allowed."));
@@ -95,12 +145,12 @@ program
95
145
  }
96
146
  const res = (options.res || "1K").toUpperCase().trim();
97
147
  if (res !== "1K" && res !== "2K" && res !== "4K") {
98
- console.error(pc.red(`\nError: Invalid resolution "${res}". Supported resolutions: 1K (10 credits), 2K (20 credits), 4K (30 credits)`));
148
+ console.error(pc.red(`\nError: Invalid resolution "${res}". Supported resolutions: 1K, 2K, 4K`));
99
149
  process.exit(1);
100
150
  }
101
151
  const client = new ThumbnailGPTClient();
102
152
  if (options.async) {
103
- const spinner = !options.json && !options.urlOnly ? ora("Dispatching to ThumbnailGPT...").start() : null;
153
+ spinner = !options.json && !options.urlOnly ? ora("Dispatching to ThumbnailGPT...").start() : null;
104
154
  const dispatch = await client.dispatchStudio({
105
155
  prompt,
106
156
  chatId: options.chat,
@@ -121,7 +171,7 @@ program
121
171
  console.log(`Check status with: ${pc.cyan(`thumbnailgpt chats show ${dispatch.chatId}`)}\n`);
122
172
  return;
123
173
  }
124
- const spinner = !options.json && !options.urlOnly ? ora("Validating credits & dispatching...").start() : null;
174
+ spinner = !options.json && !options.urlOnly ? ora("Validating credits & dispatching...").start() : null;
125
175
  const result = await client.generateAndWait({
126
176
  prompt,
127
177
  chatId: options.chat,
@@ -167,8 +217,7 @@ program
167
217
  console.log(`${pc.bold("Web App:")} https://thumbnailgpt.com/studio/${result.chatId}\n`);
168
218
  }
169
219
  catch (err) {
170
- console.error(pc.red(`\nError: ${err.message}`));
171
- process.exit(1);
220
+ handleError(err, spinner);
172
221
  }
173
222
  });
174
223
  // 5. Interactive Chat REPL
@@ -230,8 +279,7 @@ program
230
279
  askPrompt();
231
280
  }
232
281
  catch (err) {
233
- console.error(pc.red(`\nError: ${err.message}`));
234
- process.exit(1);
282
+ handleError(err);
235
283
  }
236
284
  });
237
285
  // 6. Chats Subcommand
@@ -240,9 +288,10 @@ chats
240
288
  .command("list")
241
289
  .description("List recent chat conversations")
242
290
  .action(async () => {
291
+ let spinner = null;
243
292
  try {
244
293
  const client = new ThumbnailGPTClient();
245
- const spinner = ora("Fetching conversations...").start();
294
+ spinner = ora("Fetching conversations...").start();
246
295
  const res = await client.listChats();
247
296
  spinner.stop();
248
297
  const chatList = res.chats || [];
@@ -258,17 +307,17 @@ chats
258
307
  console.log();
259
308
  }
260
309
  catch (err) {
261
- console.error(pc.red(`\nError: ${err.message}`));
262
- process.exit(1);
310
+ handleError(err, spinner);
263
311
  }
264
312
  });
265
313
  chats
266
314
  .command("show <chatId>")
267
315
  .description("View conversation history and thumbnails")
268
316
  .action(async (chatId) => {
317
+ let spinner = null;
269
318
  try {
270
319
  const client = new ThumbnailGPTClient();
271
- const spinner = ora("Fetching thread...").start();
320
+ spinner = ora("Fetching thread...").start();
272
321
  const res = await client.getChat(chatId);
273
322
  spinner.stop();
274
323
  console.log(pc.bold(`\nThread: ${res.chat?.title || chatId}\n`));
@@ -285,8 +334,7 @@ chats
285
334
  console.log();
286
335
  }
287
336
  catch (err) {
288
- console.error(pc.red(`\nError: ${err.message}`));
289
- process.exit(1);
337
+ handleError(err, spinner);
290
338
  }
291
339
  });
292
340
  chats
@@ -304,9 +352,10 @@ thumbnailsCmd
304
352
  .command("get <thumbnailId>")
305
353
  .description("Get detailed information about a thumbnail by ID")
306
354
  .action(async (thumbnailId) => {
355
+ let spinner = null;
307
356
  try {
308
357
  const client = new ThumbnailGPTClient();
309
- const spinner = ora("Fetching thumbnail details...").start();
358
+ spinner = ora("Fetching thumbnail details...").start();
310
359
  const thumb = await client.getThumbnail(thumbnailId);
311
360
  spinner.stop();
312
361
  console.log(pc.bold("\nThumbnail Details:"));
@@ -329,8 +378,7 @@ thumbnailsCmd
329
378
  console.log(pc.dim("--------------------------------------------------------------------------------\n"));
330
379
  }
331
380
  catch (err) {
332
- console.error(pc.red(`\nError: ${err.message}`));
333
- process.exit(1);
381
+ handleError(err, spinner);
334
382
  }
335
383
  });
336
384
  thumbnailsCmd
@@ -342,13 +390,13 @@ thumbnailsCmd
342
390
  const thumb = await client.getThumbnail(thumbnailId);
343
391
  if (!thumb.thumbnail_url) {
344
392
  console.error(pc.yellow(`Thumbnail is not ready yet (status: ${thumb.status || "processing"})`));
345
- process.exit(1);
393
+ process.exitCode = 1;
394
+ return;
346
395
  }
347
396
  console.log(thumb.thumbnail_url);
348
397
  }
349
398
  catch (err) {
350
- console.error(pc.red(`\nError: ${err.message}`));
351
- process.exit(1);
399
+ handleError(err);
352
400
  }
353
401
  });
354
402
  thumbnailsCmd
@@ -366,9 +414,10 @@ personasCmd
366
414
  .command("list")
367
415
  .description("List your active creator personas (only ready personas are shown)")
368
416
  .action(async () => {
417
+ let spinner = null;
369
418
  try {
370
419
  const client = new ThumbnailGPTClient();
371
- const spinner = ora("Fetching active personas...").start();
420
+ spinner = ora("Fetching active personas...").start();
372
421
  const personas = await client.getPersonas();
373
422
  spinner.stop();
374
423
  if (personas.length === 0) {
@@ -386,8 +435,7 @@ personasCmd
386
435
  console.log(`Use in generation: ${pc.green('npx thumbnailgpt generate "..." --persona <id>')}\n`);
387
436
  }
388
437
  catch (err) {
389
- console.error(pc.red(`\nError: ${err.message}`));
390
- process.exit(1);
438
+ handleError(err, spinner);
391
439
  }
392
440
  });
393
441
  personasCmd
package/dist/types.d.ts CHANGED
@@ -22,6 +22,7 @@ export interface StudioDispatchResult {
22
22
  availableCredits?: number;
23
23
  }
24
24
  export interface CreditBalanceResponse {
25
+ email?: string;
25
26
  credits: number;
26
27
  subscription_status: string;
27
28
  error?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbnailgpt",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
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",