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