spritecook-mcp 0.0.1 → 0.1.0

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 ADDED
@@ -0,0 +1,69 @@
1
+ # spritecook-mcp
2
+
3
+ Connect your AI agent (Cursor, VS Code, Claude Desktop, Claude Code) to [SpriteCook](https://spritecook.ai) for AI-powered pixel art and game asset generation.
4
+
5
+ ## Quick Setup
6
+
7
+ ```bash
8
+ npx spritecook-mcp setup
9
+ ```
10
+
11
+ This will:
12
+
13
+ 1. **Authenticate** your SpriteCook account (browser-based or manual API key)
14
+ 2. **Detect** your editors (Cursor, VS Code, Claude Desktop, Claude Code)
15
+ 3. **Configure** MCP connections automatically
16
+ 4. **Install** an optional agent skill for smarter AI integration
17
+
18
+ ## What You Get
19
+
20
+ After setup, your AI agent can generate pixel art and game assets directly:
21
+
22
+ > "Generate a 64x64 pixel art sword sprite"
23
+
24
+ > "Create a character sprite sheet for my platformer game"
25
+
26
+ > "Make a set of potion icons with transparent backgrounds"
27
+
28
+ ## Manual Configuration
29
+
30
+ If you prefer to configure manually, add this to your editor's MCP config:
31
+
32
+ **Cursor** (`.cursor/mcp.json`):
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "spritecook": {
38
+ "url": "https://api.spritecook.ai/mcp/",
39
+ "headers": { "Authorization": "Bearer YOUR_API_KEY" }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ **VS Code** (`.vscode/settings.json`):
46
+
47
+ ```json
48
+ {
49
+ "mcp": {
50
+ "servers": {
51
+ "spritecook": {
52
+ "type": "http",
53
+ "url": "https://api.spritecook.ai/mcp/",
54
+ "headers": { "Authorization": "Bearer YOUR_API_KEY" }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Environment Variables
62
+
63
+ - `SPRITECOOK_API_URL` - Override the API base URL (for local development)
64
+
65
+ ## Links
66
+
67
+ - [SpriteCook](https://spritecook.ai) - AI game asset generator
68
+ - [API Documentation](https://spritecook.ai/docs) - Full API reference
69
+ - [Get API Key](https://app.spritecook.ai) - Sign up and manage API keys
package/bin/cli.mjs ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../src/setup.mjs';
4
+
5
+ run().catch((err) => {
6
+ console.error('\nUnexpected error:', err.message || err);
7
+ process.exit(1);
8
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spritecook-mcp",
3
- "version": "0.0.1",
4
- "description": "SpriteCook MCP Server - Model Context Protocol server for AI-powered pixel art generation. Connect your AI agent to SpriteCook for game asset creation.",
3
+ "version": "0.1.0",
4
+ "description": "SpriteCook MCP Server - Connect your AI agent (Cursor, VS Code, Claude) to SpriteCook for pixel art and game asset generation.",
5
5
  "keywords": [
6
6
  "spritecook",
7
7
  "mcp",
@@ -11,7 +11,8 @@
11
11
  "ai-agent",
12
12
  "cursor",
13
13
  "claude",
14
- "vscode"
14
+ "vscode",
15
+ "cli"
15
16
  ],
16
17
  "homepage": "https://spritecook.ai",
17
18
  "repository": {
@@ -20,5 +21,19 @@
20
21
  },
21
22
  "license": "MIT",
22
23
  "author": "SpriteCook <hello@spritecook.ai>",
23
- "main": "index.js"
24
+ "type": "module",
25
+ "bin": {
26
+ "spritecook-mcp": "./bin/cli.mjs"
27
+ },
28
+ "files": [
29
+ "bin/",
30
+ "src/",
31
+ "README.md"
32
+ ],
33
+ "dependencies": {
34
+ "chalk": "^5.3.0",
35
+ "open": "^10.1.0",
36
+ "ora": "^8.1.0",
37
+ "prompts": "^2.4.2"
38
+ }
24
39
  }
package/src/auth.mjs ADDED
@@ -0,0 +1,156 @@
1
+ import prompts from 'prompts';
2
+ import open from 'open';
3
+ import { spinner, error, info } from './ui.mjs';
4
+ import { getApiBase } from './config.mjs';
5
+
6
+ /**
7
+ * Authenticate via the browser-based device authorization flow.
8
+ * Returns the raw API key string on success, or null on failure.
9
+ */
10
+ export async function deviceFlow() {
11
+ const apiBase = getApiBase();
12
+
13
+ // Step 1: Start a device auth session
14
+ let session;
15
+ try {
16
+ const res = await fetch(`${apiBase}/v1/api/device-auth/start`, {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ });
20
+ if (!res.ok) {
21
+ const body = await res.text();
22
+ error(`Failed to start device auth: ${res.status} ${body}`);
23
+ return null;
24
+ }
25
+ session = await res.json();
26
+ } catch (err) {
27
+ error(`Could not connect to SpriteCook API at ${apiBase}`);
28
+ error(err.message || String(err));
29
+ return null;
30
+ }
31
+
32
+ const { session_id, device_code, connect_url, expires_in } = session;
33
+
34
+ // Step 2: Open the browser
35
+ info(`Your verification code: ${device_code}`);
36
+ console.log();
37
+
38
+ try {
39
+ await open(connect_url);
40
+ info('Browser opened. Please log in and click "Authorize".');
41
+ } catch {
42
+ info(`Could not open browser automatically.`);
43
+ info(`Please open this URL manually:`);
44
+ console.log(` ${connect_url}`);
45
+ }
46
+
47
+ // Step 3: Poll for completion
48
+ const spin = spinner('Waiting for authorization...');
49
+ spin.start();
50
+
51
+ const pollInterval = 3000; // 3 seconds
52
+ const deadline = Date.now() + expires_in * 1000;
53
+
54
+ while (Date.now() < deadline) {
55
+ await sleep(pollInterval);
56
+
57
+ try {
58
+ const res = await fetch(`${apiBase}/v1/api/device-auth/poll/${session_id}`);
59
+ if (!res.ok) {
60
+ continue; // Retry on transient errors
61
+ }
62
+ const data = await res.json();
63
+
64
+ if (data.status === 'completed' && data.api_key) {
65
+ spin.succeed(' Authorized!');
66
+ return data.api_key;
67
+ }
68
+
69
+ if (data.status === 'expired') {
70
+ spin.fail(' Session expired.');
71
+ error('The authorization session expired. Please try again.');
72
+ return null;
73
+ }
74
+
75
+ // status === 'pending' -- keep polling
76
+ } catch {
77
+ // Network hiccup, keep polling
78
+ }
79
+ }
80
+
81
+ spin.fail(' Timed out waiting for authorization.');
82
+ error('Authorization timed out. Please try again.');
83
+ return null;
84
+ }
85
+
86
+ /**
87
+ * Authenticate by having the user paste their API key manually.
88
+ * Returns the raw API key string on success, or null on failure.
89
+ */
90
+ export async function manualKeyEntry() {
91
+ const response = await prompts({
92
+ type: 'text',
93
+ name: 'key',
94
+ message: 'Paste your SpriteCook API key (sc_live_...):',
95
+ validate: (val) => {
96
+ if (!val || !val.startsWith('sc_live_')) {
97
+ return 'API key must start with sc_live_';
98
+ }
99
+ if (val.length < 20) {
100
+ return 'API key seems too short';
101
+ }
102
+ return true;
103
+ },
104
+ });
105
+
106
+ if (!response.key) {
107
+ return null; // User cancelled
108
+ }
109
+
110
+ return response.key;
111
+ }
112
+
113
+ /**
114
+ * Prompt the user to choose an auth method and execute it.
115
+ * Returns the API key or null.
116
+ */
117
+ export async function authenticate() {
118
+ const response = await prompts({
119
+ type: 'select',
120
+ name: 'method',
121
+ message: 'How would you like to authenticate?',
122
+ choices: [
123
+ { title: 'Connect via browser (recommended)', value: 'browser' },
124
+ { title: 'Enter API key manually', value: 'manual' },
125
+ ],
126
+ initial: 0,
127
+ });
128
+
129
+ if (!response.method) {
130
+ return null; // User cancelled
131
+ }
132
+
133
+ if (response.method === 'browser') {
134
+ const key = await deviceFlow();
135
+ if (!key) {
136
+ info('Browser auth failed. You can try entering your key manually.');
137
+ const fallback = await prompts({
138
+ type: 'confirm',
139
+ name: 'retry',
140
+ message: 'Try manual key entry instead?',
141
+ initial: true,
142
+ });
143
+ if (fallback.retry) {
144
+ return manualKeyEntry();
145
+ }
146
+ return null;
147
+ }
148
+ return key;
149
+ }
150
+
151
+ return manualKeyEntry();
152
+ }
153
+
154
+ function sleep(ms) {
155
+ return new Promise((resolve) => setTimeout(resolve, ms));
156
+ }
package/src/config.mjs ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared configuration for the SpriteCook CLI.
3
+ */
4
+
5
+ /** Get the API base URL (env override for local dev/testing) */
6
+ export function getApiBase() {
7
+ return process.env.SPRITECOOK_API_URL || 'https://api.spritecook.ai';
8
+ }
9
+
10
+ /** Get the MCP server URL */
11
+ export function getMcpUrl() {
12
+ return `${getApiBase()}/mcp/`;
13
+ }
@@ -0,0 +1,214 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { success, warn, info } from './ui.mjs';
5
+ import { getMcpUrl } from './config.mjs';
6
+
7
+ /**
8
+ * Detect which editors/IDEs are available and return a list of
9
+ * { name, detected, configPath, write(apiKey) } objects.
10
+ */
11
+ export function detectEditors() {
12
+ const cwd = process.cwd();
13
+ const home = homedir();
14
+ const editors = [];
15
+
16
+ // Cursor - project-level .cursor/mcp.json
17
+ const cursorDir = join(cwd, '.cursor');
18
+ editors.push({
19
+ name: 'Cursor',
20
+ detected: existsSync(cursorDir),
21
+ configPath: join(cursorDir, 'mcp.json'),
22
+ write: (apiKey) => writeCursorConfig(cursorDir, apiKey),
23
+ });
24
+
25
+ // VS Code - project-level .vscode/settings.json
26
+ const vscodeDir = join(cwd, '.vscode');
27
+ editors.push({
28
+ name: 'VS Code',
29
+ detected: existsSync(vscodeDir),
30
+ configPath: join(vscodeDir, 'settings.json'),
31
+ write: (apiKey) => writeVSCodeConfig(vscodeDir, apiKey),
32
+ });
33
+
34
+ // Claude Desktop - platform-specific
35
+ const claudeDesktopPath = getClaudeDesktopConfigPath();
36
+ editors.push({
37
+ name: 'Claude Desktop',
38
+ detected: claudeDesktopPath ? existsSync(join(claudeDesktopPath, '..')) : false,
39
+ configPath: claudeDesktopPath,
40
+ write: (apiKey) => writeClaudeDesktopConfig(claudeDesktopPath, apiKey),
41
+ });
42
+
43
+ // Claude Code - ~/.claude.json or ~/.claude/settings.json
44
+ const claudeCodePath1 = join(home, '.claude.json');
45
+ const claudeCodePath2 = join(home, '.claude', 'settings.json');
46
+ const claudeCodeDetected = existsSync(claudeCodePath1) || existsSync(claudeCodePath2);
47
+ const claudeCodeConfigPath = existsSync(claudeCodePath2) ? claudeCodePath2 : claudeCodePath1;
48
+ editors.push({
49
+ name: 'Claude Code',
50
+ detected: claudeCodeDetected,
51
+ configPath: claudeCodeConfigPath,
52
+ write: (apiKey) => writeClaudeCodeConfig(claudeCodeConfigPath, apiKey),
53
+ });
54
+
55
+ return editors;
56
+ }
57
+
58
+ /**
59
+ * Write MCP config for all detected editors.
60
+ * Returns the count of configs successfully written.
61
+ */
62
+ export function writeConfigs(editors, apiKey) {
63
+ let written = 0;
64
+
65
+ for (const editor of editors) {
66
+ if (!editor.detected) continue;
67
+
68
+ try {
69
+ editor.write(apiKey);
70
+ success(`${editor.configPath}`);
71
+ written++;
72
+ } catch (err) {
73
+ warn(`Failed to write ${editor.name} config: ${err.message}`);
74
+ }
75
+ }
76
+
77
+ return written;
78
+ }
79
+
80
+ // ── Writer Functions ────────────────────────────────────────────────────
81
+
82
+ function writeCursorConfig(cursorDir, apiKey) {
83
+ const configPath = join(cursorDir, 'mcp.json');
84
+ const mcpUrl = getMcpUrl();
85
+
86
+ // Read existing config or start fresh
87
+ let config = {};
88
+ if (existsSync(configPath)) {
89
+ try {
90
+ config = JSON.parse(readFileSync(configPath, 'utf-8'));
91
+ } catch {
92
+ // Corrupted file, start fresh
93
+ }
94
+ }
95
+
96
+ // Merge the spritecook server entry
97
+ if (!config.mcpServers) config.mcpServers = {};
98
+ config.mcpServers.spritecook = {
99
+ url: mcpUrl,
100
+ headers: {
101
+ Authorization: `Bearer ${apiKey}`,
102
+ },
103
+ };
104
+
105
+ ensureDir(cursorDir);
106
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
107
+ }
108
+
109
+ function writeVSCodeConfig(vscodeDir, apiKey) {
110
+ const configPath = join(vscodeDir, 'settings.json');
111
+ const mcpUrl = getMcpUrl();
112
+
113
+ // Read existing config or start fresh
114
+ let config = {};
115
+ if (existsSync(configPath)) {
116
+ try {
117
+ config = JSON.parse(readFileSync(configPath, 'utf-8'));
118
+ } catch {
119
+ // Corrupted file, start fresh
120
+ }
121
+ }
122
+
123
+ // Merge into mcp.servers without overwriting other servers
124
+ if (!config.mcp) config.mcp = {};
125
+ if (!config.mcp.servers) config.mcp.servers = {};
126
+ config.mcp.servers.spritecook = {
127
+ type: 'http',
128
+ url: mcpUrl,
129
+ headers: {
130
+ Authorization: `Bearer ${apiKey}`,
131
+ },
132
+ };
133
+
134
+ ensureDir(vscodeDir);
135
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
136
+ }
137
+
138
+ function writeClaudeDesktopConfig(configPath, apiKey) {
139
+ if (!configPath) {
140
+ warn('Claude Desktop config path not found for this platform.');
141
+ return;
142
+ }
143
+ const mcpUrl = getMcpUrl();
144
+
145
+ let config = {};
146
+ if (existsSync(configPath)) {
147
+ try {
148
+ config = JSON.parse(readFileSync(configPath, 'utf-8'));
149
+ } catch {
150
+ // Start fresh
151
+ }
152
+ }
153
+
154
+ if (!config.mcpServers) config.mcpServers = {};
155
+ config.mcpServers.spritecook = {
156
+ url: mcpUrl,
157
+ headers: {
158
+ Authorization: `Bearer ${apiKey}`,
159
+ },
160
+ };
161
+
162
+ ensureDir(join(configPath, '..'));
163
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
164
+ }
165
+
166
+ function writeClaudeCodeConfig(configPath, apiKey) {
167
+ const mcpUrl = getMcpUrl();
168
+
169
+ let config = {};
170
+ if (existsSync(configPath)) {
171
+ try {
172
+ config = JSON.parse(readFileSync(configPath, 'utf-8'));
173
+ } catch {
174
+ // Start fresh
175
+ }
176
+ }
177
+
178
+ if (!config.mcpServers) config.mcpServers = {};
179
+ config.mcpServers.spritecook = {
180
+ url: mcpUrl,
181
+ headers: {
182
+ Authorization: `Bearer ${apiKey}`,
183
+ },
184
+ };
185
+
186
+ ensureDir(join(configPath, '..'));
187
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
188
+ }
189
+
190
+ // ── Helpers ─────────────────────────────────────────────────────────────
191
+
192
+ function getClaudeDesktopConfigPath() {
193
+ const platform = process.platform;
194
+ const home = homedir();
195
+
196
+ if (platform === 'darwin') {
197
+ return join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
198
+ }
199
+ if (platform === 'win32') {
200
+ const appData = process.env.APPDATA || join(home, 'AppData', 'Roaming');
201
+ return join(appData, 'Claude', 'claude_desktop_config.json');
202
+ }
203
+ if (platform === 'linux') {
204
+ return join(home, '.config', 'Claude', 'claude_desktop_config.json');
205
+ }
206
+
207
+ return null;
208
+ }
209
+
210
+ function ensureDir(dir) {
211
+ if (!existsSync(dir)) {
212
+ mkdirSync(dir, { recursive: true });
213
+ }
214
+ }
package/src/setup.mjs ADDED
@@ -0,0 +1,76 @@
1
+ import chalk from 'chalk';
2
+ import { printBanner, step, success, info, warn, error } from './ui.mjs';
3
+ import { authenticate } from './auth.mjs';
4
+ import { verifyApiKey } from './verify.mjs';
5
+ import { detectEditors, writeConfigs } from './editors.mjs';
6
+ import { maybeInstallSkill } from './skill.mjs';
7
+
8
+ /**
9
+ * Main setup flow orchestrator.
10
+ * Called by bin/cli.mjs as the entry point.
11
+ */
12
+ export async function run() {
13
+ printBanner();
14
+
15
+ // ── Step 1: Authenticate ──────────────────────────────────────────
16
+ step(1, 'Authentication');
17
+
18
+ const apiKey = await authenticate();
19
+ if (!apiKey) {
20
+ console.log();
21
+ error('Setup cancelled. No API key obtained.');
22
+ process.exit(1);
23
+ }
24
+
25
+ // ── Step 2: Verify the key works ──────────────────────────────────
26
+ step(2, 'Verification');
27
+
28
+ const verification = await verifyApiKey(apiKey);
29
+ if (!verification.ok) {
30
+ error('API key verification failed. Please check your key and try again.');
31
+ process.exit(1);
32
+ }
33
+
34
+ // ── Step 3: Detect editors and write configs ──────────────────────
35
+ step(3, 'Editor Configuration');
36
+
37
+ const editors = detectEditors();
38
+ const detected = editors.filter((e) => e.detected);
39
+
40
+ if (detected.length === 0) {
41
+ warn('No supported editors detected in the current directory.');
42
+ info('Supported editors: Cursor, VS Code, Claude Desktop, Claude Code');
43
+ info('Run this command from your project root where .cursor/ or .vscode/ exists.');
44
+ console.log();
45
+ info('You can also configure manually. Your API key:');
46
+ console.log(` ${chalk.dim(apiKey.slice(0, 16) + '...')}`);
47
+ } else {
48
+ console.log();
49
+ info('Detected editors:');
50
+ for (const editor of editors) {
51
+ const mark = editor.detected ? chalk.green('[x]') : chalk.dim('[ ]');
52
+ const label = editor.detected ? editor.name : chalk.dim(`${editor.name} (not found)`);
53
+ console.log(` ${mark} ${label}`);
54
+ }
55
+
56
+ console.log();
57
+ info('Writing MCP config...');
58
+ const written = writeConfigs(detected, apiKey);
59
+
60
+ if (written === 0) {
61
+ warn('No configs were written. You may need to configure manually.');
62
+ }
63
+ }
64
+
65
+ // ── Step 4: Optional agent skill ─────────────────────────────────
66
+ step(4, 'Agent Skill (optional)');
67
+ await maybeInstallSkill();
68
+
69
+ // ── Done ──────────────────────────────────────────────────────────
70
+ console.log();
71
+ console.log(chalk.bold.green(' Setup complete!'));
72
+ console.log();
73
+ console.log(chalk.dim(' Try asking your AI agent:'));
74
+ console.log(chalk.white(' "Generate a 64x64 pixel art sword sprite"'));
75
+ console.log();
76
+ }
package/src/skill.mjs ADDED
@@ -0,0 +1,73 @@
1
+ import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { success, info } from './ui.mjs';
5
+
6
+ const SKILL_CONTENT = `# SpriteCook - AI Game Asset Generator
7
+
8
+ Use SpriteCook MCP tools when the user needs pixel art sprites, game assets, icons, or visual game content.
9
+
10
+ ## When to use
11
+
12
+ - User asks to "generate a sprite", "create pixel art", "make a game asset", "create an icon"
13
+ - User needs visual assets for a game project (characters, items, backgrounds, UI elements)
14
+ - User references game art, sprites, or pixel art in their request
15
+
16
+ ## Available MCP Tools
17
+
18
+ ### generate_pixel_art
19
+ Generate pixel art or detailed game assets from a text prompt.
20
+
21
+ **Parameters:**
22
+ - \`prompt\` (required): Description of what to generate (e.g. "a medieval knight with a sword")
23
+ - \`pixel\` (optional, default true): Whether to generate pixel art style
24
+ - \`size\` (optional, default 64): Sprite size in pixels (16, 32, 64, 128, 256, 512)
25
+ - \`background\` (optional, default "transparent"): Background mode ("transparent", "white", "include")
26
+
27
+ ### check_job_status
28
+ Check the status of a generation job. Returns progress and result URLs when complete.
29
+
30
+ ### get_credit_balance
31
+ Check remaining credits on the connected SpriteCook account.
32
+
33
+ ## Tips
34
+
35
+ - For game characters: use size 64 or 128 with transparent background
36
+ - For icons/items: use size 32 or 64 with transparent background
37
+ - For tilesets/backgrounds: use size 128 or 256 with "include" background
38
+ - Be specific in prompts: "a red dragon breathing fire, side view" works better than "dragon"
39
+ - The tool returns download URLs for the generated images
40
+ `;
41
+
42
+ /**
43
+ * Optionally install the SpriteCook agent skill for Cursor.
44
+ * Only offered if .cursor/ directory exists.
45
+ */
46
+ export async function maybeInstallSkill() {
47
+ const cwd = process.cwd();
48
+ const cursorDir = join(cwd, '.cursor');
49
+
50
+ if (!existsSync(cursorDir)) {
51
+ return; // Cursor not detected, skip silently
52
+ }
53
+
54
+ const response = await prompts({
55
+ type: 'confirm',
56
+ name: 'install',
57
+ message: 'Install SpriteCook agent skill for Cursor? (helps AI use SpriteCook proactively)',
58
+ initial: true,
59
+ });
60
+
61
+ if (!response.install) {
62
+ return;
63
+ }
64
+
65
+ const skillDir = join(cursorDir, 'skills', 'spritecook');
66
+ if (!existsSync(skillDir)) {
67
+ mkdirSync(skillDir, { recursive: true });
68
+ }
69
+
70
+ const skillPath = join(skillDir, 'SKILL.md');
71
+ writeFileSync(skillPath, SKILL_CONTENT, 'utf-8');
72
+ success(`Agent skill installed at .cursor/skills/spritecook/SKILL.md`);
73
+ }
package/src/ui.mjs ADDED
@@ -0,0 +1,41 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+
4
+ /** Print the SpriteCook header banner */
5
+ export function printBanner() {
6
+ console.log();
7
+ console.log(chalk.bold(' SpriteCook MCP Setup'));
8
+ console.log(chalk.dim(' Connect your AI agent to SpriteCook'));
9
+ console.log();
10
+ }
11
+
12
+ /** Print a success message */
13
+ export function success(msg) {
14
+ console.log(chalk.green(' [ok]') + ' ' + msg);
15
+ }
16
+
17
+ /** Print an info message */
18
+ export function info(msg) {
19
+ console.log(chalk.blue(' [info]') + ' ' + msg);
20
+ }
21
+
22
+ /** Print a warning message */
23
+ export function warn(msg) {
24
+ console.log(chalk.yellow(' [warn]') + ' ' + msg);
25
+ }
26
+
27
+ /** Print an error message */
28
+ export function error(msg) {
29
+ console.log(chalk.red(' [error]') + ' ' + msg);
30
+ }
31
+
32
+ /** Create a spinner */
33
+ export function spinner(text) {
34
+ return ora({ text: ' ' + text, color: 'cyan' });
35
+ }
36
+
37
+ /** Print a step header */
38
+ export function step(num, msg) {
39
+ console.log();
40
+ console.log(chalk.bold.white(` ${num}. ${msg}`));
41
+ }
package/src/verify.mjs ADDED
@@ -0,0 +1,41 @@
1
+ import { spinner, success, error } from './ui.mjs';
2
+ import { getApiBase } from './config.mjs';
3
+
4
+ /**
5
+ * Verify an API key by calling the credits endpoint.
6
+ * Returns { ok: boolean, credits?: number, tier?: string }
7
+ */
8
+ export async function verifyApiKey(apiKey) {
9
+ const spin = spinner('Verifying API key...');
10
+ spin.start();
11
+
12
+ try {
13
+ const res = await fetch(`${getApiBase()}/v1/api/credits`, {
14
+ headers: {
15
+ Authorization: `Bearer ${apiKey}`,
16
+ },
17
+ });
18
+
19
+ if (!res.ok) {
20
+ spin.fail(' Verification failed');
21
+ const body = await res.text();
22
+ if (res.status === 401) {
23
+ error('Invalid or expired API key.');
24
+ } else {
25
+ error(`API returned ${res.status}: ${body}`);
26
+ }
27
+ return { ok: false };
28
+ }
29
+
30
+ const data = await res.json();
31
+ const credits = data.credits ?? data.credits_remaining ?? 0;
32
+ const tier = data.tier || data.subscription_tier || 'free';
33
+
34
+ spin.succeed(` Verified! (${credits} credits, ${tier} tier)`);
35
+ return { ok: true, credits, tier };
36
+ } catch (err) {
37
+ spin.fail(' Verification failed');
38
+ error(`Could not reach SpriteCook API: ${err.message || err}`);
39
+ return { ok: false };
40
+ }
41
+ }
package/index.js DELETED
@@ -1,8 +0,0 @@
1
- // SpriteCook MCP Server - Model Context Protocol server for AI-powered pixel art generation
2
- // This package is a placeholder. Full MCP server package coming soon.
3
- // Visit https://spritecook.ai for more information.
4
-
5
- module.exports = {
6
- version: "0.0.1",
7
- message: "SpriteCook MCP Server coming soon. Visit https://spritecook.ai for more information.",
8
- };