thumbnailgpt 1.0.7 → 1.0.9

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.d.ts CHANGED
@@ -1,15 +1,15 @@
1
- import type { CreditBalanceResponse, StudioGenerateOptions, StudioDispatchResult, StudioConversationResponse, GenerationSettledResult, PersonaItem, ThumbnailItem } from "./types.js";
1
+ import type { CreditBalanceResponse, StudioGenerateOptions, StudioDispatchResult, StudioConversationResponse, GenerationSettledResult, PersonaItem, ThumbnailItem, ListChatsResponse, ListPersonasResponse } from "./types.js";
2
2
  export declare class ThumbnailGPTClient {
3
3
  private apiKey;
4
4
  private apiUrl;
5
5
  constructor(apiKey?: string, apiUrl?: string);
6
6
  private request;
7
7
  /**
8
- * Check current user credit balance and plan status
8
+ * Fetch current credit balance and subscription status
9
9
  */
10
10
  getCredits(): Promise<CreditBalanceResponse>;
11
11
  /**
12
- * Dispatch a studio generation job
12
+ * Dispatch studio thumbnail generation task
13
13
  */
14
14
  dispatchStudio(options: StudioGenerateOptions): Promise<StudioDispatchResult>;
15
15
  /**
@@ -17,13 +17,21 @@ export declare class ThumbnailGPTClient {
17
17
  */
18
18
  getChat(chatId: string): Promise<StudioConversationResponse>;
19
19
  /**
20
- * List recent studio conversations
20
+ * List recent studio conversations with pagination
21
21
  */
22
- listChats(): Promise<any>;
22
+ listChats(options?: {
23
+ page?: number;
24
+ limit?: number;
25
+ }): Promise<ListChatsResponse>;
23
26
  /**
24
- * Retrieve active, ready-to-use personas for the user.
25
- * Only 'ready' personas are returned. Raw image URLs, character sheets,
26
- * error traces, and database internals are strictly redacted.
27
+ * Retrieve active, ready-to-use personas for the user with pagination
28
+ */
29
+ listPersonas(options?: {
30
+ page?: number;
31
+ limit?: number;
32
+ }): Promise<ListPersonasResponse>;
33
+ /**
34
+ * Retrieve active, ready-to-use personas (returns array for backward compatibility)
27
35
  */
28
36
  getPersonas(): Promise<PersonaItem[]>;
29
37
  /**
package/dist/client.js CHANGED
@@ -14,74 +14,51 @@ export class ThumbnailGPTClient {
14
14
  async request(endpoint, options = {}) {
15
15
  const url = `${this.apiUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
16
16
  const headers = {
17
- "Content-Type": "application/json",
18
17
  Authorization: `Bearer ${this.apiKey}`,
19
- Origin: this.apiUrl,
20
- "User-Agent": "thumbnailgpt-cli/1.0.7",
18
+ "Content-Type": "application/json",
21
19
  ...options.headers,
22
20
  };
23
- const res = await fetch(url, { ...options, headers });
24
- const data = (await res.json().catch(() => ({})));
21
+ let res;
22
+ try {
23
+ res = await fetch(url, { ...options, headers });
24
+ }
25
+ catch (netErr) {
26
+ throw new Error(`Failed to reach ThumbnailGPT backend at ${this.apiUrl}: ${netErr.message}`);
27
+ }
25
28
  if (!res.ok) {
26
- const errorMsg = data.error ||
27
- data.message ||
28
- `Request failed with status ${res.status}: ${res.statusText}`;
29
- const err = new Error(errorMsg);
30
- err.status = res.status;
31
- err.data = data;
32
- throw err;
29
+ let errMsg = `Request failed (${res.status} ${res.statusText})`;
30
+ try {
31
+ const errorJson = await res.json();
32
+ errMsg = errorJson.error || errorJson.message || errMsg;
33
+ }
34
+ catch {
35
+ // Fallback to text status
36
+ }
37
+ throw new Error(errMsg);
33
38
  }
34
- return data;
39
+ return res.json();
35
40
  }
36
41
  /**
37
- * Check current user credit balance and plan status
42
+ * Fetch current credit balance and subscription status
38
43
  */
39
44
  async getCredits() {
40
45
  return this.request("/api/credits/balance");
41
46
  }
42
47
  /**
43
- * Dispatch a studio generation job
48
+ * Dispatch studio thumbnail generation task
44
49
  */
45
50
  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
- }
74
51
  return this.request("/api/studio", {
75
52
  method: "POST",
76
53
  body: JSON.stringify({
77
- chatId: options.chatId,
78
54
  feature: "recreate_thumbnail",
79
55
  prompt: options.prompt,
56
+ chatId: options.chatId,
80
57
  aspectRatio: options.aspectRatio || "16:9",
81
58
  resolution: options.resolution || "1K",
82
- referenceImages: processedRefImages,
83
- youtubeUrl: options.youtubeUrl || undefined,
84
- personaId: options.personaId || undefined,
59
+ referenceImages: options.referenceImages || [],
60
+ youtubeUrl: options.youtubeUrl,
61
+ personaId: options.personaId,
85
62
  baseImage: options.baseImage,
86
63
  }),
87
64
  });
@@ -93,23 +70,36 @@ export class ThumbnailGPTClient {
93
70
  return this.request(`/api/studio/${chatId}`);
94
71
  }
95
72
  /**
96
- * List recent studio conversations
73
+ * List recent studio conversations with pagination
97
74
  */
98
- async listChats() {
99
- return this.request("/api/studio");
75
+ async listChats(options) {
76
+ const page = Math.max(1, options?.page ?? 1);
77
+ const limit = Math.min(100, Math.max(1, options?.limit ?? 20));
78
+ return this.request(`/api/studio?page=${page}&limit=${limit}`);
79
+ }
80
+ /**
81
+ * Retrieve active, ready-to-use personas for the user with pagination
82
+ */
83
+ async listPersonas(options) {
84
+ const page = Math.max(1, options?.page ?? 1);
85
+ const limit = Math.min(50, Math.max(1, options?.limit ?? 20));
86
+ const res = await this.request(`/api/personas?page=${page}&limit=${limit}`);
87
+ return {
88
+ success: res.success ?? true,
89
+ personas: (res.personas || []).map((p) => ({
90
+ id: p.id,
91
+ name: p.name,
92
+ created_at: p.created_at,
93
+ })),
94
+ pagination: res.pagination,
95
+ };
100
96
  }
101
97
  /**
102
- * Retrieve active, ready-to-use personas for the user.
103
- * Only 'ready' personas are returned. Raw image URLs, character sheets,
104
- * error traces, and database internals are strictly redacted.
98
+ * Retrieve active, ready-to-use personas (returns array for backward compatibility)
105
99
  */
106
100
  async getPersonas() {
107
- const res = await this.request("/api/personas");
108
- return (res.personas || []).map((p) => ({
109
- id: p.id,
110
- name: p.name,
111
- created_at: p.created_at,
112
- }));
101
+ const res = await this.listPersonas({ page: 1, limit: 50 });
102
+ return res.personas;
113
103
  }
114
104
  /**
115
105
  * Asynchronously dispatch and poll until completion
@@ -117,8 +107,8 @@ export class ThumbnailGPTClient {
117
107
  async generateAndWait(options, onProgress, timeoutSec = 120) {
118
108
  // 1. Credit Pre-check
119
109
  const credits = await this.getCredits();
120
- const resolutionCosts = { "1K": 10, "2K": 20, "4K": 30 };
121
- const required = resolutionCosts[options.resolution || "1K"] || 10;
110
+ const resolutionCosts = { "1K": 20, "2K": 30, "4K": 40 };
111
+ const required = resolutionCosts[options.resolution || "1K"] || 20;
122
112
  if (credits.credits < required) {
123
113
  throw new Error(`Insufficient credits. Required: ${required}, Available: ${credits.credits}. Top up at https://thumbnailgpt.com/pricing`);
124
114
  }
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ const program = new Command();
55
55
  program
56
56
  .name("thumbnailgpt")
57
57
  .description("Official CLI for ThumbnailGPT — Generate, iterate, and manage thumbnails from your terminal")
58
- .version("1.0.7");
58
+ .version("1.0.9");
59
59
  // 1. Auth Login
60
60
  program
61
61
  .command("login")
@@ -108,9 +108,11 @@ program
108
108
  const credits = await client.getCredits();
109
109
  spinner.stop();
110
110
  console.log(pc.bold("\nThumbnailGPT Account"));
111
+ if (credits.email) {
112
+ console.log(`${pc.gray("Account:")} ${pc.white(pc.bold(credits.email))}`);
113
+ }
111
114
  console.log(`${pc.gray("Plan Status:")} ${pc.cyan(credits.subscription_status.toUpperCase())}`);
112
- console.log(`${pc.gray("Credits:")} ${pc.green(pc.bold(String(credits.credits)))} available`);
113
- 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`);
114
116
  }
115
117
  catch (err) {
116
118
  handleError(err, spinner);
@@ -143,7 +145,7 @@ program
143
145
  }
144
146
  const res = (options.res || "1K").toUpperCase().trim();
145
147
  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)`));
148
+ console.error(pc.red(`\nError: Invalid resolution "${res}". Supported resolutions: 1K, 2K, 4K`));
147
149
  process.exit(1);
148
150
  }
149
151
  const client = new ThumbnailGPTClient();
@@ -284,14 +286,25 @@ program
284
286
  const chats = program.command("chats").description("Manage chat conversations");
285
287
  chats
286
288
  .command("list")
287
- .description("List recent chat conversations")
288
- .action(async () => {
289
+ .description("List recent chat conversations with pagination")
290
+ .option("-p, --page <number>", "Page number", "1")
291
+ .option("-l, --limit <number>", "Items per page (max 100)", "20")
292
+ .option("--json", "Output response as raw JSON", false)
293
+ .action(async (opts) => {
289
294
  let spinner = null;
290
295
  try {
296
+ const page = Math.max(1, parseInt(opts.page, 10) || 1);
297
+ const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 20));
298
+ if (!opts.json) {
299
+ spinner = ora("Fetching conversations...").start();
300
+ }
291
301
  const client = new ThumbnailGPTClient();
292
- spinner = ora("Fetching conversations...").start();
293
- const res = await client.listChats();
294
- spinner.stop();
302
+ const res = await client.listChats({ page, limit });
303
+ if (opts.json) {
304
+ console.log(JSON.stringify(res, null, 2));
305
+ return;
306
+ }
307
+ spinner?.stop();
295
308
  const chatList = res.chats || [];
296
309
  if (chatList.length === 0) {
297
310
  console.log(pc.gray("No conversations found."));
@@ -302,6 +315,10 @@ chats
302
315
  console.log(`• ${pc.bold(c.title || "Untitled")} ${pc.gray(`(${c.id})`)}`);
303
316
  console.log(` ${pc.gray("Updated:")} ${new Date(c.updated_at).toLocaleString()}`);
304
317
  });
318
+ if (res.pagination) {
319
+ const { page: currPage, totalPages, total, hasMore } = res.pagination;
320
+ console.log(pc.dim(`\nPage ${currPage} of ${totalPages} (${total} total conversations)${hasMore ? ` • Run with --page ${currPage + 1} to view more` : ""}`));
321
+ }
305
322
  console.log();
306
323
  }
307
324
  catch (err) {
@@ -410,14 +427,26 @@ const personasCmd = program
410
427
  .description("Inspect active creator personas for thumbnail generation");
411
428
  personasCmd
412
429
  .command("list")
413
- .description("List your active creator personas (only ready personas are shown)")
414
- .action(async () => {
430
+ .description("List your active creator personas with pagination (only ready personas are shown)")
431
+ .option("-p, --page <number>", "Page number", "1")
432
+ .option("-l, --limit <number>", "Items per page (max 50)", "20")
433
+ .option("--json", "Output response as raw JSON", false)
434
+ .action(async (opts) => {
415
435
  let spinner = null;
416
436
  try {
437
+ const page = Math.max(1, parseInt(opts.page, 10) || 1);
438
+ const limit = Math.min(50, Math.max(1, parseInt(opts.limit, 10) || 20));
439
+ if (!opts.json) {
440
+ spinner = ora("Fetching active personas...").start();
441
+ }
417
442
  const client = new ThumbnailGPTClient();
418
- spinner = ora("Fetching active personas...").start();
419
- const personas = await client.getPersonas();
420
- spinner.stop();
443
+ const res = await client.listPersonas({ page, limit });
444
+ if (opts.json) {
445
+ console.log(JSON.stringify(res, null, 2));
446
+ return;
447
+ }
448
+ spinner?.stop();
449
+ const personas = res.personas || [];
421
450
  if (personas.length === 0) {
422
451
  console.log(pc.yellow("\nNo active personas found."));
423
452
  console.log(`Create your first persona in ThumbnailGPT at: ${pc.cyan("https://thumbnailgpt.com/studio")}\n`);
@@ -430,6 +459,10 @@ personasCmd
430
459
  console.log(`${pc.cyan(p.id)} ${pc.bold(p.name)} ${dateStr}`);
431
460
  });
432
461
  console.log(pc.dim("--------------------------------------------------------------------------------"));
462
+ if (res.pagination) {
463
+ const { page: currPage, totalPages, total, hasMore } = res.pagination;
464
+ console.log(pc.dim(`Page ${currPage} of ${totalPages} (${total} total personas)${hasMore ? ` • Run with --page ${currPage + 1} to view more` : ""}\n`));
465
+ }
433
466
  console.log(`Use in generation: ${pc.green('npx thumbnailgpt generate "..." --persona <id>')}\n`);
434
467
  }
435
468
  catch (err) {
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;
@@ -72,3 +73,28 @@ export interface PersonaItem {
72
73
  name: string;
73
74
  created_at?: string;
74
75
  }
76
+ export interface PaginationMetadata {
77
+ page: number;
78
+ limit: number;
79
+ total: number;
80
+ totalPages: number;
81
+ hasMore: boolean;
82
+ }
83
+ export interface ListChatsResponse {
84
+ success: boolean;
85
+ chats: Array<{
86
+ id: string;
87
+ title: string;
88
+ created_at: string;
89
+ updated_at: string;
90
+ thumbnails?: ThumbnailItem[];
91
+ }>;
92
+ pagination?: PaginationMetadata;
93
+ error?: string;
94
+ }
95
+ export interface ListPersonasResponse {
96
+ success: boolean;
97
+ personas: PersonaItem[];
98
+ pagination?: PaginationMetadata;
99
+ error?: string;
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbnailgpt",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
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",