thumbnailgpt 0.0.0-stage → 1.0.1
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 +123 -2
- package/bin/thumbnailgpt.js +6 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.js +152 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +255 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +202 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.js +1 -0
- package/package.json +44 -4
package/README.md
CHANGED
|
@@ -1,3 +1,124 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ThumbnailGPT CLI & MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official command-line interface and [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for **[ThumbnailGPT](https://thumbnailgpt.com)**.
|
|
4
|
+
|
|
5
|
+
Create viral, high-CTR YouTube thumbnails directly from your terminal or AI code assistants (Cursor, Claude Desktop, Windsurf).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick Start (Zero Install)
|
|
10
|
+
|
|
11
|
+
You can run commands directly using `npx`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. Log in with your Personal API Key (get one from https://thumbnailgpt.com/settings)
|
|
15
|
+
npx thumbnailgpt login
|
|
16
|
+
|
|
17
|
+
# 2. Check your credit balance & user identity
|
|
18
|
+
npx thumbnailgpt whoami
|
|
19
|
+
|
|
20
|
+
# 3. Generate a thumbnail
|
|
21
|
+
npx thumbnailgpt generate "Surviving 24 Hours in Arctic Snow" --resolution 2K --output ./thumb.webp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## MCP Server Setup (Cursor & Claude Desktop)
|
|
27
|
+
|
|
28
|
+
The package includes a built-in stdio MCP server.
|
|
29
|
+
|
|
30
|
+
### 1. Cursor IDE Setup
|
|
31
|
+
Add to your project's `.cursor/mcp.json` or your global Cursor settings:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"thumbnailgpt": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "thumbnailgpt", "mcp"],
|
|
39
|
+
"env": {
|
|
40
|
+
"THUMBNAILGPT_API_KEY": "tgpt_live_YOUR_KEY_HERE"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Claude Desktop Setup
|
|
48
|
+
Add to `claude_desktop_config.json`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"thumbnailgpt": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["-y", "thumbnailgpt", "mcp"],
|
|
56
|
+
"env": {
|
|
57
|
+
"THUMBNAILGPT_API_KEY": "tgpt_live_YOUR_KEY_HERE"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Exposed MCP Tools:
|
|
65
|
+
- `thumbnailgpt_generate`: Synthesize YouTube thumbnails with custom prompts, aspect ratios (`16:9`, `9:16`), resolutions (`1K`, `2K`, `4K`), and reference images.
|
|
66
|
+
- `thumbnailgpt_get_credits`: Query available credits and subscription status.
|
|
67
|
+
- `thumbnailgpt_list_chats`: List recent Studio conversation sessions.
|
|
68
|
+
- `thumbnailgpt_get_chat`: Retrieve full message history and generated thumbnail URLs.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## CLI Commands Reference
|
|
73
|
+
|
|
74
|
+
### Authentication
|
|
75
|
+
```bash
|
|
76
|
+
# Interactive login
|
|
77
|
+
npx thumbnailgpt login
|
|
78
|
+
|
|
79
|
+
# Clear saved credentials
|
|
80
|
+
npx thumbnailgpt logout
|
|
81
|
+
|
|
82
|
+
# Print active profile & balance
|
|
83
|
+
npx thumbnailgpt whoami
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Generate Thumbnails
|
|
87
|
+
```bash
|
|
88
|
+
# Standard 16:9 1K thumbnail
|
|
89
|
+
npx thumbnailgpt generate "Extreme Minecraft Hardcore Survival"
|
|
90
|
+
|
|
91
|
+
# 4K resolution saved to local file
|
|
92
|
+
npx thumbnailgpt generate "Cyberpunk Underground Hacker Room" --resolution 4K --output ./thumb.webp
|
|
93
|
+
|
|
94
|
+
# 9:16 Shorts format with reference image URL
|
|
95
|
+
npx thumbnailgpt generate "Shocked reaction expression" --aspect 9:16 --ref "https://example.com/face.png"
|
|
96
|
+
|
|
97
|
+
# Recreate thumbnail from YouTube video URL
|
|
98
|
+
npx thumbnailgpt generate "Upgrade lighting and contrast" --youtube "https://youtube.com/watch?v=VIDEO_ID"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Interactive Studio Mode
|
|
102
|
+
```bash
|
|
103
|
+
# Launch interactive conversation session in terminal
|
|
104
|
+
npx thumbnailgpt studio
|
|
105
|
+
|
|
106
|
+
# List all past studio conversations
|
|
107
|
+
npx thumbnailgpt chats list
|
|
108
|
+
|
|
109
|
+
# View messages and image URLs of a specific conversation
|
|
110
|
+
npx thumbnailgpt chats show <chatId>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Environment Variables
|
|
116
|
+
|
|
117
|
+
- `THUMBNAILGPT_API_KEY`: Your Personal API Key (`tgpt_live_...`). If set, overrides the stored login credentials.
|
|
118
|
+
- `THUMBNAILGPT_API_URL`: Custom backend gateway URL (defaults to `https://thumbnailgpt.com`).
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT © [ThumbnailGPT](https://thumbnailgpt.com)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CreditBalanceResponse, StudioGenerateOptions, StudioDispatchResult, StudioConversationResponse, GenerationSettledResult } from "./types.js";
|
|
2
|
+
export declare class ThumbnailGPTClient {
|
|
3
|
+
private apiKey;
|
|
4
|
+
private apiUrl;
|
|
5
|
+
constructor(apiKey?: string, apiUrl?: string);
|
|
6
|
+
private request;
|
|
7
|
+
/**
|
|
8
|
+
* Check current user credit balance and plan status
|
|
9
|
+
*/
|
|
10
|
+
getCredits(): Promise<CreditBalanceResponse>;
|
|
11
|
+
/**
|
|
12
|
+
* Dispatch a studio generation job
|
|
13
|
+
*/
|
|
14
|
+
dispatchStudio(options: StudioGenerateOptions): Promise<StudioDispatchResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch conversation messages and thumbnails
|
|
17
|
+
*/
|
|
18
|
+
getChat(chatId: string): Promise<StudioConversationResponse>;
|
|
19
|
+
/**
|
|
20
|
+
* List recent studio conversations
|
|
21
|
+
*/
|
|
22
|
+
listChats(): Promise<any>;
|
|
23
|
+
/**
|
|
24
|
+
* Asynchronously dispatch and poll until completion
|
|
25
|
+
*/
|
|
26
|
+
generateAndWait(options: StudioGenerateOptions, onProgress?: (elapsedSec: number, status: string) => void, timeoutSec?: number): Promise<GenerationSettledResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Download image to local filesystem
|
|
29
|
+
*/
|
|
30
|
+
downloadImage(imageUrl: string, outputPath: string): Promise<string>;
|
|
31
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { getApiKey, getApiUrl } from "./config.js";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export class ThumbnailGPTClient {
|
|
5
|
+
apiKey;
|
|
6
|
+
apiUrl;
|
|
7
|
+
constructor(apiKey, apiUrl) {
|
|
8
|
+
this.apiKey = (apiKey || getApiKey() || "").trim();
|
|
9
|
+
this.apiUrl = (apiUrl || getApiUrl() || "").replace(/\/$/, "");
|
|
10
|
+
if (!this.apiKey) {
|
|
11
|
+
throw new Error("Missing ThumbnailGPT API Key. Run 'thumbnailgpt login' or set the THUMBNAILGPT_API_KEY environment variable.");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async request(endpoint, options = {}) {
|
|
15
|
+
const url = `${this.apiUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
|
|
16
|
+
const headers = {
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
19
|
+
...options.headers,
|
|
20
|
+
};
|
|
21
|
+
const res = await fetch(url, { ...options, headers });
|
|
22
|
+
const data = (await res.json().catch(() => ({})));
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
const errorMsg = data.error ||
|
|
25
|
+
data.message ||
|
|
26
|
+
`Request failed with status ${res.status}: ${res.statusText}`;
|
|
27
|
+
const err = new Error(errorMsg);
|
|
28
|
+
err.status = res.status;
|
|
29
|
+
err.data = data;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check current user credit balance and plan status
|
|
36
|
+
*/
|
|
37
|
+
async getCredits() {
|
|
38
|
+
return this.request("/api/credits/balance");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Dispatch a studio generation job
|
|
42
|
+
*/
|
|
43
|
+
async dispatchStudio(options) {
|
|
44
|
+
return this.request("/api/studio", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
chatId: options.chatId,
|
|
48
|
+
feature: "recreate_thumbnail",
|
|
49
|
+
prompt: options.prompt,
|
|
50
|
+
aspectRatio: options.aspectRatio || "16:9",
|
|
51
|
+
resolution: options.resolution || "1K",
|
|
52
|
+
referenceImages: options.referenceImages || [],
|
|
53
|
+
youtubeUrl: options.youtubeUrl,
|
|
54
|
+
personaId: options.personaId,
|
|
55
|
+
baseImage: options.baseImage,
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Fetch conversation messages and thumbnails
|
|
61
|
+
*/
|
|
62
|
+
async getChat(chatId) {
|
|
63
|
+
return this.request(`/api/studio/${chatId}`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* List recent studio conversations
|
|
67
|
+
*/
|
|
68
|
+
async listChats() {
|
|
69
|
+
return this.request("/api/studio");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Asynchronously dispatch and poll until completion
|
|
73
|
+
*/
|
|
74
|
+
async generateAndWait(options, onProgress, timeoutSec = 120) {
|
|
75
|
+
// 1. Credit Pre-check
|
|
76
|
+
const credits = await this.getCredits();
|
|
77
|
+
const resolutionCosts = { "1K": 10, "2K": 20, "4K": 30 };
|
|
78
|
+
const required = resolutionCosts[options.resolution || "1K"] || 10;
|
|
79
|
+
if (credits.credits < required) {
|
|
80
|
+
throw new Error(`Insufficient credits. Required: ${required}, Available: ${credits.credits}. Top up at https://thumbnailgpt.com/pricing`);
|
|
81
|
+
}
|
|
82
|
+
// 2. Dispatch generation
|
|
83
|
+
const dispatch = await this.dispatchStudio(options);
|
|
84
|
+
const chatId = dispatch.chatId;
|
|
85
|
+
const targetThumbnailId = dispatch.thumbnailId;
|
|
86
|
+
const startTime = Date.now();
|
|
87
|
+
const maxMs = timeoutSec * 1000;
|
|
88
|
+
// 3. Poll loop
|
|
89
|
+
while (Date.now() - startTime < maxMs) {
|
|
90
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
91
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
92
|
+
try {
|
|
93
|
+
const thread = await this.getChat(chatId);
|
|
94
|
+
const thumbs = thread.thumbnails || [];
|
|
95
|
+
// Match thumbnail by ID or latest
|
|
96
|
+
const matched = thumbs.find((t) => t.id === targetThumbnailId) ||
|
|
97
|
+
thumbs[thumbs.length - 1];
|
|
98
|
+
if (matched) {
|
|
99
|
+
if (matched.status === "failed") {
|
|
100
|
+
const errorReason = matched.error_reason || "Generation failed. Your credits were automatically refunded.";
|
|
101
|
+
return {
|
|
102
|
+
success: false,
|
|
103
|
+
chatId,
|
|
104
|
+
thumbnailId: matched.id,
|
|
105
|
+
prompt: options.prompt,
|
|
106
|
+
status: "failed",
|
|
107
|
+
error: errorReason,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (matched.thumbnail_url && (matched.status === "completed" || matched.status === "success")) {
|
|
111
|
+
const remaining = await this.getCredits().catch(() => ({ credits: credits.credits - required }));
|
|
112
|
+
return {
|
|
113
|
+
success: true,
|
|
114
|
+
chatId,
|
|
115
|
+
thumbnailId: matched.id,
|
|
116
|
+
imageUrl: matched.thumbnail_url,
|
|
117
|
+
prompt: options.prompt,
|
|
118
|
+
status: "completed",
|
|
119
|
+
creditsRemaining: remaining.credits,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (onProgress) {
|
|
124
|
+
onProgress(elapsed, "Synthesizing thumbnail with AI...");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (pollErr) {
|
|
128
|
+
console.warn(`[ThumbnailGPTClient] Polling check warning:`, pollErr.message);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
chatId,
|
|
134
|
+
prompt: options.prompt,
|
|
135
|
+
status: "timeout",
|
|
136
|
+
error: `Generation timed out after ${timeoutSec}s. Check status later with 'thumbnailgpt chats show ${chatId}'`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Download image to local filesystem
|
|
141
|
+
*/
|
|
142
|
+
async downloadImage(imageUrl, outputPath) {
|
|
143
|
+
const res = await fetch(imageUrl);
|
|
144
|
+
if (!res.ok)
|
|
145
|
+
throw new Error(`Failed to download image from ${imageUrl}`);
|
|
146
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
147
|
+
const resolvedPath = path.resolve(outputPath);
|
|
148
|
+
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
149
|
+
fs.writeFileSync(resolvedPath, buffer);
|
|
150
|
+
return resolvedPath;
|
|
151
|
+
}
|
|
152
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const DEFAULT_API_URL: string;
|
|
2
|
+
export declare function getApiKey(): string | undefined;
|
|
3
|
+
export declare function setApiKey(apiKey: string): void;
|
|
4
|
+
export declare function clearApiKey(): void;
|
|
5
|
+
export declare function getApiUrl(): string;
|
|
6
|
+
export declare function setApiUrl(url: string): void;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
const config = new Conf({
|
|
3
|
+
projectName: "thumbnailgpt",
|
|
4
|
+
});
|
|
5
|
+
export const DEFAULT_API_URL = process.env.THUMBNAILGPT_API_URL || "https://thumbnailgpt.com";
|
|
6
|
+
export function getApiKey() {
|
|
7
|
+
return process.env.THUMBNAILGPT_API_KEY || config.get("apiKey");
|
|
8
|
+
}
|
|
9
|
+
export function setApiKey(apiKey) {
|
|
10
|
+
config.set("apiKey", apiKey.trim());
|
|
11
|
+
}
|
|
12
|
+
export function clearApiKey() {
|
|
13
|
+
config.delete("apiKey");
|
|
14
|
+
}
|
|
15
|
+
export function getApiUrl() {
|
|
16
|
+
return process.env.THUMBNAILGPT_API_URL || config.get("apiUrl") || DEFAULT_API_URL;
|
|
17
|
+
}
|
|
18
|
+
export function setApiUrl(url) {
|
|
19
|
+
config.set("apiUrl", url.trim().replace(/\/$/, ""));
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import readline from "readline";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { setApiKey, clearApiKey, getApiUrl } from "./config.js";
|
|
6
|
+
import { ThumbnailGPTClient } from "./client.js";
|
|
7
|
+
import { runMcpServer } from "./mcp.js";
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("thumbnailgpt")
|
|
11
|
+
.description("Official CLI and MCP server for ThumbnailGPT Studio")
|
|
12
|
+
.version("1.0.0");
|
|
13
|
+
// 1. Auth Login
|
|
14
|
+
program
|
|
15
|
+
.command("login")
|
|
16
|
+
.description("Authenticate the CLI with your Personal API Key")
|
|
17
|
+
.option("-k, --key <key>", "Pass the API key directly")
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
if (options.key) {
|
|
20
|
+
setApiKey(options.key);
|
|
21
|
+
console.log(pc.green("✔ API key saved successfully!"));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const rl = readline.createInterface({
|
|
25
|
+
input: process.stdin,
|
|
26
|
+
output: process.stdout,
|
|
27
|
+
});
|
|
28
|
+
console.log(pc.bold("\nThumbnailGPT Authentication"));
|
|
29
|
+
console.log(pc.gray("Generate an API key in your Web Studio at: Settings -> Developers -> API Keys & MCP\n"));
|
|
30
|
+
rl.question(pc.cyan("Enter your Personal API Key (tgpt_live_...): "), (key) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
const trimmed = key.trim();
|
|
33
|
+
if (!trimmed.startsWith("tgpt_live_")) {
|
|
34
|
+
console.error(pc.red("\n❌ Invalid format. API keys begin with 'tgpt_live_'"));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
setApiKey(trimmed);
|
|
38
|
+
console.log(pc.green("\n✔ Logged in successfully! Key saved to local config."));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
// 2. Auth Logout
|
|
42
|
+
program
|
|
43
|
+
.command("logout")
|
|
44
|
+
.description("Log out and clear stored API key")
|
|
45
|
+
.action(() => {
|
|
46
|
+
clearApiKey();
|
|
47
|
+
console.log(pc.green("✔ Logged out. Stored credentials cleared."));
|
|
48
|
+
});
|
|
49
|
+
// 3. Whoami / Credits
|
|
50
|
+
program
|
|
51
|
+
.command("whoami")
|
|
52
|
+
.description("Display your account and available credit balance")
|
|
53
|
+
.action(async () => {
|
|
54
|
+
try {
|
|
55
|
+
const client = new ThumbnailGPTClient();
|
|
56
|
+
const spinner = ora("Fetching account details...").start();
|
|
57
|
+
const credits = await client.getCredits();
|
|
58
|
+
spinner.stop();
|
|
59
|
+
console.log(pc.bold("\nThumbnailGPT Account"));
|
|
60
|
+
console.log(`${pc.gray("Endpoint:")} ${getApiUrl()}`);
|
|
61
|
+
console.log(`${pc.gray("Plan Status:")} ${pc.cyan(credits.subscription_status.toUpperCase())}`);
|
|
62
|
+
console.log(`${pc.gray("Credits:")} ${pc.green(pc.bold(String(credits.credits)))} available`);
|
|
63
|
+
console.log(`${pc.gray("Cost Tiers:")} 1K = 10 cr | 2K = 20 cr | 4K = 30 cr\n`);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
console.error(pc.red(`\n❌ Error: ${err.message}`));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// 4. One-shot Generate
|
|
71
|
+
program
|
|
72
|
+
.command("generate <prompt>")
|
|
73
|
+
.description("Generate a YouTube thumbnail from a prompt")
|
|
74
|
+
.option("-r, --ratio <ratio>", "Aspect ratio: 16:9 or 9:16", "16:9")
|
|
75
|
+
.option("-q, --res <resolution>", "Resolution: 1K, 2K, or 4K", "1K")
|
|
76
|
+
.option("--ref <urls...>", "Reference image file paths or URLs (max 5)")
|
|
77
|
+
.option("--yt <url>", "YouTube video URL for reference")
|
|
78
|
+
.option("-c, --chat <chatId>", "Continue an existing studio conversation thread")
|
|
79
|
+
.option("-o, --out <filepath>", "Download result directly to file (e.g. ./thumb.png)")
|
|
80
|
+
.option("--async", "Dispatch job and return immediately without waiting")
|
|
81
|
+
.action(async (prompt, options) => {
|
|
82
|
+
try {
|
|
83
|
+
const client = new ThumbnailGPTClient();
|
|
84
|
+
if (options.async) {
|
|
85
|
+
const spinner = ora("Dispatching to ThumbnailGPT Studio...").start();
|
|
86
|
+
const dispatch = await client.dispatchStudio({
|
|
87
|
+
prompt,
|
|
88
|
+
chatId: options.chat,
|
|
89
|
+
aspectRatio: options.ratio,
|
|
90
|
+
resolution: options.res,
|
|
91
|
+
referenceImages: options.ref,
|
|
92
|
+
youtubeUrl: options.yt,
|
|
93
|
+
});
|
|
94
|
+
spinner.succeed("Generation dispatched successfully!");
|
|
95
|
+
console.log(`\n${pc.bold("Chat ID:")} ${dispatch.chatId}`);
|
|
96
|
+
console.log(`${pc.bold("Thumbnail ID:")} ${dispatch.thumbnailId || "Pending"}`);
|
|
97
|
+
console.log(`Check status with: ${pc.cyan(`thumbnailgpt chats show ${dispatch.chatId}`)}\n`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const spinner = ora("Validating credits & dispatching...").start();
|
|
101
|
+
const result = await client.generateAndWait({
|
|
102
|
+
prompt,
|
|
103
|
+
chatId: options.chat,
|
|
104
|
+
aspectRatio: options.ratio,
|
|
105
|
+
resolution: options.res,
|
|
106
|
+
referenceImages: options.ref,
|
|
107
|
+
youtubeUrl: options.yt,
|
|
108
|
+
}, (elapsedSec) => {
|
|
109
|
+
spinner.text = `Synthesizing thumbnail with AI [${elapsedSec}s elapsed]...`;
|
|
110
|
+
});
|
|
111
|
+
if (!result.success || !result.imageUrl) {
|
|
112
|
+
spinner.fail(`Generation failed: ${result.error}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
spinner.succeed(pc.green("Thumbnail generated successfully!"));
|
|
116
|
+
console.log(`\n${pc.bold("Image URL:")} ${pc.cyan(result.imageUrl)}`);
|
|
117
|
+
console.log(`${pc.bold("Chat ID:")} ${result.chatId}`);
|
|
118
|
+
console.log(`${pc.bold("Credits Left:")} ${result.creditsRemaining ?? "N/A"}`);
|
|
119
|
+
console.log(`${pc.bold("Web Studio:")} https://thumbnailgpt.com/studio/${result.chatId}`);
|
|
120
|
+
if (options.out) {
|
|
121
|
+
const downloadSpinner = ora(`Downloading to ${options.out}...`).start();
|
|
122
|
+
const savedPath = await client.downloadImage(result.imageUrl, options.out);
|
|
123
|
+
downloadSpinner.succeed(`Saved to ${pc.green(savedPath)}`);
|
|
124
|
+
}
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error(pc.red(`\n❌ Error: ${err.message}`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// 5. Interactive Studio TUI REPL
|
|
133
|
+
program
|
|
134
|
+
.command("studio [initialChatId]")
|
|
135
|
+
.description("Launch an interactive conversation Studio session in your terminal")
|
|
136
|
+
.action(async (initialChatId) => {
|
|
137
|
+
try {
|
|
138
|
+
const client = new ThumbnailGPTClient();
|
|
139
|
+
let currentChatId = initialChatId;
|
|
140
|
+
console.clear();
|
|
141
|
+
console.log(pc.bold(pc.yellow("✦ ThumbnailGPT Studio CLI ✦")));
|
|
142
|
+
const credits = await client.getCredits();
|
|
143
|
+
console.log(`${pc.gray("Credits:")} ${pc.green(String(credits.credits))} • ${pc.gray("Plan:")} ${credits.subscription_status}`);
|
|
144
|
+
if (currentChatId) {
|
|
145
|
+
console.log(`${pc.gray("Resuming thread:")} ${currentChatId}`);
|
|
146
|
+
}
|
|
147
|
+
console.log(pc.gray("Type your prompt or visual tweak. Press Ctrl+C anytime to exit.\n"));
|
|
148
|
+
const rl = readline.createInterface({
|
|
149
|
+
input: process.stdin,
|
|
150
|
+
output: process.stdout,
|
|
151
|
+
});
|
|
152
|
+
const askPrompt = () => {
|
|
153
|
+
rl.question(pc.cyan("\nstudio > "), async (input) => {
|
|
154
|
+
const text = input.trim();
|
|
155
|
+
if (!text) {
|
|
156
|
+
askPrompt();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const spinner = ora("Generating thumbnail...").start();
|
|
160
|
+
try {
|
|
161
|
+
const result = await client.generateAndWait({
|
|
162
|
+
prompt: text,
|
|
163
|
+
chatId: currentChatId,
|
|
164
|
+
aspectRatio: "16:9",
|
|
165
|
+
resolution: "1K",
|
|
166
|
+
}, (elapsed) => {
|
|
167
|
+
spinner.text = `Synthesizing with AI [${elapsed}s elapsed]...`;
|
|
168
|
+
});
|
|
169
|
+
if (!result.success || !result.imageUrl) {
|
|
170
|
+
spinner.fail(`Failed: ${result.error}`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
currentChatId = result.chatId;
|
|
174
|
+
spinner.succeed(pc.green("Thumbnail updated!"));
|
|
175
|
+
console.log(`${pc.bold("Image:")} ${pc.cyan(result.imageUrl)}`);
|
|
176
|
+
console.log(`${pc.bold("Remaining:")} ${result.creditsRemaining} credits`);
|
|
177
|
+
console.log(`${pc.gray("Web Studio:")} https://thumbnailgpt.com/studio/${currentChatId}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
spinner.fail(`Error: ${err.message}`);
|
|
182
|
+
}
|
|
183
|
+
askPrompt();
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
askPrompt();
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.error(pc.red(`\n❌ Error: ${err.message}`));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
// 6. Chats Subcommand
|
|
194
|
+
const chats = program.command("chats").description("Manage studio conversations");
|
|
195
|
+
chats
|
|
196
|
+
.command("list")
|
|
197
|
+
.description("List recent studio conversations")
|
|
198
|
+
.action(async () => {
|
|
199
|
+
try {
|
|
200
|
+
const client = new ThumbnailGPTClient();
|
|
201
|
+
const spinner = ora("Fetching conversations...").start();
|
|
202
|
+
const res = await client.listChats();
|
|
203
|
+
spinner.stop();
|
|
204
|
+
const chatList = res.chats || [];
|
|
205
|
+
if (chatList.length === 0) {
|
|
206
|
+
console.log(pc.gray("No conversations found."));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
console.log(pc.bold("\nRecent Studio Conversations:\n"));
|
|
210
|
+
chatList.forEach((c) => {
|
|
211
|
+
console.log(`• ${pc.bold(c.title || "Untitled")} ${pc.gray(`(${c.id})`)}`);
|
|
212
|
+
console.log(` ${pc.gray("Updated:")} ${new Date(c.updated_at).toLocaleString()}`);
|
|
213
|
+
});
|
|
214
|
+
console.log();
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
console.error(pc.red(`\n❌ Error: ${err.message}`));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
chats
|
|
222
|
+
.command("show <chatId>")
|
|
223
|
+
.description("View conversation history and thumbnails")
|
|
224
|
+
.action(async (chatId) => {
|
|
225
|
+
try {
|
|
226
|
+
const client = new ThumbnailGPTClient();
|
|
227
|
+
const spinner = ora("Fetching thread...").start();
|
|
228
|
+
const res = await client.getChat(chatId);
|
|
229
|
+
spinner.stop();
|
|
230
|
+
console.log(pc.bold(`\nThread: ${res.chat?.title || chatId}\n`));
|
|
231
|
+
(res.messages || []).forEach((m) => {
|
|
232
|
+
const prefix = m.role === "user" ? pc.cyan("You:") : pc.yellow("Assistant:");
|
|
233
|
+
console.log(`${prefix} ${m.content}`);
|
|
234
|
+
});
|
|
235
|
+
if (res.thumbnails && res.thumbnails.length > 0) {
|
|
236
|
+
console.log(pc.bold("\nThumbnails:"));
|
|
237
|
+
res.thumbnails.forEach((t) => {
|
|
238
|
+
console.log(`• [${t.status}] ${t.thumbnail_url || "Processing"}`);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
console.log();
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
console.error(pc.red(`\n❌ Error: ${err.message}`));
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
// 7. MCP Server Mode
|
|
249
|
+
program
|
|
250
|
+
.command("mcp")
|
|
251
|
+
.description("Run the ThumbnailGPT MCP server via standard I/O (stdio)")
|
|
252
|
+
.action(async () => {
|
|
253
|
+
await runMcpServer();
|
|
254
|
+
});
|
|
255
|
+
program.parse(process.argv);
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runMcpServer(): Promise<void>;
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
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.0",
|
|
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
|
+
},
|
|
67
|
+
required: ["prompt"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "thumbnailgpt_get_credits",
|
|
72
|
+
description: "Retrieve current ThumbnailGPT available credit balance, subscription status, and resolution cost tiers.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "thumbnailgpt_list_chats",
|
|
80
|
+
description: "List recent ThumbnailGPT studio conversations and thumbnail sessions.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "thumbnailgpt_get_chat",
|
|
88
|
+
description: "Retrieve full message history and generated thumbnails for a studio conversation.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
chatId: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "UUID of the conversation thread to inspect.",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ["chatId"],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
// 2. Handle tool execution
|
|
104
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
105
|
+
const { name, arguments: args } = request.params;
|
|
106
|
+
try {
|
|
107
|
+
if (name === "thumbnailgpt_get_credits") {
|
|
108
|
+
const credits = await client.getCredits();
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: JSON.stringify({
|
|
114
|
+
availableCredits: credits.credits,
|
|
115
|
+
subscriptionStatus: credits.subscription_status,
|
|
116
|
+
costs: { "1K": "10 credits", "2K": "20 credits", "4K": "30 credits" },
|
|
117
|
+
}, null, 2),
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (name === "thumbnailgpt_generate") {
|
|
123
|
+
const prompt = args?.prompt || "";
|
|
124
|
+
const chatId = args?.chatId;
|
|
125
|
+
const aspectRatio = args?.aspectRatio || "16:9";
|
|
126
|
+
const resolution = args?.resolution || "1K";
|
|
127
|
+
const referenceImages = args?.referenceImages;
|
|
128
|
+
const youtubeUrl = args?.youtubeUrl;
|
|
129
|
+
const baseImage = args?.baseImage;
|
|
130
|
+
const result = await client.generateAndWait({
|
|
131
|
+
prompt,
|
|
132
|
+
chatId,
|
|
133
|
+
aspectRatio,
|
|
134
|
+
resolution,
|
|
135
|
+
referenceImages,
|
|
136
|
+
youtubeUrl,
|
|
137
|
+
baseImage,
|
|
138
|
+
});
|
|
139
|
+
if (!result.success || !result.imageUrl) {
|
|
140
|
+
return {
|
|
141
|
+
isError: true,
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: `❌ Thumbnail generation failed: ${result.error || "Unknown error"}\nChat ID: ${result.chatId}`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `✅ Thumbnail successfully generated!\n\n` +
|
|
155
|
+
`**Preview**: \n\n` +
|
|
156
|
+
`**Direct Image URL**: ${result.imageUrl}\n` +
|
|
157
|
+
`**Conversation Thread ID**: \`${result.chatId}\` (pass this \`chatId\` to iterate or refine)\n` +
|
|
158
|
+
`**Remaining Credits**: ${result.creditsRemaining ?? "N/A"}\n` +
|
|
159
|
+
`**Web Studio Link**: https://thumbnailgpt.com/studio/${result.chatId}`,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (name === "thumbnailgpt_list_chats") {
|
|
165
|
+
const chats = await client.listChats();
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: JSON.stringify(chats, null, 2),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (name === "thumbnailgpt_get_chat") {
|
|
176
|
+
const chatId = args?.chatId;
|
|
177
|
+
const thread = await client.getChat(chatId);
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: JSON.stringify(thread, null, 2),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
isError: true,
|
|
189
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
return {
|
|
194
|
+
isError: true,
|
|
195
|
+
content: [{ type: "text", text: `Error: ${err.message || String(err)}` }],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
const transport = new StdioServerTransport();
|
|
200
|
+
await server.connect(transport);
|
|
201
|
+
console.error("ThumbnailGPT MCP server running on stdio");
|
|
202
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type AspectRatio = "16:9" | "9:16";
|
|
2
|
+
export type Resolution = "1K" | "2K" | "4K";
|
|
3
|
+
export interface StudioGenerateOptions {
|
|
4
|
+
prompt: string;
|
|
5
|
+
chatId?: string;
|
|
6
|
+
aspectRatio?: AspectRatio;
|
|
7
|
+
resolution?: Resolution;
|
|
8
|
+
referenceImages?: string[];
|
|
9
|
+
youtubeUrl?: string;
|
|
10
|
+
personaId?: string;
|
|
11
|
+
baseImage?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface StudioDispatchResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
chatId: string;
|
|
16
|
+
batchId?: string;
|
|
17
|
+
taskId?: string;
|
|
18
|
+
thumbnailId?: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
requiredCredits?: number;
|
|
22
|
+
availableCredits?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface CreditBalanceResponse {
|
|
25
|
+
credits: number;
|
|
26
|
+
subscription_status: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ThumbnailItem {
|
|
30
|
+
id: string;
|
|
31
|
+
chat_id?: string;
|
|
32
|
+
thumbnail_url?: string;
|
|
33
|
+
status?: string;
|
|
34
|
+
error_reason?: string;
|
|
35
|
+
prompt?: string;
|
|
36
|
+
created_at: string;
|
|
37
|
+
}
|
|
38
|
+
export interface ChatMessageItem {
|
|
39
|
+
id: string;
|
|
40
|
+
chat_id: string;
|
|
41
|
+
role: "user" | "assistant";
|
|
42
|
+
content: string;
|
|
43
|
+
status?: string;
|
|
44
|
+
created_at: string;
|
|
45
|
+
}
|
|
46
|
+
export interface StudioConversationResponse {
|
|
47
|
+
success: boolean;
|
|
48
|
+
chat?: {
|
|
49
|
+
id: string;
|
|
50
|
+
title: string;
|
|
51
|
+
created_at: string;
|
|
52
|
+
updated_at: string;
|
|
53
|
+
};
|
|
54
|
+
messages?: ChatMessageItem[];
|
|
55
|
+
thumbnails?: ThumbnailItem[];
|
|
56
|
+
error?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface GenerationSettledResult {
|
|
59
|
+
success: boolean;
|
|
60
|
+
chatId: string;
|
|
61
|
+
thumbnailId?: string;
|
|
62
|
+
imageUrl?: string;
|
|
63
|
+
prompt: string;
|
|
64
|
+
status: "completed" | "failed" | "timeout";
|
|
65
|
+
error?: string;
|
|
66
|
+
creditsRemaining?: number;
|
|
67
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbnailgpt",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Official CLI and Model Context Protocol (MCP) server for ThumbnailGPT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"thumbnailgpt": "bin/thumbnailgpt.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"bin",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"mcp": "node dist/index.js mcp"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"thumbnailgpt",
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"youtube",
|
|
27
|
+
"thumbnails",
|
|
28
|
+
"ai",
|
|
29
|
+
"claude-desktop",
|
|
30
|
+
"cursor"
|
|
31
|
+
],
|
|
32
|
+
"author": "ThumbnailGPT",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
36
|
+
"commander": "^13.1.0",
|
|
37
|
+
"conf": "^13.1.0",
|
|
38
|
+
"ora": "^8.2.0",
|
|
39
|
+
"picocolors": "^1.1.1",
|
|
40
|
+
"zod": "^3.24.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.13.4",
|
|
44
|
+
"typescript": "^5.7.3"
|
|
45
|
+
}
|
|
46
|
+
}
|