ebay-mcp 1.7.9 → 1.8.2
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 +3 -0
- package/build/scripts/setup.js +544 -1158
- package/package.json +2 -2
package/build/scripts/setup.js
CHANGED
|
@@ -6,31 +6,14 @@ import axios from 'axios';
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { checkForUpdates } from '../utils/version.js';
|
|
8
8
|
import { config } from 'dotenv';
|
|
9
|
-
import { exec } from 'child_process';
|
|
10
9
|
import { fileURLToPath } from 'url';
|
|
11
10
|
import { getOAuthAuthorizationUrl } from '../config/environment.js';
|
|
12
|
-
import
|
|
11
|
+
import { defineWizard, runWizard, ClackRenderer } from 'grimoire-wizard';
|
|
13
12
|
config({ quiet: true });
|
|
14
13
|
checkForUpdates();
|
|
15
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
15
|
const __dirname = dirname(__filename);
|
|
17
16
|
const PROJECT_ROOT = join(__dirname, '../..');
|
|
18
|
-
const TOTAL_STEPS = 6;
|
|
19
|
-
const ebay = {
|
|
20
|
-
red: chalk.hex('#E53238'),
|
|
21
|
-
blue: chalk.hex('#0064D2'),
|
|
22
|
-
yellow: chalk.hex('#F5AF02'),
|
|
23
|
-
green: chalk.hex('#86B817'),
|
|
24
|
-
};
|
|
25
|
-
const ui = {
|
|
26
|
-
dim: chalk.dim,
|
|
27
|
-
bold: chalk.bold,
|
|
28
|
-
success: chalk.green,
|
|
29
|
-
warning: chalk.yellow,
|
|
30
|
-
error: chalk.red,
|
|
31
|
-
info: chalk.cyan,
|
|
32
|
-
hint: chalk.gray,
|
|
33
|
-
};
|
|
34
17
|
const MARKETPLACE_OPTIONS = [
|
|
35
18
|
{ value: 'EBAY_US', label: 'EBAY_US — United States' },
|
|
36
19
|
{ value: 'EBAY_GB', label: 'EBAY_GB — United Kingdom' },
|
|
@@ -51,6 +34,12 @@ const CONTENT_LANGUAGE_OPTIONS = [
|
|
|
51
34
|
{ value: 'fr-CA', label: 'fr-CA — French (Canada)' },
|
|
52
35
|
{ value: 'nl-BE', label: 'nl-BE — Dutch (Belgium)' },
|
|
53
36
|
];
|
|
37
|
+
const ebay = {
|
|
38
|
+
red: chalk.hex('#E53238'),
|
|
39
|
+
blue: chalk.hex('#0064D2'),
|
|
40
|
+
yellow: chalk.hex('#F5AF02'),
|
|
41
|
+
green: chalk.hex('#86B817'),
|
|
42
|
+
};
|
|
54
43
|
const LOGO = `
|
|
55
44
|
${ebay.red('███████╗')}${ebay.blue('██████╗ ')}${ebay.yellow('█████╗ ')}${ebay.green('██╗ ██╗')}
|
|
56
45
|
${ebay.red('██╔════╝')}${ebay.blue('██╔══██╗')}${ebay.yellow('██╔══██╗')}${ebay.green('╚██╗ ██╔╝')}
|
|
@@ -59,120 +48,35 @@ const LOGO = `
|
|
|
59
48
|
${ebay.red('███████╗')}${ebay.blue('██████╔╝')}${ebay.yellow('██║ ██║')}${ebay.green(' ██║ ')}
|
|
60
49
|
${ebay.red('╚══════╝')}${ebay.blue('╚═════╝ ')}${ebay.yellow('╚═╝ ╚═╝')}${ebay.green(' ╚═╝ ')}
|
|
61
50
|
`;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
function showLogo() {
|
|
72
|
-
console.log(LOGO);
|
|
73
|
-
console.log(ui.bold.white(' MCP Server Setup Wizard by Yosef Hayim Sabag\n'));
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Render a step progress bar with title.
|
|
77
|
-
*/
|
|
78
|
-
function showProgress(step, title) {
|
|
79
|
-
const filled = '●'.repeat(step);
|
|
80
|
-
const empty = '○'.repeat(TOTAL_STEPS - step);
|
|
81
|
-
const progress = `${ui.info(filled)}${ui.dim(empty)}`;
|
|
82
|
-
console.log(ui.dim('─'.repeat(60)));
|
|
83
|
-
console.log(` ${progress} ${ui.bold(`Step ${step}/${TOTAL_STEPS}`)}: ${title}`);
|
|
84
|
-
console.log(ui.dim('─'.repeat(60)) + '\n');
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Render keyboard hints for the current step.
|
|
88
|
-
*/
|
|
89
|
-
function showKeyboardHints(hints) {
|
|
90
|
-
const hintText = hints.map((h) => ui.dim(h)).join(' │ ');
|
|
91
|
-
console.log(`\n ${hintText}\n`);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Render a tip callout.
|
|
95
|
-
*/
|
|
96
|
-
function showTip(message) {
|
|
97
|
-
console.log(` ${ebay.yellow('💡 Tip:')} ${ui.dim(message)}\n`);
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Render a success line.
|
|
101
|
-
*/
|
|
102
|
-
function showSuccess(message) {
|
|
103
|
-
console.log(` ${ui.success('✓')} ${message}`);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Render an error line.
|
|
107
|
-
*/
|
|
108
|
-
function showError(message) {
|
|
109
|
-
console.log(` ${ui.error('✗')} ${message}`);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Open a URL in the default browser (cross-platform).
|
|
113
|
-
*/
|
|
114
|
-
function openBrowser(url) {
|
|
115
|
-
return new Promise((resolve, reject) => {
|
|
116
|
-
const os = platform();
|
|
117
|
-
let command;
|
|
118
|
-
switch (os) {
|
|
119
|
-
case 'darwin':
|
|
120
|
-
command = `open "${url}"`;
|
|
121
|
-
break;
|
|
122
|
-
case 'win32':
|
|
123
|
-
command = `start "" "${url}"`;
|
|
124
|
-
break;
|
|
125
|
-
default:
|
|
126
|
-
command = `xdg-open "${url}"`;
|
|
127
|
-
}
|
|
128
|
-
exec(command, (error) => {
|
|
129
|
-
if (error) {
|
|
130
|
-
reject(error);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
resolve();
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Render a warning line.
|
|
140
|
-
*/
|
|
141
|
-
function showWarning(message) {
|
|
142
|
-
console.log(` ${ui.warning('⚠')} ${message}`);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Parse authorization code from callback URL or raw code
|
|
146
|
-
* Handles both full URLs and just the code parameter
|
|
147
|
-
*/
|
|
51
|
+
const ui = {
|
|
52
|
+
dim: chalk.dim,
|
|
53
|
+
bold: chalk.bold,
|
|
54
|
+
success: chalk.green,
|
|
55
|
+
warning: chalk.yellow,
|
|
56
|
+
error: chalk.red,
|
|
57
|
+
info: chalk.cyan,
|
|
58
|
+
};
|
|
59
|
+
// ─── Business-logic helpers (preserved from original) ─────────────────────────
|
|
148
60
|
function parseAuthorizationCode(input) {
|
|
149
61
|
const trimmed = input.trim();
|
|
150
|
-
// If it looks like a URL, parse the code parameter
|
|
151
62
|
if (trimmed.includes('code=') || trimmed.includes('?') || trimmed.includes('&')) {
|
|
152
63
|
try {
|
|
153
|
-
// Handle both full URLs and query strings
|
|
154
64
|
let searchParams;
|
|
155
65
|
if (trimmed.startsWith('http')) {
|
|
156
|
-
|
|
157
|
-
searchParams = url.searchParams;
|
|
66
|
+
searchParams = new URL(trimmed).searchParams;
|
|
158
67
|
}
|
|
159
68
|
else {
|
|
160
|
-
// It might just be query params like "code=xxx&expires_in=299"
|
|
161
69
|
searchParams = new URLSearchParams(trimmed.startsWith('?') ? trimmed.slice(1) : trimmed);
|
|
162
70
|
}
|
|
163
71
|
const code = searchParams.get('code');
|
|
164
|
-
if (code)
|
|
165
|
-
// URL decode the code (it's often URL-encoded)
|
|
72
|
+
if (code)
|
|
166
73
|
return decodeURIComponent(code);
|
|
167
|
-
}
|
|
168
74
|
}
|
|
169
75
|
catch {
|
|
170
|
-
//
|
|
76
|
+
// fall through
|
|
171
77
|
}
|
|
172
78
|
}
|
|
173
|
-
// Check if it looks like a raw authorization code (starts with v^1.1#)
|
|
174
79
|
if (trimmed.startsWith('v^1.1#') || trimmed.startsWith('v%5E1.1')) {
|
|
175
|
-
// Decode if URL-encoded
|
|
176
80
|
try {
|
|
177
81
|
return decodeURIComponent(trimmed);
|
|
178
82
|
}
|
|
@@ -182,25 +86,10 @@ function parseAuthorizationCode(input) {
|
|
|
182
86
|
}
|
|
183
87
|
return null;
|
|
184
88
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Exchange authorization code for tokens using eBay API
|
|
187
|
-
* This mirrors the logic in auth/oauth.ts EbayOAuthClient.exchangeCodeForToken()
|
|
188
|
-
*/
|
|
189
89
|
async function exchangeAuthorizationCode(code, clientId, clientSecret, redirectUri, environment) {
|
|
190
|
-
const
|
|
191
|
-
? 'https://api.ebay.com/identity/v1/oauth2/token'
|
|
192
|
-
: 'https://api.sandbox.ebay.com/identity/v1/oauth2/token';
|
|
90
|
+
const baseUrl = environment === 'production' ? 'https://api.ebay.com' : 'https://api.sandbox.ebay.com';
|
|
193
91
|
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
194
|
-
const response = await axios.post(
|
|
195
|
-
grant_type: 'authorization_code',
|
|
196
|
-
code,
|
|
197
|
-
redirect_uri: redirectUri,
|
|
198
|
-
}).toString(), {
|
|
199
|
-
headers: {
|
|
200
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
201
|
-
Authorization: `Basic ${credentials}`,
|
|
202
|
-
},
|
|
203
|
-
});
|
|
92
|
+
const response = await axios.post(`${baseUrl}/identity/v1/oauth2/token`, new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: redirectUri }).toString(), { headers: { Authorization: `Basic ${credentials}`, 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
204
93
|
return {
|
|
205
94
|
accessToken: response.data.access_token,
|
|
206
95
|
refreshToken: response.data.refresh_token,
|
|
@@ -208,142 +97,37 @@ async function exchangeAuthorizationCode(code, clientId, clientSecret, redirectU
|
|
|
208
97
|
refreshTokenExpiresIn: response.data.refresh_token_expires_in,
|
|
209
98
|
};
|
|
210
99
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Get app access token using client credentials flow
|
|
213
|
-
* This mirrors the logic in auth/oauth.ts EbayOAuthClient.getOrRefreshAppAccessToken()
|
|
214
|
-
*/
|
|
215
100
|
async function getAppAccessToken(clientId, clientSecret, environment) {
|
|
216
|
-
const
|
|
217
|
-
? 'https://api.ebay.com/identity/v1/oauth2/token'
|
|
218
|
-
: 'https://api.sandbox.ebay.com/identity/v1/oauth2/token';
|
|
101
|
+
const baseUrl = environment === 'production' ? 'https://api.ebay.com' : 'https://api.sandbox.ebay.com';
|
|
219
102
|
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
220
|
-
const response = await axios.post(
|
|
103
|
+
const response = await axios.post(`${baseUrl}/identity/v1/oauth2/token`, new URLSearchParams({
|
|
221
104
|
grant_type: 'client_credentials',
|
|
222
105
|
scope: 'https://api.ebay.com/oauth/api_scope',
|
|
223
|
-
}).toString(), {
|
|
224
|
-
headers: {
|
|
225
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
226
|
-
Authorization: `Basic ${credentials}`,
|
|
227
|
-
},
|
|
228
|
-
});
|
|
106
|
+
}).toString(), { headers: { Authorization: `Basic ${credentials}`, 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
229
107
|
return response.data.access_token;
|
|
230
108
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
*/
|
|
234
|
-
async function refreshAccessToken(refreshToken, clientId, clientSecret, environment) {
|
|
235
|
-
const tokenUrl = environment === 'production'
|
|
236
|
-
? 'https://api.ebay.com/identity/v1/oauth2/token'
|
|
237
|
-
: 'https://api.sandbox.ebay.com/identity/v1/oauth2/token';
|
|
109
|
+
async function verifyRefreshToken(refreshToken, clientId, clientSecret, environment) {
|
|
110
|
+
const baseUrl = environment === 'production' ? 'https://api.ebay.com' : 'https://api.sandbox.ebay.com';
|
|
238
111
|
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
239
|
-
const
|
|
112
|
+
const tokenResponse = await axios.post(`${baseUrl}/identity/v1/oauth2/token`, new URLSearchParams({
|
|
240
113
|
grant_type: 'refresh_token',
|
|
241
114
|
refresh_token: refreshToken,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
115
|
+
scope: 'https://api.ebay.com/oauth/api_scope https://api.ebay.com/oauth/api_scope/sell.inventory',
|
|
116
|
+
}).toString(), { headers: { Authorization: `Basic ${credentials}`, 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
117
|
+
const accessToken = tokenResponse.data.access_token;
|
|
118
|
+
const identityBase = environment === 'production' ? 'https://apiz.ebay.com' : 'https://apiz.sandbox.ebay.com';
|
|
119
|
+
const userResponse = await axios.get(`${identityBase}/commerce/identity/v1/user/`, {
|
|
120
|
+
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
247
121
|
});
|
|
248
|
-
return {
|
|
249
|
-
accessToken: response.data.access_token,
|
|
250
|
-
expiresIn: response.data.expires_in,
|
|
251
|
-
};
|
|
122
|
+
return { accessToken, userInfo: userResponse.data };
|
|
252
123
|
}
|
|
253
|
-
/**
|
|
254
|
-
* Verify refresh token by getting an access token and fetching user info
|
|
255
|
-
*/
|
|
256
|
-
async function verifyRefreshToken(refreshToken, clientId, clientSecret, environment) {
|
|
257
|
-
// First, refresh to get an access token
|
|
258
|
-
const { accessToken } = await refreshAccessToken(refreshToken, clientId, clientSecret, environment);
|
|
259
|
-
// Then fetch user info to verify everything works
|
|
260
|
-
const userInfo = await fetchEbayUserInfo(accessToken, environment);
|
|
261
|
-
return { accessToken, userInfo };
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Fetch eBay user info using the Identity API
|
|
265
|
-
* Uses apiz.ebay.com subdomain as per eBay API requirements
|
|
266
|
-
*/
|
|
267
124
|
async function fetchEbayUserInfo(accessToken, environment) {
|
|
268
|
-
const
|
|
269
|
-
const response = await axios.get(`${
|
|
270
|
-
headers: {
|
|
271
|
-
Authorization: `Bearer ${accessToken}`,
|
|
272
|
-
'Content-Type': 'application/json',
|
|
273
|
-
},
|
|
125
|
+
const identityBase = environment === 'production' ? 'https://apiz.ebay.com' : 'https://apiz.sandbox.ebay.com';
|
|
126
|
+
const response = await axios.get(`${identityBase}/commerce/identity/v1/user/`, {
|
|
127
|
+
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
|
|
274
128
|
});
|
|
275
129
|
return response.data;
|
|
276
130
|
}
|
|
277
|
-
/**
|
|
278
|
-
* Display eBay user info in a formatted box
|
|
279
|
-
*/
|
|
280
|
-
function displayUserInfo(userInfo) {
|
|
281
|
-
const accountName = userInfo.individualAccount?.firstName && userInfo.individualAccount?.lastName
|
|
282
|
-
? `${userInfo.individualAccount.firstName} ${userInfo.individualAccount.lastName}`
|
|
283
|
-
: userInfo.businessAccount?.name || 'N/A';
|
|
284
|
-
const email = userInfo.individualAccount?.email || userInfo.businessAccount?.email || 'N/A';
|
|
285
|
-
const marketplaceMap = {
|
|
286
|
-
EBAY_US: 'eBay United States',
|
|
287
|
-
EBAY_GB: 'eBay United Kingdom',
|
|
288
|
-
EBAY_DE: 'eBay Germany',
|
|
289
|
-
EBAY_AU: 'eBay Australia',
|
|
290
|
-
EBAY_CA: 'eBay Canada',
|
|
291
|
-
EBAY_FR: 'eBay France',
|
|
292
|
-
EBAY_IT: 'eBay Italy',
|
|
293
|
-
EBAY_ES: 'eBay Spain',
|
|
294
|
-
};
|
|
295
|
-
const marketplace = marketplaceMap[userInfo.registrationMarketplaceId || ''] ||
|
|
296
|
-
userInfo.registrationMarketplaceId ||
|
|
297
|
-
'N/A';
|
|
298
|
-
showBox('eBay Account Verified', [
|
|
299
|
-
`Username: ${userInfo.username}`,
|
|
300
|
-
`Account Name: ${accountName}`,
|
|
301
|
-
`Email: ${email}`,
|
|
302
|
-
`Account Type: ${userInfo.accountType || 'N/A'}`,
|
|
303
|
-
`Marketplace: ${marketplace}`,
|
|
304
|
-
`User ID: ${userInfo.userId?.slice(0, 30)}...`,
|
|
305
|
-
]);
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Render an informational line.
|
|
309
|
-
*/
|
|
310
|
-
function showInfo(message) {
|
|
311
|
-
console.log(` ${ui.info('ℹ')} ${message}`);
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Render a spinner and return a stop callback.
|
|
315
|
-
*/
|
|
316
|
-
function showSpinner(message) {
|
|
317
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
318
|
-
let i = 0;
|
|
319
|
-
process.stdout.write(` ${ui.info(frames[0])} ${message}`);
|
|
320
|
-
const interval = setInterval(() => {
|
|
321
|
-
i = (i + 1) % frames.length;
|
|
322
|
-
process.stdout.write(`\r ${ui.info(frames[i])} ${message}`);
|
|
323
|
-
}, 80);
|
|
324
|
-
return () => {
|
|
325
|
-
clearInterval(interval);
|
|
326
|
-
process.stdout.write('\r' + ' '.repeat(message.length + 10) + '\r');
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Render a bordered info box.
|
|
331
|
-
*/
|
|
332
|
-
function showBox(title, content) {
|
|
333
|
-
const width = 60;
|
|
334
|
-
const line = '─'.repeat(width - 2);
|
|
335
|
-
console.log(`\n ${ui.dim('┌' + line + '┐')}`);
|
|
336
|
-
console.log(` ${ui.dim('│')} ${ui.bold(title.padEnd(width - 3))}${ui.dim('│')}`);
|
|
337
|
-
console.log(` ${ui.dim('├' + line + '┤')}`);
|
|
338
|
-
for (const item of content) {
|
|
339
|
-
const displayItem = item.length > width - 4 ? item.slice(0, width - 7) + '...' : item;
|
|
340
|
-
console.log(` ${ui.dim('│')} ${displayItem.padEnd(width - 3)}${ui.dim('│')}`);
|
|
341
|
-
}
|
|
342
|
-
console.log(` ${ui.dim('└' + line + '┘')}\n`);
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Compute MCP client config paths by OS.
|
|
346
|
-
*/
|
|
347
131
|
function getConfigPaths() {
|
|
348
132
|
const home = homedir();
|
|
349
133
|
const os = platform();
|
|
@@ -378,40 +162,23 @@ function getConfigPaths() {
|
|
|
378
162
|
path: join(home, '.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json'),
|
|
379
163
|
};
|
|
380
164
|
}
|
|
381
|
-
paths.continue = {
|
|
382
|
-
display: 'Continue.dev',
|
|
383
|
-
path: join(home, '.continue/config.json'),
|
|
384
|
-
};
|
|
165
|
+
paths.continue = { display: 'Continue.dev', path: join(home, '.continue/config.json') };
|
|
385
166
|
return paths;
|
|
386
167
|
}
|
|
387
|
-
/**
|
|
388
|
-
* Detect installed MCP-compatible clients.
|
|
389
|
-
*/
|
|
390
168
|
function detectLLMClients() {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
displayName: info.display,
|
|
399
|
-
configPath: info.path,
|
|
400
|
-
detected: parentExists,
|
|
401
|
-
configExists,
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
return clients;
|
|
169
|
+
return Object.entries(getConfigPaths()).map(([name, info]) => ({
|
|
170
|
+
name,
|
|
171
|
+
displayName: info.display,
|
|
172
|
+
configPath: info.path,
|
|
173
|
+
detected: existsSync(dirname(info.path)),
|
|
174
|
+
configExists: existsSync(info.path),
|
|
175
|
+
}));
|
|
405
176
|
}
|
|
406
|
-
/**
|
|
407
|
-
* Write MCP server config for a detected client.
|
|
408
|
-
*/
|
|
409
177
|
function configureLLMClient(client, projectRoot) {
|
|
410
178
|
try {
|
|
411
179
|
const configDir = dirname(client.configPath);
|
|
412
|
-
if (!existsSync(configDir))
|
|
180
|
+
if (!existsSync(configDir))
|
|
413
181
|
mkdirSync(configDir, { recursive: true });
|
|
414
|
-
}
|
|
415
182
|
let existingConfig = {};
|
|
416
183
|
if (existsSync(client.configPath)) {
|
|
417
184
|
try {
|
|
@@ -421,24 +188,18 @@ function configureLLMClient(client, projectRoot) {
|
|
|
421
188
|
existingConfig = {};
|
|
422
189
|
}
|
|
423
190
|
}
|
|
424
|
-
const serverConfig = {
|
|
425
|
-
command: 'node',
|
|
426
|
-
args: [join(projectRoot, 'build/index.js')],
|
|
427
|
-
};
|
|
191
|
+
const serverConfig = { command: 'node', args: [join(projectRoot, 'build/index.js')] };
|
|
428
192
|
if (client.name === 'continue') {
|
|
429
193
|
if (!existingConfig.experimental)
|
|
430
194
|
existingConfig.experimental = {};
|
|
431
|
-
if (!existingConfig.experimental.modelContextProtocolServers)
|
|
195
|
+
if (!existingConfig.experimental.modelContextProtocolServers)
|
|
432
196
|
existingConfig.experimental.modelContextProtocolServers = [];
|
|
433
|
-
}
|
|
434
197
|
const servers = existingConfig.experimental.modelContextProtocolServers;
|
|
435
|
-
const
|
|
436
|
-
if (
|
|
437
|
-
servers[
|
|
438
|
-
|
|
439
|
-
else {
|
|
198
|
+
const idx = servers.findIndex((s) => s?.args?.[0]?.includes('ebay-mcp'));
|
|
199
|
+
if (idx >= 0)
|
|
200
|
+
servers[idx] = serverConfig;
|
|
201
|
+
else
|
|
440
202
|
servers.push(serverConfig);
|
|
441
|
-
}
|
|
442
203
|
}
|
|
443
204
|
else {
|
|
444
205
|
if (!existingConfig.mcpServers)
|
|
@@ -452,151 +213,93 @@ function configureLLMClient(client, projectRoot) {
|
|
|
452
213
|
return false;
|
|
453
214
|
}
|
|
454
215
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Get Claude Desktop config path for the current platform
|
|
457
|
-
*/
|
|
458
216
|
function getClaudeDesktopConfigPath() {
|
|
459
217
|
const home = homedir();
|
|
460
218
|
const os = platform();
|
|
461
|
-
if (os === 'darwin')
|
|
219
|
+
if (os === 'darwin')
|
|
462
220
|
return join(home, 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
463
|
-
|
|
464
|
-
else if (os === 'win32') {
|
|
221
|
+
if (os === 'win32')
|
|
465
222
|
return join(home, 'AppData/Roaming/Claude/claude_desktop_config.json');
|
|
466
|
-
|
|
467
|
-
else {
|
|
468
|
-
return join(home, '.config/Claude/claude_desktop_config.json');
|
|
469
|
-
}
|
|
223
|
+
return join(home, '.config/Claude/claude_desktop_config.json');
|
|
470
224
|
}
|
|
471
|
-
/**
|
|
472
|
-
* Check if Claude Desktop is installed
|
|
473
|
-
*/
|
|
474
225
|
function isClaudeDesktopInstalled() {
|
|
475
|
-
|
|
476
|
-
const configDir = dirname(configPath);
|
|
477
|
-
return existsSync(configDir);
|
|
226
|
+
return existsSync(dirname(getClaudeDesktopConfigPath()));
|
|
478
227
|
}
|
|
479
|
-
/**
|
|
480
|
-
* Update Claude Desktop config with eBay MCP server credentials
|
|
481
|
-
* This ensures Claude Desktop has access to the verified tokens
|
|
482
|
-
* IMPORTANT: This preserves all existing mcpServers and other config
|
|
483
|
-
*/
|
|
484
228
|
function updateClaudeDesktopConfig(envConfig, environment) {
|
|
485
229
|
const configPath = getClaudeDesktopConfigPath();
|
|
486
|
-
|
|
487
|
-
// Check if Claude Desktop is installed
|
|
488
|
-
if (!existsSync(configDir)) {
|
|
230
|
+
if (!existsSync(dirname(configPath)))
|
|
489
231
|
return { success: false, configPath, error: 'Claude Desktop not installed' };
|
|
490
|
-
}
|
|
491
232
|
try {
|
|
492
|
-
|
|
493
|
-
let existingConfig = {};
|
|
233
|
+
let existing = {};
|
|
494
234
|
if (existsSync(configPath)) {
|
|
495
235
|
try {
|
|
496
|
-
|
|
497
|
-
existingConfig = JSON.parse(fileContent);
|
|
236
|
+
existing = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
498
237
|
}
|
|
499
|
-
catch (
|
|
500
|
-
// If JSON is invalid, start fresh but warn user
|
|
238
|
+
catch (e) {
|
|
501
239
|
return {
|
|
502
240
|
success: false,
|
|
503
241
|
configPath,
|
|
504
|
-
error: `Invalid JSON in config
|
|
242
|
+
error: `Invalid JSON in config: ${e instanceof Error ? e.message : 'parse error'}`,
|
|
505
243
|
details: 'Please fix the JSON syntax in your Claude config file',
|
|
506
244
|
};
|
|
507
245
|
}
|
|
508
246
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Build env object with all credentials
|
|
515
|
-
const envVars = {
|
|
516
|
-
EBAY_ENVIRONMENT: environment,
|
|
517
|
-
};
|
|
518
|
-
if (envConfig.EBAY_CLIENT_ID) {
|
|
247
|
+
if (!existing.mcpServers || typeof existing.mcpServers !== 'object')
|
|
248
|
+
existing.mcpServers = {};
|
|
249
|
+
const mcpServers = existing.mcpServers;
|
|
250
|
+
const envVars = { EBAY_ENVIRONMENT: environment };
|
|
251
|
+
if (envConfig.EBAY_CLIENT_ID)
|
|
519
252
|
envVars.EBAY_CLIENT_ID = envConfig.EBAY_CLIENT_ID;
|
|
520
|
-
|
|
521
|
-
if (envConfig.EBAY_CLIENT_SECRET) {
|
|
253
|
+
if (envConfig.EBAY_CLIENT_SECRET)
|
|
522
254
|
envVars.EBAY_CLIENT_SECRET = envConfig.EBAY_CLIENT_SECRET;
|
|
523
|
-
|
|
524
|
-
if (envConfig.EBAY_REDIRECT_URI) {
|
|
255
|
+
if (envConfig.EBAY_REDIRECT_URI)
|
|
525
256
|
envVars.EBAY_REDIRECT_URI = envConfig.EBAY_REDIRECT_URI;
|
|
526
|
-
|
|
527
|
-
if (envConfig.EBAY_MARKETPLACE_ID) {
|
|
257
|
+
if (envConfig.EBAY_MARKETPLACE_ID)
|
|
528
258
|
envVars.EBAY_MARKETPLACE_ID = envConfig.EBAY_MARKETPLACE_ID;
|
|
529
|
-
|
|
530
|
-
if (envConfig.EBAY_CONTENT_LANGUAGE) {
|
|
259
|
+
if (envConfig.EBAY_CONTENT_LANGUAGE)
|
|
531
260
|
envVars.EBAY_CONTENT_LANGUAGE = envConfig.EBAY_CONTENT_LANGUAGE;
|
|
532
|
-
|
|
533
|
-
if (envConfig.EBAY_USER_REFRESH_TOKEN) {
|
|
261
|
+
if (envConfig.EBAY_USER_REFRESH_TOKEN)
|
|
534
262
|
envVars.EBAY_USER_REFRESH_TOKEN = envConfig.EBAY_USER_REFRESH_TOKEN;
|
|
535
|
-
|
|
536
|
-
// Only include access tokens if they exist and are not empty
|
|
537
|
-
if (envConfig.EBAY_USER_ACCESS_TOKEN && envConfig.EBAY_USER_ACCESS_TOKEN.startsWith('v^')) {
|
|
263
|
+
if (envConfig.EBAY_USER_ACCESS_TOKEN?.startsWith('v^'))
|
|
538
264
|
envVars.EBAY_USER_ACCESS_TOKEN = envConfig.EBAY_USER_ACCESS_TOKEN;
|
|
539
|
-
|
|
540
|
-
if (envConfig.EBAY_APP_ACCESS_TOKEN && envConfig.EBAY_APP_ACCESS_TOKEN.startsWith('v^')) {
|
|
265
|
+
if (envConfig.EBAY_APP_ACCESS_TOKEN?.startsWith('v^'))
|
|
541
266
|
envVars.EBAY_APP_ACCESS_TOKEN = envConfig.EBAY_APP_ACCESS_TOKEN;
|
|
542
|
-
}
|
|
543
|
-
// Add or update only the 'ebay' server - preserve all other servers
|
|
544
|
-
// Use npx with --yes flag and suppress npm/node output to keep stdout clean for MCP
|
|
545
267
|
mcpServers['ebay'] = {
|
|
546
268
|
command: 'npx',
|
|
547
269
|
args: ['--yes', '--quiet', 'ebay-mcp'],
|
|
548
|
-
env: {
|
|
549
|
-
...envVars,
|
|
550
|
-
NODE_NO_WARNINGS: '1',
|
|
551
|
-
NPM_CONFIG_UPDATE_NOTIFIER: 'false',
|
|
552
|
-
},
|
|
270
|
+
env: { ...envVars, NODE_NO_WARNINGS: '1', NPM_CONFIG_UPDATE_NOTIFIER: 'false' },
|
|
553
271
|
};
|
|
554
|
-
|
|
555
|
-
writeFileSync(configPath, JSON.stringify(existingConfig, null, 2));
|
|
556
|
-
// Count existing servers for confirmation message
|
|
557
|
-
const serverCount = Object.keys(mcpServers).length;
|
|
272
|
+
writeFileSync(configPath, JSON.stringify(existing, null, 2));
|
|
558
273
|
const otherServers = Object.keys(mcpServers).filter((k) => k !== 'ebay');
|
|
559
274
|
return {
|
|
560
275
|
success: true,
|
|
561
276
|
configPath,
|
|
562
277
|
details: otherServers.length > 0
|
|
563
278
|
? `Preserved ${otherServers.length} existing server(s): ${otherServers.join(', ')}`
|
|
564
|
-
: `Added ebay server (${
|
|
279
|
+
: `Added ebay server (${Object.keys(mcpServers).length} total)`,
|
|
565
280
|
};
|
|
566
281
|
}
|
|
567
282
|
catch (error) {
|
|
568
|
-
return {
|
|
569
|
-
success: false,
|
|
570
|
-
configPath,
|
|
571
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
572
|
-
};
|
|
283
|
+
return { success: false, configPath, error: error instanceof Error ? error.message : 'Unknown' };
|
|
573
284
|
}
|
|
574
285
|
}
|
|
575
|
-
/**
|
|
576
|
-
* Load existing environment variables from the .env file.
|
|
577
|
-
*/
|
|
578
286
|
function loadExistingConfig() {
|
|
579
287
|
const envPath = join(PROJECT_ROOT, '.env');
|
|
580
288
|
const envConfig = {};
|
|
581
|
-
if (existsSync(envPath))
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
}
|
|
289
|
+
if (!existsSync(envPath))
|
|
290
|
+
return envConfig;
|
|
291
|
+
for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
|
|
292
|
+
if (line.trim() && !line.startsWith('#')) {
|
|
293
|
+
const [key, ...valueParts] = line.split('=');
|
|
294
|
+
const value = valueParts.join('=').trim();
|
|
295
|
+
if (key && value && !value.includes('_here'))
|
|
296
|
+
envConfig[key.trim()] = value;
|
|
591
297
|
}
|
|
592
298
|
}
|
|
593
299
|
return envConfig;
|
|
594
300
|
}
|
|
595
|
-
/**
|
|
596
|
-
* Format a date for display in the .env header.
|
|
597
|
-
*/
|
|
598
301
|
function formatDate(date) {
|
|
599
|
-
|
|
302
|
+
return date.toLocaleString('en-US', {
|
|
600
303
|
weekday: 'long',
|
|
601
304
|
year: 'numeric',
|
|
602
305
|
month: 'long',
|
|
@@ -605,23 +308,18 @@ function formatDate(date) {
|
|
|
605
308
|
minute: '2-digit',
|
|
606
309
|
second: '2-digit',
|
|
607
310
|
timeZoneName: 'short',
|
|
608
|
-
};
|
|
609
|
-
return date.toLocaleString('en-US', options);
|
|
311
|
+
});
|
|
610
312
|
}
|
|
611
|
-
/**
|
|
612
|
-
* Persist setup configuration to the project .env file.
|
|
613
|
-
*/
|
|
614
313
|
function saveConfig(envConfig, environment) {
|
|
615
314
|
const envPath = join(PROJECT_ROOT, '.env');
|
|
616
|
-
const now = new Date();
|
|
617
315
|
const marketplaceLine = envConfig.EBAY_MARKETPLACE_ID
|
|
618
316
|
? `EBAY_MARKETPLACE_ID=${envConfig.EBAY_MARKETPLACE_ID}`
|
|
619
317
|
: '# EBAY_MARKETPLACE_ID=EBAY_US';
|
|
620
318
|
const contentLanguageLine = envConfig.EBAY_CONTENT_LANGUAGE
|
|
621
319
|
? `EBAY_CONTENT_LANGUAGE=${envConfig.EBAY_CONTENT_LANGUAGE}`
|
|
622
320
|
: '# EBAY_CONTENT_LANGUAGE=en-US';
|
|
623
|
-
|
|
624
|
-
# Last Updated: ${formatDate(
|
|
321
|
+
writeFileSync(envPath, `# eBay MCP Server Configuration
|
|
322
|
+
# Last Updated: ${formatDate(new Date())}
|
|
625
323
|
# Environment: ${environment}
|
|
626
324
|
|
|
627
325
|
EBAY_CLIENT_ID=${envConfig.EBAY_CLIENT_ID || ''}
|
|
@@ -634,12 +332,68 @@ ${contentLanguageLine}
|
|
|
634
332
|
EBAY_USER_REFRESH_TOKEN=${envConfig.EBAY_USER_REFRESH_TOKEN || ''}
|
|
635
333
|
EBAY_USER_ACCESS_TOKEN=${envConfig.EBAY_USER_ACCESS_TOKEN || ''}
|
|
636
334
|
EBAY_APP_ACCESS_TOKEN=${envConfig.EBAY_APP_ACCESS_TOKEN || ''}
|
|
637
|
-
|
|
638
|
-
|
|
335
|
+
`, 'utf-8');
|
|
336
|
+
}
|
|
337
|
+
function showInfo(message) {
|
|
338
|
+
console.log(` ${ui.info('ℹ')} ${message}`);
|
|
339
|
+
}
|
|
340
|
+
function showWarning(message) {
|
|
341
|
+
console.log(` ${ui.warning('⚠')} ${message}`);
|
|
342
|
+
}
|
|
343
|
+
function showSuccess(message) {
|
|
344
|
+
console.log(` ${ui.success('✓')} ${message}`);
|
|
345
|
+
}
|
|
346
|
+
function showError(message) {
|
|
347
|
+
console.log(` ${ui.error('✗')} ${message}`);
|
|
348
|
+
}
|
|
349
|
+
function showBox(title, content) {
|
|
350
|
+
const width = 54;
|
|
351
|
+
const line = '─'.repeat(width);
|
|
352
|
+
console.log(` ${ui.dim('┌─')} ${ui.bold(title)} ${ui.dim('─'.repeat(width - title.length - 2))}┐`);
|
|
353
|
+
for (const item of content) {
|
|
354
|
+
const displayItem = item.length > width - 2 ? item.slice(0, width - 5) + '...' : item;
|
|
355
|
+
console.log(` ${ui.dim('│')} ${displayItem.padEnd(width)}${ui.dim('│')}`);
|
|
356
|
+
}
|
|
357
|
+
console.log(` ${ui.dim('└' + line + '┘')}\n`);
|
|
358
|
+
}
|
|
359
|
+
function showSpinner(message) {
|
|
360
|
+
console.log(` ⠋ ${message}...`);
|
|
361
|
+
return () => { };
|
|
639
362
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
363
|
+
function displayUserInfo(userInfo) {
|
|
364
|
+
const name = userInfo.individualAccount?.firstName && userInfo.individualAccount?.lastName
|
|
365
|
+
? `${userInfo.individualAccount.firstName} ${userInfo.individualAccount.lastName}`
|
|
366
|
+
: userInfo.businessAccount?.name || 'N/A';
|
|
367
|
+
const email = userInfo.individualAccount?.email || userInfo.businessAccount?.email || 'N/A';
|
|
368
|
+
const marketplaceMap = {
|
|
369
|
+
EBAY_US: 'eBay United States', EBAY_GB: 'eBay United Kingdom',
|
|
370
|
+
EBAY_DE: 'eBay Germany', EBAY_AU: 'eBay Australia',
|
|
371
|
+
EBAY_CA: 'eBay Canada', EBAY_FR: 'eBay France',
|
|
372
|
+
EBAY_IT: 'eBay Italy', EBAY_ES: 'eBay Spain',
|
|
373
|
+
};
|
|
374
|
+
const marketplace = marketplaceMap[userInfo.registrationMarketplaceId || ''] ||
|
|
375
|
+
userInfo.registrationMarketplaceId ||
|
|
376
|
+
'N/A';
|
|
377
|
+
console.log(` ${ui.dim('┌─ eBay Account Verified ─────────────────────────')}`);
|
|
378
|
+
console.log(` ${ui.dim('│')} Username: ${userInfo.username}`);
|
|
379
|
+
console.log(` ${ui.dim('│')} Name: ${name}`);
|
|
380
|
+
console.log(` ${ui.dim('│')} Email: ${email}`);
|
|
381
|
+
console.log(` ${ui.dim('│')} Type: ${userInfo.accountType || 'N/A'}`);
|
|
382
|
+
console.log(` ${ui.dim('│')} Marketplace: ${marketplace}`);
|
|
383
|
+
console.log(` ${ui.dim('└─────────────────────────────────────────────────')}`);
|
|
384
|
+
console.log('');
|
|
385
|
+
}
|
|
386
|
+
// ─── Grimoire wizard ──────────────────────────────────────────────────────────
|
|
387
|
+
export async function runSetup() {
|
|
388
|
+
const existingConfig = loadExistingConfig();
|
|
389
|
+
const detectedClients = detectLLMClients();
|
|
390
|
+
const availableClients = detectedClients.filter((c) => c.detected);
|
|
391
|
+
const tokens = {};
|
|
392
|
+
const finalStepId = availableClients.length > 0 ? 'mcp-clients' : 'no-mcp-clients';
|
|
393
|
+
const args = parseArgs();
|
|
394
|
+
console.log(LOGO);
|
|
395
|
+
console.log(ui.bold.white(' MCP Server Setup Wizard by Yosef Hayim Sabag'));
|
|
396
|
+
console.log(ui.dim(' Powered by ') + chalk.hex('#0064D2').bold('grimoire-wizard') + '\n');
|
|
643
397
|
console.log(ui.dim(' Welcome to the eBay MCP Server setup wizard!\n'));
|
|
644
398
|
console.log(' This wizard will help you:\n');
|
|
645
399
|
console.log(` ${ui.success('1.')} Choose environment (sandbox/production)`);
|
|
@@ -648,759 +402,425 @@ async function stepWelcome(state) {
|
|
|
648
402
|
console.log(` ${ui.success('4.')} Set up OAuth authentication`);
|
|
649
403
|
console.log(` ${ui.success('5.')} Configure your MCP client (Claude, Cline, etc.)`);
|
|
650
404
|
console.log(` ${ui.success('6.')} Validate your setup\n`);
|
|
651
|
-
if (
|
|
405
|
+
if (Object.keys(existingConfig).length > 0) {
|
|
652
406
|
showInfo('Existing configuration detected. You can update or keep current values.');
|
|
407
|
+
console.log('');
|
|
653
408
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
let contentLanguage = languageResponse.contentLanguage;
|
|
796
|
-
if (contentLanguage === '__custom__') {
|
|
797
|
-
const customLanguage = await prompts({
|
|
798
|
-
type: 'text',
|
|
799
|
-
name: 'customContentLanguage',
|
|
800
|
-
message: 'Enter Content-Language (e.g., en-US, de-DE):',
|
|
801
|
-
initial: currentLanguage || 'en-US',
|
|
802
|
-
validate: (value) => value.trim().length === 0 ? 'Content-Language cannot be empty' : true,
|
|
803
|
-
});
|
|
804
|
-
if (customLanguage.customContentLanguage === undefined) {
|
|
805
|
-
return 'cancel';
|
|
806
|
-
}
|
|
807
|
-
contentLanguage = customLanguage.customContentLanguage.trim();
|
|
808
|
-
}
|
|
809
|
-
if (contentLanguage) {
|
|
810
|
-
state.config.EBAY_CONTENT_LANGUAGE = contentLanguage;
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
delete state.config.EBAY_CONTENT_LANGUAGE;
|
|
814
|
-
}
|
|
815
|
-
return 'continue';
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Collect eBay app credentials from the user.
|
|
819
|
-
*/
|
|
820
|
-
async function stepCredentials(state) {
|
|
821
|
-
clearScreen();
|
|
822
|
-
showLogo();
|
|
823
|
-
showProgress(3, 'eBay Credentials');
|
|
824
|
-
console.log(' Enter your eBay Developer credentials:\n');
|
|
825
|
-
showTip('Get credentials at: https://developer.ebay.com/my/keys');
|
|
826
|
-
showKeyboardHints(['Tab: Next field', 'Enter: Submit', 'Ctrl+C: Cancel']);
|
|
827
|
-
// First ask if they want to go back
|
|
828
|
-
const navChoice = await prompts({
|
|
829
|
-
type: 'select',
|
|
830
|
-
name: 'action',
|
|
831
|
-
message: 'What would you like to do?',
|
|
832
|
-
choices: [
|
|
833
|
-
{ title: '📝 Enter/update credentials', value: 'enter' },
|
|
834
|
-
{ title: ui.dim('← Go back'), value: 'back' },
|
|
409
|
+
const wizardConfig = defineWizard({
|
|
410
|
+
meta: {
|
|
411
|
+
name: 'eBay MCP',
|
|
412
|
+
description: 'Server Setup Wizard — powered by grimoire',
|
|
413
|
+
},
|
|
414
|
+
theme: {
|
|
415
|
+
tokens: {
|
|
416
|
+
primary: '#0064D2',
|
|
417
|
+
success: '#86B817',
|
|
418
|
+
warning: '#F5AF02',
|
|
419
|
+
error: '#E53238',
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
steps: [
|
|
423
|
+
{
|
|
424
|
+
id: 'environment',
|
|
425
|
+
type: 'select',
|
|
426
|
+
message: 'Select eBay environment:',
|
|
427
|
+
description: '🧪 Sandbox for testing • 🚀 Production for live trading',
|
|
428
|
+
options: [
|
|
429
|
+
{ value: 'sandbox', label: '🧪 Sandbox — development & testing (recommended)' },
|
|
430
|
+
{ value: 'production', label: '🚀 Production — live trading' },
|
|
431
|
+
],
|
|
432
|
+
default: existingConfig.EBAY_ENVIRONMENT || 'sandbox',
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
id: 'marketplace',
|
|
436
|
+
type: 'select',
|
|
437
|
+
message: 'Default eBay marketplace:',
|
|
438
|
+
description: 'Used as a default header for API requests — optional',
|
|
439
|
+
options: [
|
|
440
|
+
{ value: '', label: 'Skip — leave unset' },
|
|
441
|
+
...MARKETPLACE_OPTIONS.map((o) => ({ value: o.value, label: o.label })),
|
|
442
|
+
{ value: '__custom__', label: 'Other — enter manually' },
|
|
443
|
+
],
|
|
444
|
+
default: existingConfig.EBAY_MARKETPLACE_ID || 'EBAY_US',
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
id: 'marketplace-custom',
|
|
448
|
+
type: 'text',
|
|
449
|
+
message: 'Enter marketplace ID:',
|
|
450
|
+
description: 'e.g. EBAY_US, EBAY_DE',
|
|
451
|
+
when: { field: 'marketplace', equals: '__custom__' },
|
|
452
|
+
default: existingConfig.EBAY_MARKETPLACE_ID || 'EBAY_US',
|
|
453
|
+
validate: [{ rule: 'required' }],
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
id: 'content-language',
|
|
457
|
+
type: 'select',
|
|
458
|
+
message: 'Default Content-Language:',
|
|
459
|
+
description: 'Used as a default header for API requests — optional',
|
|
460
|
+
options: [
|
|
461
|
+
{ value: '', label: 'Skip — leave unset' },
|
|
462
|
+
...CONTENT_LANGUAGE_OPTIONS.map((o) => ({ value: o.value, label: o.label })),
|
|
463
|
+
{ value: '__custom__', label: 'Other — enter manually' },
|
|
464
|
+
],
|
|
465
|
+
default: existingConfig.EBAY_CONTENT_LANGUAGE || 'en-US',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: 'content-language-custom',
|
|
469
|
+
type: 'text',
|
|
470
|
+
message: 'Enter Content-Language:',
|
|
471
|
+
description: 'e.g. en-US, de-DE',
|
|
472
|
+
when: { field: 'content-language', equals: '__custom__' },
|
|
473
|
+
default: existingConfig.EBAY_CONTENT_LANGUAGE || 'en-US',
|
|
474
|
+
validate: [{ rule: 'required' }],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
id: 'client-id',
|
|
478
|
+
type: 'text',
|
|
479
|
+
message: 'Client ID (App ID):',
|
|
480
|
+
description: 'From https://developer.ebay.com/my/keys',
|
|
481
|
+
default: existingConfig.EBAY_CLIENT_ID || '',
|
|
482
|
+
validate: [{ rule: 'required' }],
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: 'client-secret',
|
|
486
|
+
type: 'password',
|
|
487
|
+
message: 'Client Secret (Cert ID):',
|
|
488
|
+
description: existingConfig.EBAY_CLIENT_SECRET
|
|
489
|
+
? 'Press Enter to keep existing secret'
|
|
490
|
+
: 'From https://developer.ebay.com/my/keys',
|
|
491
|
+
required: existingConfig.EBAY_CLIENT_SECRET ? false : true,
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: 'redirect-uri',
|
|
495
|
+
type: 'text',
|
|
496
|
+
message: 'Redirect URI (RuName):',
|
|
497
|
+
description: 'The RuName configured in your eBay developer account',
|
|
498
|
+
default: existingConfig.EBAY_REDIRECT_URI || '',
|
|
499
|
+
validate: [{ rule: 'required' }],
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
id: 'oauth-method',
|
|
503
|
+
type: 'select',
|
|
504
|
+
message: 'Set up OAuth for higher API rate limits:',
|
|
505
|
+
description: 'App credentials: 1,000 req/day • User OAuth: 10,000–50,000 req/day',
|
|
506
|
+
options: [
|
|
507
|
+
{ value: 'existing', label: '📝 I have a refresh token' },
|
|
508
|
+
{ value: 'manual', label: '🔗 Generate OAuth URL (opens browser)' },
|
|
509
|
+
{ value: 'code', label: '🔑 Paste authorization code (already have code)' },
|
|
510
|
+
{ value: 'skip', label: '⏭️ Skip for now (1,000 req/day limit)' },
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: 'oauth-token',
|
|
515
|
+
type: 'text',
|
|
516
|
+
message: 'Paste your refresh token:',
|
|
517
|
+
description: 'Should start with v^1.1#',
|
|
518
|
+
default: '',
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
id: 'oauth-code',
|
|
522
|
+
type: 'text',
|
|
523
|
+
message: 'Paste the callback URL or authorization code:',
|
|
524
|
+
description: 'Copy the full redirect URL or just the code= parameter',
|
|
525
|
+
default: '',
|
|
526
|
+
},
|
|
527
|
+
...(availableClients.length > 0
|
|
528
|
+
? [
|
|
529
|
+
{
|
|
530
|
+
id: 'mcp-clients',
|
|
531
|
+
type: 'multiselect',
|
|
532
|
+
message: 'Configure MCP clients:',
|
|
533
|
+
description: 'Select AI assistants to connect to the eBay MCP server',
|
|
534
|
+
required: false,
|
|
535
|
+
options: availableClients.map((c) => ({
|
|
536
|
+
value: c.name,
|
|
537
|
+
label: `${c.displayName}${c.configExists ? ' (update)' : ' (new)'}`,
|
|
538
|
+
})),
|
|
539
|
+
},
|
|
540
|
+
]
|
|
541
|
+
: [
|
|
542
|
+
{
|
|
543
|
+
id: 'no-mcp-clients',
|
|
544
|
+
type: 'note',
|
|
545
|
+
message: 'No MCP clients detected',
|
|
546
|
+
description: 'Install Claude Desktop, Cline (VSCode), or Continue.dev, then run setup again.',
|
|
547
|
+
},
|
|
548
|
+
]),
|
|
835
549
|
],
|
|
836
|
-
initial: 0,
|
|
837
550
|
});
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
validate: (v) => v.trim().length > 0 || 'Required',
|
|
551
|
+
const answers = await runWizard(wizardConfig, {
|
|
552
|
+
renderer: new ClackRenderer(),
|
|
553
|
+
quiet: true,
|
|
554
|
+
optionsProvider: async (stepId) => {
|
|
555
|
+
if (stepId === 'oauth-method') {
|
|
556
|
+
const hasToken = existingConfig.EBAY_USER_REFRESH_TOKEN?.startsWith('v^1.1#');
|
|
557
|
+
if (hasToken) {
|
|
558
|
+
return [
|
|
559
|
+
{ value: 'keep', label: '✓ Keep and verify existing token' },
|
|
560
|
+
{ value: 'existing', label: '📝 Replace with a different refresh token' },
|
|
561
|
+
{ value: 'manual', label: '🔗 Generate OAuth URL (opens browser)' },
|
|
562
|
+
{ value: 'code', label: '🔑 Paste authorization code' },
|
|
563
|
+
{ value: 'skip', label: '⏭️ Skip for now' },
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return undefined;
|
|
856
568
|
},
|
|
857
|
-
{
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
569
|
+
asyncValidate: async (stepId, value) => {
|
|
570
|
+
if (stepId === 'oauth-token') {
|
|
571
|
+
const clean = String(value).trim().replace(/^["']|["']$/g, '');
|
|
572
|
+
if (!clean)
|
|
573
|
+
return 'Token is required';
|
|
574
|
+
if (!clean.startsWith('v^1.1#'))
|
|
575
|
+
return 'Token should start with v^1.1#';
|
|
576
|
+
}
|
|
577
|
+
if (stepId === 'oauth-code') {
|
|
578
|
+
const code = parseAuthorizationCode(String(value));
|
|
579
|
+
if (!code)
|
|
580
|
+
return 'Could not find authorization code. Paste the full redirect URL or the code parameter.';
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
863
583
|
},
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const keepToken = await prompts({
|
|
890
|
-
type: 'select',
|
|
891
|
-
name: 'action',
|
|
892
|
-
message: 'What would you like to do?',
|
|
893
|
-
choices: [
|
|
894
|
-
{ title: '✓ Keep and verify existing token', value: 'keep' },
|
|
895
|
-
{ title: '🔄 Set up new OAuth token', value: 'new' },
|
|
896
|
-
{ title: ui.dim('← Go back'), value: 'back' },
|
|
897
|
-
],
|
|
898
|
-
initial: 0,
|
|
899
|
-
});
|
|
900
|
-
if (!keepToken.action)
|
|
901
|
-
return 'cancel';
|
|
902
|
-
if (keepToken.action === 'back')
|
|
903
|
-
return 'back';
|
|
904
|
-
if (keepToken.action === 'keep') {
|
|
905
|
-
// Verify the existing token works by fetching user info
|
|
906
|
-
console.log('\n ' + ui.info('Verifying existing refresh token...'));
|
|
907
|
-
try {
|
|
908
|
-
const { accessToken, userInfo } = await verifyRefreshToken(state.config.EBAY_USER_REFRESH_TOKEN, state.config.EBAY_CLIENT_ID, state.config.EBAY_CLIENT_SECRET, state.environment);
|
|
909
|
-
showSuccess('Refresh token verified successfully!');
|
|
910
|
-
state.config.EBAY_USER_ACCESS_TOKEN = accessToken;
|
|
911
|
-
displayUserInfo(userInfo);
|
|
912
|
-
// Update Claude Desktop config if installed
|
|
913
|
-
if (isClaudeDesktopInstalled()) {
|
|
914
|
-
console.log(' ' + ui.info('Updating Claude Desktop configuration...'));
|
|
915
|
-
const claudeResult = updateClaudeDesktopConfig(state.config, state.environment);
|
|
916
|
-
if (claudeResult.success) {
|
|
917
|
-
showSuccess('Claude Desktop config updated!');
|
|
918
|
-
if (claudeResult.details) {
|
|
919
|
-
showInfo(claudeResult.details);
|
|
584
|
+
onAfterStep: async (stepId, value, context) => {
|
|
585
|
+
const a = context.answers;
|
|
586
|
+
const environment = a['environment'] || 'sandbox';
|
|
587
|
+
const clientId = a['client-id'] || existingConfig.EBAY_CLIENT_ID || '';
|
|
588
|
+
const clientSecret = a['client-secret'] || existingConfig.EBAY_CLIENT_SECRET || '';
|
|
589
|
+
const redirectUri = a['redirect-uri'] || existingConfig.EBAY_REDIRECT_URI || '';
|
|
590
|
+
if (stepId === 'environment' && args.quick) {
|
|
591
|
+
showInfo('Quick setup enabled — skipping optional marketplace configuration.');
|
|
592
|
+
context.setNextStep('client-id');
|
|
593
|
+
}
|
|
594
|
+
// ── oauth-method: dispatch to correct sub-flow ─────────────────────────
|
|
595
|
+
if (stepId === 'oauth-method') {
|
|
596
|
+
const method = value;
|
|
597
|
+
if (method === 'keep') {
|
|
598
|
+
const stopSpinner = showSpinner('Verifying existing refresh token...');
|
|
599
|
+
try {
|
|
600
|
+
const { accessToken, userInfo } = await verifyRefreshToken(existingConfig.EBAY_USER_REFRESH_TOKEN, clientId, clientSecret, environment);
|
|
601
|
+
stopSpinner();
|
|
602
|
+
showSuccess('Refresh token verified!');
|
|
603
|
+
tokens.refreshToken = existingConfig.EBAY_USER_REFRESH_TOKEN;
|
|
604
|
+
tokens.accessToken = accessToken;
|
|
605
|
+
displayUserInfo(userInfo);
|
|
606
|
+
try {
|
|
607
|
+
tokens.appAccessToken = await getAppAccessToken(clientId, clientSecret, environment);
|
|
608
|
+
showSuccess('App access token obtained.');
|
|
920
609
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
610
|
+
catch {
|
|
611
|
+
showWarning('Could not obtain app access token (user tokens still work).');
|
|
612
|
+
}
|
|
613
|
+
if (isClaudeDesktopInstalled()) {
|
|
614
|
+
const r = updateClaudeDesktopConfig({ ...a, EBAY_USER_REFRESH_TOKEN: tokens.refreshToken ?? '' }, environment);
|
|
615
|
+
if (r.success) {
|
|
616
|
+
showSuccess('Claude Desktop config updated.');
|
|
617
|
+
if (r.details)
|
|
618
|
+
showInfo(r.details);
|
|
619
|
+
}
|
|
620
|
+
else
|
|
621
|
+
showWarning(`Could not update Claude Desktop: ${r.error}`);
|
|
927
622
|
}
|
|
928
623
|
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
stopSpinner();
|
|
626
|
+
const msg = axios.isAxiosError(error)
|
|
627
|
+
? error.response?.data?.error_description || error.response?.data?.errors?.[0]?.message || error.message
|
|
628
|
+
: error instanceof Error ? error.message : 'Unknown error';
|
|
629
|
+
showError(`Token verification failed: ${msg}`);
|
|
630
|
+
if (msg.toLowerCase().includes('access denied'))
|
|
631
|
+
showWarning('Token may be missing required OAuth scopes.');
|
|
632
|
+
else
|
|
633
|
+
showWarning('Existing token may be expired or invalid.');
|
|
634
|
+
showInfo('Continuing with existing token — re-run setup to refresh it.');
|
|
635
|
+
tokens.refreshToken = existingConfig.EBAY_USER_REFRESH_TOKEN;
|
|
636
|
+
}
|
|
637
|
+
context.setNextStep(finalStepId);
|
|
929
638
|
}
|
|
930
|
-
else {
|
|
931
|
-
|
|
639
|
+
else if (method === 'existing') {
|
|
640
|
+
context.setNextStep('oauth-token');
|
|
932
641
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
: error instanceof Error
|
|
943
|
-
? error.message
|
|
944
|
-
: 'Unknown error';
|
|
945
|
-
showError(`Token verification failed: ${errorMsg}`);
|
|
946
|
-
// Provide specific guidance based on error type
|
|
947
|
-
if (errorMsg.toLowerCase().includes('access denied')) {
|
|
948
|
-
showWarning('Your token may be missing required OAuth scopes.');
|
|
949
|
-
showInfo('Generating a new token via OAuth URL will include all necessary scopes.');
|
|
642
|
+
else if (method === 'manual') {
|
|
643
|
+
const authUrl = getOAuthAuthorizationUrl(clientId, redirectUri, environment);
|
|
644
|
+
context.showNote('OAuth Authorization URL', authUrl);
|
|
645
|
+
await context.openBrowser(authUrl);
|
|
646
|
+
showInfo('1. Sign in to your eBay account in the browser');
|
|
647
|
+
showInfo('2. Grant permissions to your app');
|
|
648
|
+
showInfo('3. Copy the redirect URL or the code parameter, then paste it below');
|
|
649
|
+
console.log('');
|
|
650
|
+
context.setNextStep('oauth-code');
|
|
950
651
|
}
|
|
951
|
-
else {
|
|
952
|
-
|
|
652
|
+
else if (method === 'code') {
|
|
653
|
+
context.setNextStep('oauth-code');
|
|
953
654
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
message: 'Would you like to set up a new OAuth token?',
|
|
958
|
-
initial: true,
|
|
959
|
-
});
|
|
960
|
-
if (!continueAnyway.continue) {
|
|
961
|
-
showInfo("Keeping existing token. You may need to re-authenticate if it doesn't work.");
|
|
962
|
-
return 'continue';
|
|
655
|
+
else if (method === 'skip') {
|
|
656
|
+
showWarning("Skipping OAuth — you'll be limited to 1,000 requests/day.");
|
|
657
|
+
context.setNextStep(finalStepId);
|
|
963
658
|
}
|
|
964
|
-
// Clear invalid tokens so we fall through to OAuth setup options
|
|
965
|
-
state.config.EBAY_USER_ACCESS_TOKEN = '';
|
|
966
|
-
state.config.EBAY_USER_REFRESH_TOKEN = '';
|
|
967
|
-
}
|
|
968
|
-
if (state.config.EBAY_USER_ACCESS_TOKEN) {
|
|
969
|
-
return 'continue';
|
|
970
659
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
const tokenChoice = await prompts({
|
|
977
|
-
type: 'select',
|
|
978
|
-
name: 'method',
|
|
979
|
-
message: 'How would you like to set up OAuth?',
|
|
980
|
-
choices: hadTokenFailure
|
|
981
|
-
? [
|
|
982
|
-
// After token failure, prioritize OAuth URL to get fresh token with proper scopes
|
|
983
|
-
{ title: '🔗 Generate OAuth URL (recommended)', value: 'manual' },
|
|
984
|
-
{ title: '🔑 Paste authorization code (already have code)', value: 'code' },
|
|
985
|
-
{ title: '📝 I have a different refresh token', value: 'existing' },
|
|
986
|
-
{ title: '⏭️ Skip for now (1k req/day limit)', value: 'skip' },
|
|
987
|
-
{ title: ui.dim('← Go back'), value: 'back' },
|
|
988
|
-
]
|
|
989
|
-
: [
|
|
990
|
-
{ title: '📝 I have a refresh token', value: 'existing' },
|
|
991
|
-
{ title: '🔗 Generate OAuth URL (opens browser)', value: 'manual' },
|
|
992
|
-
{ title: '🔑 Paste authorization code (already have code)', value: 'code' },
|
|
993
|
-
{ title: '⏭️ Skip for now (1k req/day limit)', value: 'skip' },
|
|
994
|
-
{ title: ui.dim('← Go back'), value: 'back' },
|
|
995
|
-
],
|
|
996
|
-
initial: 0,
|
|
997
|
-
});
|
|
998
|
-
if (!tokenChoice.method)
|
|
999
|
-
return 'cancel';
|
|
1000
|
-
if (tokenChoice.method === 'back')
|
|
1001
|
-
return 'back';
|
|
1002
|
-
if (tokenChoice.method === 'existing') {
|
|
1003
|
-
const tokenInput = await prompts({
|
|
1004
|
-
type: 'text',
|
|
1005
|
-
name: 'token',
|
|
1006
|
-
message: 'Paste your refresh token:',
|
|
1007
|
-
validate: (v) => {
|
|
1008
|
-
const clean = v.trim().replace(/^["']|["']$/g, '');
|
|
1009
|
-
if (!clean)
|
|
1010
|
-
return 'Token is required';
|
|
1011
|
-
if (!clean.startsWith('v^1.1#'))
|
|
1012
|
-
return 'Token should start with v^1.1#';
|
|
1013
|
-
return true;
|
|
1014
|
-
},
|
|
1015
|
-
});
|
|
1016
|
-
if (tokenInput.token) {
|
|
1017
|
-
const cleanToken = tokenInput.token.trim().replace(/^["']|["']$/g, '');
|
|
1018
|
-
state.config.EBAY_USER_REFRESH_TOKEN = cleanToken;
|
|
1019
|
-
// Verify the pasted token works
|
|
1020
|
-
console.log('\n ' + ui.info('Verifying refresh token...'));
|
|
1021
|
-
try {
|
|
1022
|
-
const { accessToken, userInfo } = await verifyRefreshToken(cleanToken, state.config.EBAY_CLIENT_ID, state.config.EBAY_CLIENT_SECRET, state.environment);
|
|
1023
|
-
showSuccess('Refresh token verified successfully!');
|
|
1024
|
-
state.config.EBAY_USER_ACCESS_TOKEN = accessToken;
|
|
1025
|
-
displayUserInfo(userInfo);
|
|
1026
|
-
// Get app access token too
|
|
1027
|
-
console.log(' ' + ui.info('Getting app access token...'));
|
|
660
|
+
// ── oauth-token: verify pasted refresh token ───────────────────────────
|
|
661
|
+
if (stepId === 'oauth-token') {
|
|
662
|
+
const rawToken = String(value).trim().replace(/^["']|["']$/g, '');
|
|
663
|
+
tokens.refreshToken = rawToken;
|
|
664
|
+
const stopSpinner = showSpinner('Verifying refresh token...');
|
|
1028
665
|
try {
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
showSuccess('
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
if (isClaudeDesktopInstalled()) {
|
|
1038
|
-
console.log(' ' + ui.info('Updating Claude Desktop configuration...'));
|
|
1039
|
-
const claudeResult = updateClaudeDesktopConfig(state.config, state.environment);
|
|
1040
|
-
if (claudeResult.success) {
|
|
1041
|
-
showSuccess('Claude Desktop config updated with credentials!');
|
|
1042
|
-
showInfo(`Config: ${claudeResult.configPath}`);
|
|
666
|
+
const { accessToken, userInfo } = await verifyRefreshToken(rawToken, clientId, clientSecret, environment);
|
|
667
|
+
stopSpinner();
|
|
668
|
+
showSuccess('Refresh token verified!');
|
|
669
|
+
tokens.accessToken = accessToken;
|
|
670
|
+
displayUserInfo(userInfo);
|
|
671
|
+
try {
|
|
672
|
+
tokens.appAccessToken = await getAppAccessToken(clientId, clientSecret, environment);
|
|
673
|
+
showSuccess('App access token obtained.');
|
|
1043
674
|
}
|
|
1044
|
-
|
|
1045
|
-
showWarning(
|
|
675
|
+
catch {
|
|
676
|
+
showWarning('Could not obtain app access token (user tokens still work).');
|
|
677
|
+
}
|
|
678
|
+
if (isClaudeDesktopInstalled()) {
|
|
679
|
+
const r = updateClaudeDesktopConfig({ ...a, EBAY_USER_REFRESH_TOKEN: rawToken }, environment);
|
|
680
|
+
if (r.success) {
|
|
681
|
+
showSuccess('Claude Desktop config updated.');
|
|
682
|
+
if (r.details)
|
|
683
|
+
showInfo(r.details);
|
|
684
|
+
}
|
|
685
|
+
else
|
|
686
|
+
showWarning(`Could not update Claude Desktop: ${r.error}`);
|
|
1046
687
|
}
|
|
1047
688
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
error.response?.data?.errors?.[0]?.message ||
|
|
1056
|
-
error.message
|
|
1057
|
-
: error instanceof Error
|
|
1058
|
-
? error.message
|
|
1059
|
-
: 'Unknown error';
|
|
1060
|
-
showError(`Token verification failed: ${errorMsg}`);
|
|
1061
|
-
showWarning('The refresh token may be expired or invalid.');
|
|
1062
|
-
showInfo("Token saved anyway. You may need to generate a new token if it doesn't work.\n");
|
|
1063
|
-
showKeyboardHints(['Enter: Continue to next step']);
|
|
1064
|
-
await prompts({ type: 'text', name: 'continue', message: 'Press Enter to continue...' });
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
else if (tokenChoice.method === 'manual') {
|
|
1069
|
-
const authUrl = getOAuthAuthorizationUrl(state.config.EBAY_CLIENT_ID, state.config.EBAY_REDIRECT_URI, state.environment);
|
|
1070
|
-
console.log('\n ' + ui.bold('OAuth Authorization URL:'));
|
|
1071
|
-
console.log(ui.dim(' ' + '─'.repeat(56)));
|
|
1072
|
-
console.log(` ${ui.info(authUrl)}`);
|
|
1073
|
-
console.log(ui.dim(' ' + '─'.repeat(56)));
|
|
1074
|
-
// Automatically open the URL in the browser
|
|
1075
|
-
console.log('\n ' + ui.info('Opening browser...'));
|
|
1076
|
-
try {
|
|
1077
|
-
await openBrowser(authUrl);
|
|
1078
|
-
showSuccess('Browser opened successfully!');
|
|
1079
|
-
}
|
|
1080
|
-
catch {
|
|
1081
|
-
showWarning('Could not open browser automatically. Please copy the URL above.');
|
|
1082
|
-
}
|
|
1083
|
-
console.log('\n ' + ui.bold('Steps:'));
|
|
1084
|
-
console.log(' 1. Sign in to your eBay account in the browser');
|
|
1085
|
-
console.log(' 2. Grant permissions to your app');
|
|
1086
|
-
console.log(' 3. You will be redirected - copy the URL or code parameter');
|
|
1087
|
-
console.log(' 4. Paste it below to complete the setup\n');
|
|
1088
|
-
// Ask for the callback URL or code
|
|
1089
|
-
const codeInput = await prompts({
|
|
1090
|
-
type: 'text',
|
|
1091
|
-
name: 'code',
|
|
1092
|
-
message: 'Paste the callback URL or authorization code:',
|
|
1093
|
-
validate: (v) => {
|
|
1094
|
-
if (!v.trim())
|
|
1095
|
-
return 'Please paste the URL or code from the callback';
|
|
1096
|
-
const code = parseAuthorizationCode(v);
|
|
1097
|
-
if (!code)
|
|
1098
|
-
return 'Could not find authorization code. Paste the full URL or the code parameter.';
|
|
1099
|
-
return true;
|
|
1100
|
-
},
|
|
1101
|
-
});
|
|
1102
|
-
if (!codeInput.code) {
|
|
1103
|
-
showWarning('OAuth setup cancelled.');
|
|
1104
|
-
return 'continue';
|
|
1105
|
-
}
|
|
1106
|
-
const authCode = parseAuthorizationCode(codeInput.code);
|
|
1107
|
-
if (!authCode) {
|
|
1108
|
-
showError('Could not parse authorization code.');
|
|
1109
|
-
return 'continue';
|
|
1110
|
-
}
|
|
1111
|
-
// Exchange code for tokens
|
|
1112
|
-
console.log('\n ' + ui.info('Exchanging authorization code for tokens...'));
|
|
1113
|
-
try {
|
|
1114
|
-
const tokens = await exchangeAuthorizationCode(authCode, state.config.EBAY_CLIENT_ID, state.config.EBAY_CLIENT_SECRET, state.config.EBAY_REDIRECT_URI, state.environment);
|
|
1115
|
-
showSuccess('Authorization code exchanged successfully!');
|
|
1116
|
-
// Store all user tokens in state.config (will be saved to .env by saveConfig)
|
|
1117
|
-
state.config.EBAY_USER_REFRESH_TOKEN = tokens.refreshToken;
|
|
1118
|
-
state.config.EBAY_USER_ACCESS_TOKEN = tokens.accessToken;
|
|
1119
|
-
// Verify setup by fetching user info (optional - requires identity scope)
|
|
1120
|
-
console.log(' ' + ui.info('Verifying setup by fetching your eBay account info...'));
|
|
1121
|
-
try {
|
|
1122
|
-
const userInfo = await fetchEbayUserInfo(tokens.accessToken, state.environment);
|
|
1123
|
-
showSuccess('Account verified successfully!');
|
|
1124
|
-
displayUserInfo(userInfo);
|
|
1125
|
-
}
|
|
1126
|
-
catch (userError) {
|
|
1127
|
-
const userErrorMsg = axios.isAxiosError(userError)
|
|
1128
|
-
? userError.response?.data?.errors?.[0]?.message || userError.message
|
|
1129
|
-
: userError instanceof Error
|
|
1130
|
-
? userError.message
|
|
1131
|
-
: 'Unknown error';
|
|
1132
|
-
showWarning(`Could not fetch user info: ${userErrorMsg}`);
|
|
1133
|
-
if (userErrorMsg.toLowerCase().includes('access denied')) {
|
|
1134
|
-
showInfo('This is normal if your RuName does not include the commerce.identity.readonly scope.');
|
|
1135
|
-
showInfo('Your OAuth tokens are valid and all other APIs will work correctly.');
|
|
1136
|
-
}
|
|
1137
|
-
else {
|
|
1138
|
-
showInfo('OAuth tokens were saved successfully. You can still use the MCP server.');
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
// Also get app access token for client credentials flow
|
|
1142
|
-
console.log(' ' + ui.info('Getting app access token...'));
|
|
1143
|
-
try {
|
|
1144
|
-
const appToken = await getAppAccessToken(state.config.EBAY_CLIENT_ID, state.config.EBAY_CLIENT_SECRET, state.environment);
|
|
1145
|
-
state.config.EBAY_APP_ACCESS_TOKEN = appToken;
|
|
1146
|
-
showSuccess('App access token obtained!');
|
|
1147
|
-
}
|
|
1148
|
-
catch {
|
|
1149
|
-
showWarning('Could not get app access token (user tokens will still work).');
|
|
1150
|
-
}
|
|
1151
|
-
// Update Claude Desktop config if installed
|
|
1152
|
-
if (isClaudeDesktopInstalled()) {
|
|
1153
|
-
console.log(' ' + ui.info('Updating Claude Desktop configuration...'));
|
|
1154
|
-
const claudeResult = updateClaudeDesktopConfig(state.config, state.environment);
|
|
1155
|
-
if (claudeResult.success) {
|
|
1156
|
-
showSuccess('Claude Desktop config updated with credentials!');
|
|
1157
|
-
showInfo(`Config: ${claudeResult.configPath}`);
|
|
1158
|
-
}
|
|
1159
|
-
else {
|
|
1160
|
-
showWarning(`Could not update Claude Desktop: ${claudeResult.error}`);
|
|
689
|
+
catch (error) {
|
|
690
|
+
stopSpinner();
|
|
691
|
+
const msg = axios.isAxiosError(error)
|
|
692
|
+
? error.response?.data?.error_description || error.response?.data?.errors?.[0]?.message || error.message
|
|
693
|
+
: error instanceof Error ? error.message : 'Unknown error';
|
|
694
|
+
showError(`Token verification failed: ${msg}`);
|
|
695
|
+
showWarning("Token saved anyway — you may need to re-authenticate if it doesn't work.");
|
|
1161
696
|
}
|
|
697
|
+
context.setNextStep(finalStepId);
|
|
1162
698
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
// Exchange code for tokens
|
|
1212
|
-
console.log('\n ' + ui.info('Exchanging authorization code for tokens...'));
|
|
1213
|
-
try {
|
|
1214
|
-
const tokens = await exchangeAuthorizationCode(authCode, state.config.EBAY_CLIENT_ID, state.config.EBAY_CLIENT_SECRET, state.config.EBAY_REDIRECT_URI, state.environment);
|
|
1215
|
-
showSuccess('Authorization code exchanged successfully!');
|
|
1216
|
-
// Store all user tokens in state.config (will be saved to .env by saveConfig)
|
|
1217
|
-
state.config.EBAY_USER_REFRESH_TOKEN = tokens.refreshToken;
|
|
1218
|
-
state.config.EBAY_USER_ACCESS_TOKEN = tokens.accessToken;
|
|
1219
|
-
// Verify setup by fetching user info (optional - requires identity scope)
|
|
1220
|
-
console.log(' ' + ui.info('Verifying setup by fetching your eBay account info...'));
|
|
1221
|
-
try {
|
|
1222
|
-
const userInfo = await fetchEbayUserInfo(tokens.accessToken, state.environment);
|
|
1223
|
-
showSuccess('Account verified successfully!');
|
|
1224
|
-
displayUserInfo(userInfo);
|
|
1225
|
-
}
|
|
1226
|
-
catch (userError) {
|
|
1227
|
-
const userErrorMsg = axios.isAxiosError(userError)
|
|
1228
|
-
? userError.response?.data?.errors?.[0]?.message || userError.message
|
|
1229
|
-
: userError instanceof Error
|
|
1230
|
-
? userError.message
|
|
1231
|
-
: 'Unknown error';
|
|
1232
|
-
showWarning(`Could not fetch user info: ${userErrorMsg}`);
|
|
1233
|
-
if (userErrorMsg.toLowerCase().includes('access denied')) {
|
|
1234
|
-
showInfo('This is normal if your RuName does not include the commerce.identity.readonly scope.');
|
|
1235
|
-
showInfo('Your OAuth tokens are valid and all other APIs will work correctly.');
|
|
699
|
+
// ── oauth-code: exchange authorization code for tokens ─────────────────
|
|
700
|
+
if (stepId === 'oauth-code') {
|
|
701
|
+
const authCode = parseAuthorizationCode(String(value));
|
|
702
|
+
if (!authCode)
|
|
703
|
+
return;
|
|
704
|
+
const stopSpinner = showSpinner('Exchanging authorization code for tokens...');
|
|
705
|
+
try {
|
|
706
|
+
const result = await exchangeAuthorizationCode(authCode, clientId, clientSecret, redirectUri, environment);
|
|
707
|
+
stopSpinner();
|
|
708
|
+
showSuccess('Authorization code exchanged successfully!');
|
|
709
|
+
tokens.refreshToken = result.refreshToken;
|
|
710
|
+
tokens.accessToken = result.accessToken;
|
|
711
|
+
try {
|
|
712
|
+
const userInfo = await fetchEbayUserInfo(result.accessToken, environment);
|
|
713
|
+
showSuccess('Account verified!');
|
|
714
|
+
displayUserInfo(userInfo);
|
|
715
|
+
}
|
|
716
|
+
catch (userError) {
|
|
717
|
+
const userMsg = axios.isAxiosError(userError)
|
|
718
|
+
? userError.response?.data?.errors?.[0]?.message || userError.message
|
|
719
|
+
: userError instanceof Error ? userError.message : 'Unknown';
|
|
720
|
+
showWarning(`Could not fetch account info: ${userMsg}`);
|
|
721
|
+
if (userMsg.toLowerCase().includes('access denied'))
|
|
722
|
+
showInfo('Normal if RuName lacks commerce.identity.readonly scope — tokens are valid.');
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
tokens.appAccessToken = await getAppAccessToken(clientId, clientSecret, environment);
|
|
726
|
+
showSuccess('App access token obtained.');
|
|
727
|
+
}
|
|
728
|
+
catch {
|
|
729
|
+
showWarning('Could not obtain app access token (user tokens still work).');
|
|
730
|
+
}
|
|
731
|
+
showInfo(`Access token expires in: ${Math.floor(result.expiresIn / 60)} minutes`);
|
|
732
|
+
showInfo(`Refresh token expires in: ${Math.floor(result.refreshTokenExpiresIn / 60 / 60 / 24)} days`);
|
|
733
|
+
if (isClaudeDesktopInstalled()) {
|
|
734
|
+
const r = updateClaudeDesktopConfig({
|
|
735
|
+
...a,
|
|
736
|
+
EBAY_USER_REFRESH_TOKEN: tokens.refreshToken ?? '',
|
|
737
|
+
EBAY_USER_ACCESS_TOKEN: tokens.accessToken ?? '',
|
|
738
|
+
}, environment);
|
|
739
|
+
if (r.success) {
|
|
740
|
+
showSuccess('Claude Desktop config updated.');
|
|
741
|
+
if (r.details)
|
|
742
|
+
showInfo(r.details);
|
|
743
|
+
}
|
|
744
|
+
else
|
|
745
|
+
showWarning(`Could not update Claude Desktop: ${r.error}`);
|
|
746
|
+
}
|
|
1236
747
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
748
|
+
catch (error) {
|
|
749
|
+
stopSpinner();
|
|
750
|
+
const msg = axios.isAxiosError(error)
|
|
751
|
+
? error.response?.data?.error_description || error.message
|
|
752
|
+
: error instanceof Error ? error.message : 'Unknown error';
|
|
753
|
+
showError(`Failed to exchange code: ${msg}`);
|
|
754
|
+
console.log(' Common issues:');
|
|
755
|
+
console.log(' • Authorization code expired (codes are valid for ~5 minutes)');
|
|
756
|
+
console.log(' • Code was already used (each code can only be used once)');
|
|
757
|
+
console.log(' • Redirect URI mismatch\n');
|
|
1239
758
|
}
|
|
759
|
+
context.setNextStep(finalStepId);
|
|
1240
760
|
}
|
|
1241
|
-
//
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
showSuccess('Claude Desktop config updated with credentials!');
|
|
1257
|
-
showInfo(`Config: ${claudeResult.configPath}`);
|
|
1258
|
-
}
|
|
1259
|
-
else {
|
|
1260
|
-
showWarning(`Could not update Claude Desktop: ${claudeResult.error}`);
|
|
761
|
+
// ── mcp-clients: configure selected clients ────────────────────────────
|
|
762
|
+
if (stepId === 'mcp-clients') {
|
|
763
|
+
const selectedNames = value;
|
|
764
|
+
for (const name of selectedNames) {
|
|
765
|
+
const client = detectedClients.find((c) => c.name === name);
|
|
766
|
+
if (!client)
|
|
767
|
+
continue;
|
|
768
|
+
const stopSpinner = showSpinner(`Configuring ${client.displayName}...`);
|
|
769
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
770
|
+
const success = configureLLMClient(client, PROJECT_ROOT);
|
|
771
|
+
stopSpinner();
|
|
772
|
+
if (success)
|
|
773
|
+
showSuccess(`Configured ${client.displayName}`);
|
|
774
|
+
else
|
|
775
|
+
showError(`Failed to configure ${client.displayName}`);
|
|
1261
776
|
}
|
|
1262
777
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
console.log(
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
await prompts({ type: 'text', name: 'continue', message: 'Press Enter to continue...' });
|
|
1269
|
-
}
|
|
1270
|
-
catch (error) {
|
|
1271
|
-
const errorMsg = axios.isAxiosError(error)
|
|
1272
|
-
? error.response?.data?.error_description || error.message
|
|
1273
|
-
: error instanceof Error
|
|
1274
|
-
? error.message
|
|
1275
|
-
: 'Unknown error';
|
|
1276
|
-
showError(`Failed to exchange code: ${errorMsg}`);
|
|
1277
|
-
console.log('\n ' + ui.dim('Common issues:'));
|
|
1278
|
-
console.log(' • Authorization code expired (codes are valid for ~5 minutes)');
|
|
1279
|
-
console.log(' • Code was already used (each code can only be used once)');
|
|
1280
|
-
console.log(' • Redirect URI mismatch (must match exactly what is configured in eBay)\n');
|
|
1281
|
-
showKeyboardHints(['Enter: Continue']);
|
|
1282
|
-
await prompts({ type: 'text', name: 'continue', message: 'Press Enter to continue...' });
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
else if (tokenChoice.method === 'skip') {
|
|
1286
|
-
showWarning("Skipping OAuth. You'll be limited to 1,000 requests/day.");
|
|
1287
|
-
}
|
|
1288
|
-
return 'continue';
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Configure MCP clients with the generated environment variables.
|
|
1292
|
-
*/
|
|
1293
|
-
async function stepMCPClients(state) {
|
|
1294
|
-
clearScreen();
|
|
1295
|
-
showLogo();
|
|
1296
|
-
showProgress(5, 'MCP Client Setup');
|
|
1297
|
-
console.log(' Configure your AI assistant to use the eBay MCP server:\n');
|
|
1298
|
-
state.detectedClients = detectLLMClients();
|
|
1299
|
-
const detected = state.detectedClients.filter((c) => c.detected);
|
|
1300
|
-
if (detected.length === 0) {
|
|
1301
|
-
showWarning('No supported MCP clients detected.\n');
|
|
1302
|
-
console.log(' Supported clients:');
|
|
1303
|
-
console.log(' • Claude Desktop (Anthropic)');
|
|
1304
|
-
console.log(' • Cline (VSCode extension)');
|
|
1305
|
-
console.log(' • Continue.dev (VSCode/JetBrains)\n');
|
|
1306
|
-
showInfo('Install one of these clients and run setup again.');
|
|
1307
|
-
const navChoice = await prompts({
|
|
1308
|
-
type: 'select',
|
|
1309
|
-
name: 'action',
|
|
1310
|
-
message: 'What would you like to do?',
|
|
1311
|
-
choices: [
|
|
1312
|
-
{ title: '→ Continue to finish setup', value: 'continue' },
|
|
1313
|
-
{ title: ui.dim('← Go back'), value: 'back' },
|
|
1314
|
-
],
|
|
1315
|
-
initial: 0,
|
|
1316
|
-
});
|
|
1317
|
-
if (!navChoice.action)
|
|
1318
|
-
return 'cancel';
|
|
1319
|
-
if (navChoice.action === 'back')
|
|
1320
|
-
return 'back';
|
|
1321
|
-
return 'continue';
|
|
1322
|
-
}
|
|
1323
|
-
showBox('Detected MCP Clients', detected.map((c) => {
|
|
1324
|
-
const status = c.configExists
|
|
1325
|
-
? `${c.displayName} [Already configured]`
|
|
1326
|
-
: `${c.displayName} [Not configured]`;
|
|
1327
|
-
return status;
|
|
1328
|
-
}));
|
|
1329
|
-
// First ask if they want to configure or go back
|
|
1330
|
-
const navChoice = await prompts({
|
|
1331
|
-
type: 'select',
|
|
1332
|
-
name: 'action',
|
|
1333
|
-
message: 'What would you like to do?',
|
|
1334
|
-
choices: [
|
|
1335
|
-
{ title: '⚙️ Configure MCP clients', value: 'configure' },
|
|
1336
|
-
{ title: '⏭️ Skip client configuration', value: 'skip' },
|
|
1337
|
-
{ title: ui.dim('← Go back'), value: 'back' },
|
|
1338
|
-
],
|
|
1339
|
-
initial: 0,
|
|
1340
|
-
});
|
|
1341
|
-
if (!navChoice.action)
|
|
1342
|
-
return 'cancel';
|
|
1343
|
-
if (navChoice.action === 'back')
|
|
1344
|
-
return 'back';
|
|
1345
|
-
if (navChoice.action === 'skip') {
|
|
1346
|
-
showInfo('Skipping client configuration.');
|
|
1347
|
-
return 'continue';
|
|
1348
|
-
}
|
|
1349
|
-
const clientChoice = await prompts({
|
|
1350
|
-
type: 'multiselect',
|
|
1351
|
-
name: 'clients',
|
|
1352
|
-
message: 'Select clients to configure:',
|
|
1353
|
-
choices: detected.map((c) => ({
|
|
1354
|
-
title: c.displayName + (c.configExists ? chalk.yellow(' [Update]') : chalk.green(' [New]')),
|
|
1355
|
-
value: c.name,
|
|
1356
|
-
selected: !c.configExists,
|
|
1357
|
-
})),
|
|
1358
|
-
hint: 'Space: Toggle Enter: Confirm',
|
|
1359
|
-
instructions: false,
|
|
778
|
+
},
|
|
779
|
+
onCancel: () => {
|
|
780
|
+
console.log(ui.warning('\n Setup cancelled.\n'));
|
|
781
|
+
process.exit(0);
|
|
782
|
+
},
|
|
1360
783
|
});
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
*/
|
|
1386
|
-
async function stepComplete(state) {
|
|
1387
|
-
clearScreen();
|
|
1388
|
-
showLogo();
|
|
1389
|
-
showProgress(6, 'Setup Complete');
|
|
1390
|
-
const stopSpinner = showSpinner('Saving configuration...');
|
|
784
|
+
// ── Persist final .env ─────────────────────────────────────────────────────
|
|
785
|
+
const marketplaceId = answers['marketplace'] === '__custom__'
|
|
786
|
+
? answers['marketplace-custom']
|
|
787
|
+
: answers['marketplace'];
|
|
788
|
+
const contentLanguage = answers['content-language'] === '__custom__'
|
|
789
|
+
? answers['content-language-custom']
|
|
790
|
+
: answers['content-language'];
|
|
791
|
+
const environment = answers['environment'];
|
|
792
|
+
const finalConfig = {
|
|
793
|
+
EBAY_CLIENT_ID: answers['client-id'],
|
|
794
|
+
EBAY_CLIENT_SECRET: answers['client-secret'] || existingConfig.EBAY_CLIENT_SECRET || '',
|
|
795
|
+
EBAY_REDIRECT_URI: answers['redirect-uri'],
|
|
796
|
+
EBAY_ENVIRONMENT: environment,
|
|
797
|
+
...(marketplaceId ? { EBAY_MARKETPLACE_ID: marketplaceId } : {}),
|
|
798
|
+
...(contentLanguage ? { EBAY_CONTENT_LANGUAGE: contentLanguage } : {}),
|
|
799
|
+
...(tokens.refreshToken
|
|
800
|
+
? { EBAY_USER_REFRESH_TOKEN: tokens.refreshToken }
|
|
801
|
+
: existingConfig.EBAY_USER_REFRESH_TOKEN
|
|
802
|
+
? { EBAY_USER_REFRESH_TOKEN: existingConfig.EBAY_USER_REFRESH_TOKEN }
|
|
803
|
+
: {}),
|
|
804
|
+
...(tokens.accessToken ? { EBAY_USER_ACCESS_TOKEN: tokens.accessToken } : {}),
|
|
805
|
+
...(tokens.appAccessToken ? { EBAY_APP_ACCESS_TOKEN: tokens.appAccessToken } : {}),
|
|
806
|
+
};
|
|
807
|
+
const stopSave = showSpinner('Saving configuration...');
|
|
1391
808
|
await new Promise((r) => setTimeout(r, 300));
|
|
1392
|
-
saveConfig(
|
|
1393
|
-
|
|
809
|
+
saveConfig(finalConfig, environment);
|
|
810
|
+
stopSave();
|
|
1394
811
|
showSuccess('Configuration saved to .env\n');
|
|
812
|
+
console.log(LOGO);
|
|
813
|
+
console.log(ui.bold.white(' MCP Server Setup Wizard by Yosef Hayim Sabag'));
|
|
814
|
+
console.log(ui.dim(' Powered by ') + chalk.hex('#0064D2').bold('grimoire-wizard') + '\n');
|
|
1395
815
|
console.log(ui.bold.green('\n 🎉 Setup Complete!\n'));
|
|
1396
816
|
showBox('Configuration Summary', [
|
|
1397
|
-
`Environment: ${
|
|
1398
|
-
`Marketplace ID: ${
|
|
1399
|
-
`Content-Lang: ${
|
|
1400
|
-
`Client ID: ${
|
|
1401
|
-
`Redirect URI: ${
|
|
1402
|
-
`OAuth Token: ${
|
|
1403
|
-
`Rate Limit: ${
|
|
817
|
+
`Environment: ${environment}`,
|
|
818
|
+
`Marketplace ID: ${finalConfig.EBAY_MARKETPLACE_ID || 'Not set'}`,
|
|
819
|
+
`Content-Lang: ${finalConfig.EBAY_CONTENT_LANGUAGE || 'Not set'}`,
|
|
820
|
+
`Client ID: ${(finalConfig.EBAY_CLIENT_ID || '').slice(0, 20)}...`,
|
|
821
|
+
`Redirect URI: ${(finalConfig.EBAY_REDIRECT_URI || '').slice(0, 30)}...`,
|
|
822
|
+
`OAuth Token: ${finalConfig.EBAY_USER_REFRESH_TOKEN ? '✓ Configured' : '✗ Not set'}`,
|
|
823
|
+
`Rate Limit: ${finalConfig.EBAY_USER_REFRESH_TOKEN ? '10k-50k/day' : '1k/day'}`,
|
|
1404
824
|
]);
|
|
1405
825
|
console.log(ui.bold.cyan('\n 📋 Quick Reference\n'));
|
|
1406
826
|
console.log(' ' + ui.dim('─'.repeat(56)));
|
|
@@ -1416,6 +836,7 @@ async function stepComplete(state) {
|
|
|
1416
836
|
console.log(ui.dim(' Documentation: ') + ui.info('https://github.com/YosefHayim/ebay-mcp'));
|
|
1417
837
|
console.log(ui.dim(' Get Help: ') + ui.info('https://github.com/YosefHayim/ebay-mcp/issues\n'));
|
|
1418
838
|
}
|
|
839
|
+
// ─── Entry point ──────────────────────────────────────────────────────────────
|
|
1419
840
|
function parseArgs() {
|
|
1420
841
|
const args = process.argv.slice(2);
|
|
1421
842
|
return {
|
|
@@ -1426,14 +847,14 @@ function parseArgs() {
|
|
|
1426
847
|
}
|
|
1427
848
|
function showHelp() {
|
|
1428
849
|
console.log(`
|
|
1429
|
-
${chalk.bold('eBay MCP Server Setup')}
|
|
850
|
+
${chalk.bold('eBay MCP Server Setup')} ${chalk.dim('powered by grimoire-wizard')}
|
|
1430
851
|
|
|
1431
852
|
${chalk.bold('Usage:')}
|
|
1432
853
|
npm run setup [options]
|
|
1433
854
|
|
|
1434
855
|
${chalk.bold('Options:')}
|
|
1435
856
|
--help, -h Show this help message
|
|
1436
|
-
--quick, -q Quick setup (skip optional
|
|
857
|
+
--quick, -q Quick setup (skip optional configuration)
|
|
1437
858
|
--diagnose, -d Run diagnostics only
|
|
1438
859
|
|
|
1439
860
|
${chalk.bold('Examples:')}
|
|
@@ -1451,8 +872,7 @@ async function main() {
|
|
|
1451
872
|
if (args.diagnose) {
|
|
1452
873
|
const { runSecurityChecks, displaySecurityResults } = await import('../utils/security-checker.js');
|
|
1453
874
|
const { validateSetup, displayRecommendations } = await import('../utils/setup-validator.js');
|
|
1454
|
-
|
|
1455
|
-
showLogo();
|
|
875
|
+
console.clear();
|
|
1456
876
|
console.log(ui.bold.cyan(' Running Diagnostics...\n'));
|
|
1457
877
|
const securityResults = await runSecurityChecks(PROJECT_ROOT);
|
|
1458
878
|
displaySecurityResults(securityResults);
|
|
@@ -1460,50 +880,16 @@ async function main() {
|
|
|
1460
880
|
displayRecommendations(summary);
|
|
1461
881
|
process.exit(0);
|
|
1462
882
|
}
|
|
1463
|
-
|
|
1464
|
-
const state = {
|
|
1465
|
-
currentStep: 0,
|
|
1466
|
-
config: existingConfig,
|
|
1467
|
-
detectedClients: [],
|
|
1468
|
-
environment: existingConfig.EBAY_ENVIRONMENT || 'sandbox',
|
|
1469
|
-
hasExistingConfig: Object.keys(existingConfig).length > 0,
|
|
1470
|
-
isQuickMode: args.quick,
|
|
1471
|
-
};
|
|
1472
|
-
const steps = [
|
|
1473
|
-
stepWelcome,
|
|
1474
|
-
stepEnvironment,
|
|
1475
|
-
stepMarketplaceSettings,
|
|
1476
|
-
stepCredentials,
|
|
1477
|
-
stepOAuth,
|
|
1478
|
-
stepMCPClients,
|
|
1479
|
-
];
|
|
1480
|
-
let stepIndex = 0;
|
|
1481
|
-
while (stepIndex < steps.length) {
|
|
1482
|
-
const result = await steps[stepIndex](state);
|
|
1483
|
-
if (result === 'cancel') {
|
|
1484
|
-
console.log(ui.warning('\n Setup cancelled.\n'));
|
|
1485
|
-
process.exit(0);
|
|
1486
|
-
}
|
|
1487
|
-
else if (result === 'back' && stepIndex > 0) {
|
|
1488
|
-
stepIndex--;
|
|
1489
|
-
}
|
|
1490
|
-
else {
|
|
1491
|
-
stepIndex++;
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
await stepComplete(state);
|
|
883
|
+
await runSetup();
|
|
1495
884
|
}
|
|
1496
885
|
process.on('SIGINT', () => {
|
|
1497
886
|
console.log(ui.warning('\n\n Setup interrupted.\n'));
|
|
1498
887
|
process.exit(0);
|
|
1499
888
|
});
|
|
1500
|
-
export async function runSetup() {
|
|
1501
|
-
await main();
|
|
1502
|
-
}
|
|
1503
889
|
const entryPath = process.argv[1] ? resolve(process.argv[1]) : undefined;
|
|
1504
890
|
const modulePath = resolve(fileURLToPath(import.meta.url));
|
|
1505
891
|
if (entryPath && modulePath === entryPath) {
|
|
1506
|
-
|
|
892
|
+
main().catch((error) => {
|
|
1507
893
|
console.error(ui.error('\n Setup failed:'), error);
|
|
1508
894
|
process.exit(1);
|
|
1509
895
|
});
|