imagemcp-cli 1.0.1 → 1.0.3
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/package.json +1 -1
- package/src/auth.js +58 -171
- package/src/config.js +7 -4
package/package.json
CHANGED
package/src/auth.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const http = require('http');
|
|
2
|
-
const url = require('url');
|
|
3
1
|
const { exec } = require('child_process');
|
|
4
2
|
const { saveConfig, loadConfig } = require('./config');
|
|
5
3
|
|
|
@@ -52,194 +50,83 @@ async function verifyToken(token, apiUrl = 'https://api.imagemcpserver.com') {
|
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
/**
|
|
55
|
-
*
|
|
53
|
+
* Execute Cloud Device-Code browser login flow with 5-minute timeout
|
|
56
54
|
*/
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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;
|
|
55
|
+
async function loginFlow(options = {}) {
|
|
56
|
+
const currentConfig = loadConfig();
|
|
57
|
+
const apiUrl = (options.apiUrl || currentConfig.apiUrl || 'https://api.imagemcpserver.com').replace(/\/+$/, '');
|
|
58
|
+
|
|
59
|
+
// 1. Initialize CLI Authentication session via Backend API
|
|
60
|
+
let sessionData;
|
|
61
|
+
try {
|
|
62
|
+
const initRes = await fetch(`${apiUrl}/auth/cli-session/init`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': 'application/json' }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!initRes.ok) {
|
|
68
|
+
throw new Error(`Server returned status ${initRes.status}`);
|
|
107
69
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
}
|
|
70
|
+
sessionData = await initRes.json();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw new Error(`Failed to initiate authentication session with ${apiUrl}: ${err.message}`);
|
|
73
|
+
}
|
|
122
74
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
}
|
|
75
|
+
const { code, userUrl } = sessionData;
|
|
76
|
+
const timeoutMs = 5 * 60 * 1000; // 5 Minutes (300 seconds) timeout
|
|
77
|
+
const startTime = Date.now();
|
|
146
78
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const apiUrl = options.apiUrl || currentConfig.apiUrl || 'https://api.imagemcpserver.com';
|
|
153
|
-
const webUrl = options.webUrl || 'https://imagemcpserver.com';
|
|
79
|
+
console.log(`\n\x1b[1m\x1b[36m--- ImageMCP CLI Authentication ---\x1b[0m\n`);
|
|
80
|
+
console.log(`Opening browser to log in and approve CLI session...`);
|
|
81
|
+
console.log(`URL: \x1b[34m${userUrl}\x1b[0m`);
|
|
82
|
+
console.log(`Code: \x1b[1m\x1b[33m${code}\x1b[0m\n`);
|
|
83
|
+
console.log(`Waiting for approval in browser (expires in 5 minutes)...\n`);
|
|
154
84
|
|
|
85
|
+
openBrowser(userUrl);
|
|
86
|
+
|
|
87
|
+
// 2. Poll backend every 2 seconds for user approval
|
|
155
88
|
return new Promise((resolve, reject) => {
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
164
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
|
165
|
-
});
|
|
166
|
-
return res.end();
|
|
89
|
+
const pollInterval = setInterval(async () => {
|
|
90
|
+
const elapsed = Date.now() - startTime;
|
|
91
|
+
|
|
92
|
+
// 5-Minute Auto-Timeout Check
|
|
93
|
+
if (elapsed >= timeoutMs) {
|
|
94
|
+
clearInterval(pollInterval);
|
|
95
|
+
return reject(new Error('Authentication timed out after 5 minutes. Please run npx imagemcp login again.'));
|
|
167
96
|
}
|
|
168
97
|
|
|
169
|
-
|
|
170
|
-
const
|
|
98
|
+
try {
|
|
99
|
+
const statusRes = await fetch(`${apiUrl}/auth/cli-session/${code}/status`);
|
|
100
|
+
if (!statusRes.ok) return;
|
|
101
|
+
|
|
102
|
+
const data = await statusRes.json();
|
|
171
103
|
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
server.close();
|
|
176
|
-
return reject(new Error('No authentication token received'));
|
|
104
|
+
if (data.status === 'expired') {
|
|
105
|
+
clearInterval(pollInterval);
|
|
106
|
+
return reject(new Error('Authentication session expired after 5 minutes. Please run npx imagemcp login again.'));
|
|
177
107
|
}
|
|
178
108
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const user = await verifyToken(token, apiUrl);
|
|
109
|
+
if (data.status === 'approved' && data.token) {
|
|
110
|
+
clearInterval(pollInterval);
|
|
182
111
|
|
|
183
|
-
// Save encrypted token & user
|
|
112
|
+
// Save encrypted token & user metadata to ~/.imagemcp/config.json
|
|
184
113
|
const savedFile = saveConfig({
|
|
185
|
-
apiKey: token,
|
|
114
|
+
apiKey: data.token,
|
|
186
115
|
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
|
-
}
|
|
116
|
+
user: data.user || null
|
|
194
117
|
});
|
|
195
118
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
119
|
+
resolve({
|
|
120
|
+
success: true,
|
|
121
|
+
token: data.token,
|
|
122
|
+
user: data.user,
|
|
123
|
+
savedTo: savedFile
|
|
199
124
|
});
|
|
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
125
|
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
res.end('Not found');
|
|
126
|
+
} catch (_) {
|
|
127
|
+
// Ignore temporary network errors during polling loop
|
|
222
128
|
}
|
|
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
|
-
});
|
|
129
|
+
}, 2000);
|
|
243
130
|
});
|
|
244
131
|
}
|
|
245
132
|
|
package/src/config.js
CHANGED
|
@@ -89,7 +89,8 @@ function loadConfig() {
|
|
|
89
89
|
if (!config.apiKey && fs.existsSync(LOCAL_CONFIG_FILE)) {
|
|
90
90
|
try {
|
|
91
91
|
const data = JSON.parse(fs.readFileSync(LOCAL_CONFIG_FILE, 'utf8'));
|
|
92
|
-
|
|
92
|
+
const rawToken = data.token || data.apiKey;
|
|
93
|
+
if (rawToken) config.apiKey = typeof rawToken === 'string' ? rawToken : decrypt(rawToken);
|
|
93
94
|
if (data.apiUrl) config.apiUrl = data.apiUrl;
|
|
94
95
|
if (data.user) config.user = data.user;
|
|
95
96
|
config.savedTo = LOCAL_CONFIG_FILE;
|
|
@@ -100,7 +101,8 @@ function loadConfig() {
|
|
|
100
101
|
if (!config.apiKey && fs.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
101
102
|
try {
|
|
102
103
|
const data = JSON.parse(fs.readFileSync(GLOBAL_CONFIG_FILE, 'utf8'));
|
|
103
|
-
|
|
104
|
+
const rawToken = data.token || data.apiKey;
|
|
105
|
+
if (rawToken) config.apiKey = typeof rawToken === 'string' ? rawToken : decrypt(rawToken);
|
|
104
106
|
if (data.apiUrl) config.apiUrl = data.apiUrl;
|
|
105
107
|
if (data.user) config.user = data.user;
|
|
106
108
|
config.savedTo = GLOBAL_CONFIG_FILE;
|
|
@@ -129,11 +131,12 @@ function saveConfig({ apiKey, apiUrl, user }, isLocal = false) {
|
|
|
129
131
|
} catch (_) {}
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
const
|
|
134
|
+
const tokenString = typeof apiKey === 'string' ? apiKey : (apiKey?.raw || apiKey?.token || '');
|
|
133
135
|
|
|
134
136
|
const updated = {
|
|
135
137
|
...existing,
|
|
136
|
-
token:
|
|
138
|
+
token: tokenString,
|
|
139
|
+
apiKey: tokenString,
|
|
137
140
|
apiUrl: apiUrl || existing.apiUrl || DEFAULT_API_URL,
|
|
138
141
|
user: user || existing.user || null,
|
|
139
142
|
updatedAt: new Date().toISOString()
|