thumbnailgpt 1.0.8 → 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/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.8",
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.8");
58
+ .version("1.0.9");
59
59
  // 1. Auth Login
60
60
  program
61
61
  .command("login")
@@ -286,14 +286,25 @@ program
286
286
  const chats = program.command("chats").description("Manage chat conversations");
287
287
  chats
288
288
  .command("list")
289
- .description("List recent chat conversations")
290
- .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) => {
291
294
  let spinner = null;
292
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
+ }
293
301
  const client = new ThumbnailGPTClient();
294
- spinner = ora("Fetching conversations...").start();
295
- const res = await client.listChats();
296
- 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();
297
308
  const chatList = res.chats || [];
298
309
  if (chatList.length === 0) {
299
310
  console.log(pc.gray("No conversations found."));
@@ -304,6 +315,10 @@ chats
304
315
  console.log(`• ${pc.bold(c.title || "Untitled")} ${pc.gray(`(${c.id})`)}`);
305
316
  console.log(` ${pc.gray("Updated:")} ${new Date(c.updated_at).toLocaleString()}`);
306
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
+ }
307
322
  console.log();
308
323
  }
309
324
  catch (err) {
@@ -412,14 +427,26 @@ const personasCmd = program
412
427
  .description("Inspect active creator personas for thumbnail generation");
413
428
  personasCmd
414
429
  .command("list")
415
- .description("List your active creator personas (only ready personas are shown)")
416
- .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) => {
417
435
  let spinner = null;
418
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
+ }
419
442
  const client = new ThumbnailGPTClient();
420
- spinner = ora("Fetching active personas...").start();
421
- const personas = await client.getPersonas();
422
- 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 || [];
423
450
  if (personas.length === 0) {
424
451
  console.log(pc.yellow("\nNo active personas found."));
425
452
  console.log(`Create your first persona in ThumbnailGPT at: ${pc.cyan("https://thumbnailgpt.com/studio")}\n`);
@@ -432,6 +459,10 @@ personasCmd
432
459
  console.log(`${pc.cyan(p.id)} ${pc.bold(p.name)} ${dateStr}`);
433
460
  });
434
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
+ }
435
466
  console.log(`Use in generation: ${pc.green('npx thumbnailgpt generate "..." --persona <id>')}\n`);
436
467
  }
437
468
  catch (err) {
package/dist/types.d.ts CHANGED
@@ -73,3 +73,28 @@ export interface PersonaItem {
73
73
  name: string;
74
74
  created_at?: string;
75
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.8",
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",