imagemcp-cli 1.0.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,32 @@
1
+ # ImageMCP CLI & Auth SDK (`imagemcp`)
2
+
3
+ The official CLI and authentication SDK for [ImageMCP Server](https://api.imagemcpserver.com).
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Browser-based Authentication
8
+ ```bash
9
+ npx imagemcp login
10
+ ```
11
+ Opens your browser, lets you select or generate an API token, and securely stores an encrypted access token in `~/.imagemcp/config.json`.
12
+
13
+ ### 2. Check Auth Status
14
+ ```bash
15
+ npx imagemcp status
16
+ ```
17
+
18
+ ### 3. Log Out
19
+ ```bash
20
+ npx imagemcp logout
21
+ ```
22
+
23
+ ## Agent Skill Usage
24
+ When installed via skills CLI:
25
+ ```bash
26
+ npx skills add web5lab/imagemcpserver
27
+ ```
28
+ If not logged in, any invocation prompts:
29
+ ```
30
+ ImageMCPServer isn't connected yet. Run npx imagemcp login to connect your account.
31
+ ```
32
+ Once `npx imagemcp login` is completed, skills automatically resolve and use the saved token.
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ImageMCP CLI - Browser-based authentication & token manager
5
+ */
6
+
7
+ const { loginFlow, verifyToken } = require('../src/auth');
8
+ const { loadConfig, clearConfig, GLOBAL_CONFIG_FILE } = require('../src/config');
9
+
10
+ const isTTY = Boolean(process.stderr.isTTY);
11
+ const c = {
12
+ reset: isTTY ? '\x1b[0m' : '',
13
+ bold: isTTY ? '\x1b[1m' : '',
14
+ green: isTTY ? '\x1b[32m' : '',
15
+ yellow: isTTY ? '\x1b[33m' : '',
16
+ cyan: isTTY ? '\x1b[36m' : '',
17
+ red: isTTY ? '\x1b[31m' : '',
18
+ dim: isTTY ? '\x1b[2m' : ''
19
+ };
20
+
21
+ async function handleLogin() {
22
+ const cfg = loadConfig();
23
+ if (cfg.apiKey) {
24
+ try {
25
+ const user = await verifyToken(cfg.apiKey, cfg.apiUrl);
26
+ console.log(`${c.bold}${c.green}✓ Already logged in as ${user.name} (${user.email})${c.reset}`);
27
+ console.log(`Stored token in ${cfg.savedTo || GLOBAL_CONFIG_FILE}\n`);
28
+ console.log(`Re-authenticating...`);
29
+ } catch (_) {
30
+ // Stored token invalid/expired, proceed to login
31
+ }
32
+ }
33
+
34
+ try {
35
+ const result = await loginFlow();
36
+ console.log(`${c.bold}${c.green}✓ Successfully authenticated as ${result.user.name} (${result.user.email})!${c.reset}`);
37
+ console.log(`${c.dim}Encrypted access token stored in ${result.savedTo}${c.reset}`);
38
+ } catch (err) {
39
+ console.error(`${c.bold}${c.red}✗ Login failed: ${err.message}${c.reset}`);
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ function handleLogout() {
45
+ const cleared = clearConfig();
46
+ if (cleared) {
47
+ console.log(`${c.bold}${c.green}✓ Successfully logged out from ImageMCP Server.${c.reset}`);
48
+ console.log(`Cleared config at ${GLOBAL_CONFIG_FILE}`);
49
+ } else {
50
+ console.log(`${c.yellow}No active login session found.${c.reset}`);
51
+ }
52
+ }
53
+
54
+ async function handleStatus() {
55
+ const cfg = loadConfig();
56
+ if (!cfg.apiKey) {
57
+ console.log(`${c.bold}${c.yellow}ImageMCPServer isn't connected yet. Run npx imagemcp login to connect your account.${c.reset}`);
58
+ process.exit(1);
59
+ }
60
+
61
+ try {
62
+ const user = await verifyToken(cfg.apiKey, cfg.apiUrl);
63
+ console.log(`${c.bold}${c.cyan}ImageMCP CLI Status:${c.reset}`);
64
+ console.log(` User: ${c.bold}${user.name}${c.reset} (${user.email})`);
65
+ console.log(` Plan: ${user.plan || 'free'}`);
66
+ console.log(` Credits: ${user.credits ?? 0}`);
67
+ console.log(` API URL: ${cfg.apiUrl}`);
68
+ console.log(` Saved To: ${cfg.savedTo || GLOBAL_CONFIG_FILE}`);
69
+ } catch (err) {
70
+ console.error(`${c.bold}${c.red}Invalid or expired session. Run npx imagemcp login to re-authenticate.${c.reset}`);
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ function printHelp() {
76
+ console.log(`
77
+ ${c.bold}${c.cyan}ImageMCP CLI${c.reset} - Authentication & Token Management
78
+
79
+ ${c.bold}USAGE:${c.reset}
80
+ npx imagemcp <command>
81
+
82
+ ${c.bold}COMMANDS:${c.reset}
83
+ ${c.green}login${c.reset} Log into ImageMCPServer via browser & save encrypted token
84
+ ${c.green}status${c.reset} (whoami) Check current authentication status and user credits
85
+ ${c.green}logout${c.reset} Remove stored authentication credentials from this machine
86
+ ${c.green}help${c.reset} Display help details
87
+ `);
88
+ }
89
+
90
+ async function main() {
91
+ const args = process.argv.slice(2);
92
+ const command = args[0];
93
+
94
+ switch (command) {
95
+ case 'login':
96
+ await handleLogin();
97
+ break;
98
+ case 'logout':
99
+ handleLogout();
100
+ break;
101
+ case 'status':
102
+ case 'whoami':
103
+ await handleStatus();
104
+ break;
105
+ case 'help':
106
+ case '--help':
107
+ case '-h':
108
+ printHelp();
109
+ break;
110
+ default:
111
+ if (!command) {
112
+ printHelp();
113
+ } else {
114
+ console.error(`${c.red}Unknown command: ${command}${c.reset}`);
115
+ printHelp();
116
+ process.exit(1);
117
+ }
118
+ }
119
+ }
120
+
121
+ main();
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "imagemcp-cli",
3
+ "version": "1.0.0",
4
+ "description": "ImageMCP CLI & Authentication SDK — Seamless browser login, token encryption, and multi-model AI image generation.",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "imagemcp": "bin/imagemcp.js"
8
+ },
9
+ "scripts": {
10
+ "login": "node bin/imagemcp.js login",
11
+ "status": "node bin/imagemcp.js status",
12
+ "logout": "node bin/imagemcp.js logout"
13
+ },
14
+ "keywords": [
15
+ "imagemcp",
16
+ "mcp",
17
+ "auth",
18
+ "cli",
19
+ "ai",
20
+ "image-generation",
21
+ "skills"
22
+ ],
23
+ "author": "web5lab",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ }
28
+ }
package/src/auth.js ADDED
@@ -0,0 +1,250 @@
1
+ const http = require('http');
2
+ const url = require('url');
3
+ const { exec } = require('child_process');
4
+ const { saveConfig, loadConfig } = require('./config');
5
+
6
+ /**
7
+ * Open URL in default system web browser across OS platforms
8
+ */
9
+ function openBrowser(targetUrl) {
10
+ const platform = process.platform;
11
+ let command = '';
12
+
13
+ if (platform === 'darwin') {
14
+ command = `open "${targetUrl}"`;
15
+ } else if (platform === 'win32') {
16
+ command = `start "" "${targetUrl}"`;
17
+ } else {
18
+ command = `xdg-open "${targetUrl}"`;
19
+ }
20
+
21
+ exec(command, (err) => {
22
+ if (err) {
23
+ console.error(`\x1b[33mNote: Could not open browser automatically. Please open this link manually:\x1b[0m`);
24
+ console.error(` \x1b[36m${targetUrl}\x1b[0m\n`);
25
+ }
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Verify token with ImageMCP Server backend
31
+ */
32
+ async function verifyToken(token, apiUrl = 'https://api.imagemcpserver.com') {
33
+ const backendUrl = apiUrl.replace(/\/+$/, '');
34
+ const res = await fetch(`${backendUrl}/auth/user-data`, {
35
+ headers: {
36
+ 'Authorization': `Bearer ${token}`,
37
+ 'Content-Type': 'application/json'
38
+ }
39
+ });
40
+
41
+ if (!res.ok) {
42
+ const errorData = await res.json().catch(() => ({}));
43
+ throw new Error(errorData.message || `Authentication failed with status ${res.status}`);
44
+ }
45
+
46
+ const data = await res.json();
47
+ if (!data.user) {
48
+ throw new Error('User profile data not returned by backend');
49
+ }
50
+
51
+ return data.user;
52
+ }
53
+
54
+ /**
55
+ * HTML Response template shown in browser after login callback
56
+ */
57
+ function getSuccessHtml(userName, userEmail) {
58
+ return `<!DOCTYPE html>
59
+ <html lang="en">
60
+ <head>
61
+ <meta charset="UTF-8">
62
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
63
+ <title>ImageMCP CLI Authentication Successful</title>
64
+ <style>
65
+ body {
66
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
67
+ background-color: #0f172a;
68
+ color: #f8fafc;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ height: 100vh;
73
+ margin: 0;
74
+ }
75
+ .card {
76
+ background: #1e293b;
77
+ border: 1px solid #334155;
78
+ border-radius: 16px;
79
+ padding: 40px;
80
+ text-align: center;
81
+ max-width: 440px;
82
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
83
+ }
84
+ .icon {
85
+ width: 64px;
86
+ height: 64px;
87
+ background: #10b981;
88
+ color: #ffffff;
89
+ border-radius: 50%;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ font-size: 32px;
94
+ margin: 0 auto 24px;
95
+ }
96
+ h1 { font-size: 24px; margin: 0 0 12px; font-weight: 700; color: #ffffff; }
97
+ p { color: #94a3b8; font-size: 15px; margin: 0 0 24px; line-height: 1.5; }
98
+ .user-badge {
99
+ background: #334155;
100
+ border-radius: 8px;
101
+ padding: 12px 16px;
102
+ font-size: 14px;
103
+ color: #38bdf8;
104
+ font-weight: 600;
105
+ margin-bottom: 24px;
106
+ word-break: break-all;
107
+ }
108
+ .footer { font-size: 13px; color: #64748b; }
109
+ </style>
110
+ </head>
111
+ <body>
112
+ <div class="card">
113
+ <div class="icon">✓</div>
114
+ <h1>CLI Connected!</h1>
115
+ <p>ImageMCP Server CLI has been successfully authenticated on this machine.</p>
116
+ <div class="user-badge">${userName || 'Authenticated User'} (${userEmail || 'ImageMCP Account'})</div>
117
+ <div class="footer">You can safely close this browser window and return to your terminal.</div>
118
+ </div>
119
+ </body>
120
+ </html>`;
121
+ }
122
+
123
+ function getErrorHtml(errorMessage) {
124
+ return `<!DOCTYPE html>
125
+ <html lang="en">
126
+ <head>
127
+ <meta charset="UTF-8">
128
+ <title>Authentication Failed - ImageMCP</title>
129
+ <style>
130
+ body { font-family: sans-serif; background: #0f172a; color: #f8fafc; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
131
+ .card { background: #1e293b; border: 1px solid #ef4444; border-radius: 16px; padding: 40px; text-align: center; max-width: 440px; }
132
+ .icon { font-size: 36px; margin-bottom: 16px; color: #ef4444; }
133
+ h1 { margin: 0 0 12px; color: #f87171; }
134
+ p { color: #cbd5e1; }
135
+ </style>
136
+ </head>
137
+ <body>
138
+ <div class="card">
139
+ <div class="icon">✕</div>
140
+ <h1>Authentication Failed</h1>
141
+ <p>${errorMessage || 'An error occurred during authentication.'}</p>
142
+ </div>
143
+ </body>
144
+ </html>`;
145
+ }
146
+
147
+ /**
148
+ * Execute interactive browser login flow
149
+ */
150
+ function loginFlow(options = {}) {
151
+ const currentConfig = loadConfig();
152
+ const apiUrl = options.apiUrl || currentConfig.apiUrl || 'https://api.imagemcpserver.com';
153
+ const webUrl = options.webUrl || process.env.IMAGEMCP_WEB_URL || 'http://localhost:5173';
154
+
155
+ return new Promise((resolve, reject) => {
156
+ const server = http.createServer(async (req, res) => {
157
+ const parsedUrl = url.parse(req.url, true);
158
+
159
+ // Handle CORS for options request
160
+ if (req.method === 'OPTIONS') {
161
+ res.writeHead(204, {
162
+ 'Access-Control-Allow-Origin': '*',
163
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
164
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
165
+ });
166
+ return res.end();
167
+ }
168
+
169
+ if (parsedUrl.pathname === '/callback') {
170
+ const token = parsedUrl.query.token;
171
+
172
+ if (!token) {
173
+ res.writeHead(400, { 'Content-Type': 'text/html' });
174
+ res.end(getErrorHtml('No authentication token received in callback.'));
175
+ server.close();
176
+ return reject(new Error('No authentication token received'));
177
+ }
178
+
179
+ try {
180
+ // Verify token against ImageMCP backend
181
+ const user = await verifyToken(token, apiUrl);
182
+
183
+ // Save encrypted token & user data to ~/.imagemcp/config.json
184
+ const savedFile = saveConfig({
185
+ apiKey: token,
186
+ apiUrl,
187
+ user: {
188
+ id: user._id,
189
+ name: user.name,
190
+ email: user.email,
191
+ plan: user.plan || 'free',
192
+ credits: user.credits ?? 0
193
+ }
194
+ });
195
+
196
+ res.writeHead(200, {
197
+ 'Content-Type': 'text/html',
198
+ 'Access-Control-Allow-Origin': '*'
199
+ });
200
+ res.end(getSuccessHtml(user.name, user.email));
201
+
202
+ // Close server and resolve CLI login process
203
+ setTimeout(() => {
204
+ server.close();
205
+ resolve({
206
+ success: true,
207
+ token,
208
+ user,
209
+ savedTo: savedFile
210
+ });
211
+ }, 500);
212
+
213
+ } catch (err) {
214
+ res.writeHead(401, { 'Content-Type': 'text/html' });
215
+ res.end(getErrorHtml(`Failed to verify token: ${err.message}`));
216
+ server.close();
217
+ reject(err);
218
+ }
219
+ } else {
220
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
221
+ res.end('Not found');
222
+ }
223
+ });
224
+
225
+ // Listen on random available port or fallback port
226
+ server.listen(0, '127.0.0.1', () => {
227
+ const address = server.address();
228
+ const port = address.port;
229
+
230
+ const authUrl = `${webUrl.replace(/\/+$/, '')}/cli-login?port=${port}&apiUrl=${encodeURIComponent(apiUrl)}`;
231
+
232
+ console.log(`\n\x1b[1m\x1b[36m--- ImageMCP CLI Authentication ---\x1b[0m\n`);
233
+ console.log(`Opening browser to authenticate with ImageMCPServer...`);
234
+ console.log(`If browser does not open automatically, visit:\n \x1b[34m${authUrl}\x1b[0m\n`);
235
+ console.log(`Waiting for authentication...\n`);
236
+
237
+ openBrowser(authUrl);
238
+ });
239
+
240
+ server.on('error', (err) => {
241
+ reject(new Error(`Local authentication server failed: ${err.message}`));
242
+ });
243
+ });
244
+ }
245
+
246
+ module.exports = {
247
+ openBrowser,
248
+ verifyToken,
249
+ loginFlow
250
+ };
package/src/config.js ADDED
@@ -0,0 +1,169 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const crypto = require('crypto');
5
+
6
+ // Configuration directory & file paths
7
+ const GLOBAL_CONFIG_DIR = path.join(os.homedir(), '.imagemcp');
8
+ const GLOBAL_CONFIG_FILE = path.join(GLOBAL_CONFIG_DIR, 'config.json');
9
+ const LOCAL_CONFIG_DIR = path.join(process.cwd(), '.imagemcp');
10
+ const LOCAL_CONFIG_FILE = path.join(LOCAL_CONFIG_DIR, 'config.json');
11
+
12
+ const DEFAULT_API_URL = process.env.IMAGEMCP_API_URL || 'https://api.imagemcpserver.com';
13
+
14
+ // Derive machine-specific key for AES-256-GCM encryption
15
+ function getMasterKey() {
16
+ const secretInfo = `${os.hostname()}-${os.userInfo().username}-imagemcp-secure-salt-v1`;
17
+ return crypto.createHash('sha256').update(secretInfo).digest();
18
+ }
19
+
20
+ /**
21
+ * Encrypt string data using AES-256-GCM
22
+ */
23
+ function encrypt(text) {
24
+ if (!text) return null;
25
+ try {
26
+ const iv = crypto.randomBytes(12);
27
+ const key = getMasterKey();
28
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
29
+
30
+ let encrypted = cipher.update(text, 'utf8', 'hex');
31
+ encrypted += cipher.final('hex');
32
+ const authTag = cipher.getAuthTag().toString('hex');
33
+
34
+ return {
35
+ encrypted,
36
+ iv: iv.toString('hex'),
37
+ authTag
38
+ };
39
+ } catch (err) {
40
+ // Fallback to unencrypted in case of crypto error
41
+ return { raw: text };
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Decrypt data structure produced by encrypt()
47
+ */
48
+ function decrypt(payload) {
49
+ if (!payload) return '';
50
+ if (typeof payload === 'string') return payload; // Legacy / plain text support
51
+ if (payload.raw) return payload.raw;
52
+
53
+ try {
54
+ const key = getMasterKey();
55
+ const iv = Buffer.from(payload.iv, 'hex');
56
+ const authTag = Buffer.from(payload.authTag, 'hex');
57
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
58
+ decipher.setAuthTag(authTag);
59
+
60
+ let decrypted = decipher.update(payload.encrypted, 'hex', 'utf8');
61
+ decrypted += decipher.final('utf8');
62
+ return decrypted;
63
+ } catch (err) {
64
+ // If decryption fails, check if plain text token exists
65
+ return payload.encrypted || '';
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Load active config from env, local file, or global ~/.imagemcp/config.json
71
+ */
72
+ function loadConfig() {
73
+ let config = {
74
+ apiKey: '',
75
+ apiUrl: DEFAULT_API_URL,
76
+ user: null,
77
+ savedTo: null
78
+ };
79
+
80
+ // 1. Environment variable override
81
+ if (process.env.IMAGEMCP_API_KEY || process.env.IMAGEMCP_KEY || process.env.IMAGEMCP_TOKEN) {
82
+ config.apiKey = process.env.IMAGEMCP_API_KEY || process.env.IMAGEMCP_KEY || process.env.IMAGEMCP_TOKEN;
83
+ }
84
+ if (process.env.IMAGEMCP_API_URL) {
85
+ config.apiUrl = process.env.IMAGEMCP_API_URL;
86
+ }
87
+
88
+ // 2. Local config file (.imagemcp/config.json)
89
+ if (!config.apiKey && fs.existsSync(LOCAL_CONFIG_FILE)) {
90
+ try {
91
+ const data = JSON.parse(fs.readFileSync(LOCAL_CONFIG_FILE, 'utf8'));
92
+ if (data.token) config.apiKey = decrypt(data.token);
93
+ if (data.apiUrl) config.apiUrl = data.apiUrl;
94
+ if (data.user) config.user = data.user;
95
+ config.savedTo = LOCAL_CONFIG_FILE;
96
+ } catch (_) {}
97
+ }
98
+
99
+ // 3. Global config file (~/.imagemcp/config.json)
100
+ if (!config.apiKey && fs.existsSync(GLOBAL_CONFIG_FILE)) {
101
+ try {
102
+ const data = JSON.parse(fs.readFileSync(GLOBAL_CONFIG_FILE, 'utf8'));
103
+ if (data.token) config.apiKey = decrypt(data.token);
104
+ if (data.apiUrl) config.apiUrl = data.apiUrl;
105
+ if (data.user) config.user = data.user;
106
+ config.savedTo = GLOBAL_CONFIG_FILE;
107
+ } catch (_) {}
108
+ }
109
+
110
+ config.apiUrl = (config.apiUrl || DEFAULT_API_URL).replace(/\/+$/, '');
111
+ return config;
112
+ }
113
+
114
+ /**
115
+ * Save configuration to global ~/.imagemcp/config.json or local .imagemcp/config.json
116
+ */
117
+ function saveConfig({ apiKey, apiUrl, user }, isLocal = false) {
118
+ const configFile = isLocal ? LOCAL_CONFIG_FILE : GLOBAL_CONFIG_FILE;
119
+ const configDir = isLocal ? LOCAL_CONFIG_DIR : GLOBAL_CONFIG_DIR;
120
+
121
+ if (!fs.existsSync(configDir)) {
122
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
123
+ }
124
+
125
+ let existing = {};
126
+ if (fs.existsSync(configFile)) {
127
+ try {
128
+ existing = JSON.parse(fs.readFileSync(configFile, 'utf8'));
129
+ } catch (_) {}
130
+ }
131
+
132
+ const encryptedToken = encrypt(apiKey);
133
+
134
+ const updated = {
135
+ ...existing,
136
+ token: encryptedToken,
137
+ apiUrl: apiUrl || existing.apiUrl || DEFAULT_API_URL,
138
+ user: user || existing.user || null,
139
+ updatedAt: new Date().toISOString()
140
+ };
141
+
142
+ fs.writeFileSync(configFile, JSON.stringify(updated, null, 2), { encoding: 'utf8', mode: 0o600 });
143
+ return configFile;
144
+ }
145
+
146
+ /**
147
+ * Clear configuration / token
148
+ */
149
+ function clearConfig(isLocal = false) {
150
+ const configFile = isLocal ? LOCAL_CONFIG_FILE : GLOBAL_CONFIG_FILE;
151
+ if (fs.existsSync(configFile)) {
152
+ fs.unlinkSync(configFile);
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+
158
+ module.exports = {
159
+ GLOBAL_CONFIG_DIR,
160
+ GLOBAL_CONFIG_FILE,
161
+ LOCAL_CONFIG_DIR,
162
+ LOCAL_CONFIG_FILE,
163
+ DEFAULT_API_URL,
164
+ loadConfig,
165
+ saveConfig,
166
+ clearConfig,
167
+ encrypt,
168
+ decrypt
169
+ };
package/src/index.js ADDED
@@ -0,0 +1,32 @@
1
+ const config = require('./config');
2
+ const auth = require('./auth');
3
+
4
+ /**
5
+ * Get active API token or null if unauthenticated
6
+ */
7
+ function getToken() {
8
+ const cfg = config.loadConfig();
9
+ return cfg.apiKey || null;
10
+ }
11
+
12
+ /**
13
+ * Check authentication or throw helpful error message
14
+ */
15
+ function requireAuth(options = {}) {
16
+ const cfg = config.loadConfig();
17
+ if (!cfg.apiKey) {
18
+ const message = options.customMessage || "ImageMCPServer isn't connected yet. Run npx imagemcp login to connect your account.";
19
+ const err = new Error(message);
20
+ err.code = 'UNAUTHENTICATED';
21
+ err.hint = "Run 'npx imagemcp login' in your terminal to log in via browser.";
22
+ throw err;
23
+ }
24
+ return cfg;
25
+ }
26
+
27
+ module.exports = {
28
+ ...config,
29
+ ...auth,
30
+ getToken,
31
+ requireAuth
32
+ };