@tostudy-ai/mcp-setup 1.0.3 → 1.2.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/dist/__tests__/e2e-wizard-flow.test.d.ts.map +1 -1
- package/dist/__tests__/e2e-wizard-flow.test.js +128 -117
- package/dist/__tests__/e2e-wizard-flow.test.js.map +1 -1
- package/dist/callback-page.d.ts +6 -0
- package/dist/callback-page.d.ts.map +1 -0
- package/dist/callback-page.js +96 -0
- package/dist/callback-page.js.map +1 -0
- package/dist/index.js +294 -189
- package/dist/index.js.map +1 -1
- package/dist/oauth-server.d.ts +4 -0
- package/dist/oauth-server.d.ts.map +1 -0
- package/dist/oauth-server.js +49 -0
- package/dist/oauth-server.js.map +1 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -10,20 +10,23 @@
|
|
|
10
10
|
* npx @tostudy-ai/mcp-setup --api-key <key>
|
|
11
11
|
* npx @tostudy-ai/mcp-setup --uninstall
|
|
12
12
|
*/
|
|
13
|
-
import { program } from
|
|
14
|
-
import chalk from
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
import { program } from "commander";
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import { execFile } from "node:child_process";
|
|
16
|
+
import { getClaudeConfigPath, isClaudeInstalled, addTostudyMcpServer, removeTostudyMcpServer, isTostudyMcpConfigured, } from "./config.js";
|
|
17
|
+
import { promptApiKey, confirm } from "./prompts.js";
|
|
18
|
+
import { getInstalledIDEs } from "./detect.js";
|
|
19
|
+
import { runDiagnostics, printDiagnosticReport } from "./diagnose.js";
|
|
20
|
+
import { repairAllIssues, printRepairReport } from "./repair.js";
|
|
21
|
+
import { getIDEHandler, detectInstalledIDEs } from "./ide-handlers/index.js";
|
|
22
|
+
import { startCallbackServer } from "./oauth-server.js";
|
|
23
|
+
const VERSION = "1.2.0";
|
|
24
|
+
const DEFAULT_PLATFORM_URL = "https://tostudy.com";
|
|
25
|
+
const OAUTH_PORT = 9877;
|
|
26
|
+
function println(message = "") {
|
|
24
27
|
process.stdout.write(`${message}\n`);
|
|
25
28
|
}
|
|
26
|
-
function eprintln(message =
|
|
29
|
+
function eprintln(message = "") {
|
|
27
30
|
process.stderr.write(`${message}\n`);
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
@@ -31,9 +34,9 @@ function eprintln(message = '') {
|
|
|
31
34
|
*/
|
|
32
35
|
function printBanner() {
|
|
33
36
|
println();
|
|
34
|
-
println(chalk.cyan(
|
|
35
|
-
println(chalk.cyan(
|
|
36
|
-
println(chalk.cyan(
|
|
37
|
+
println(chalk.cyan(" ╔═══════════════════════════════════════╗"));
|
|
38
|
+
println(chalk.cyan(" ║") + chalk.white.bold(" Catalyst MCP Setup ") + chalk.cyan("║"));
|
|
39
|
+
println(chalk.cyan(" ╚═══════════════════════════════════════╝"));
|
|
37
40
|
println();
|
|
38
41
|
}
|
|
39
42
|
/**
|
|
@@ -42,10 +45,10 @@ function printBanner() {
|
|
|
42
45
|
async function validateApiKey(apiKey, platformUrl) {
|
|
43
46
|
try {
|
|
44
47
|
const response = await fetch(`${platformUrl}/api/mcp/heartbeat`, {
|
|
45
|
-
method:
|
|
48
|
+
method: "POST",
|
|
46
49
|
headers: {
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
Authorization: `Bearer ${apiKey}`,
|
|
51
|
+
"Content-Type": "application/json",
|
|
49
52
|
},
|
|
50
53
|
body: JSON.stringify({ timestamp: new Date().toISOString() }),
|
|
51
54
|
});
|
|
@@ -53,78 +56,147 @@ async function validateApiKey(apiKey, platformUrl) {
|
|
|
53
56
|
}
|
|
54
57
|
catch {
|
|
55
58
|
// Network error - can't validate, but allow anyway for offline setup
|
|
56
|
-
println(chalk.yellow(
|
|
57
|
-
println(chalk.yellow(
|
|
59
|
+
println(chalk.yellow("! Nao foi possivel validar a API key (servidor offline?)"));
|
|
60
|
+
println(chalk.yellow(" A configuracao sera salva mesmo assim."));
|
|
58
61
|
return true;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Exchange an OAuth authorization code for a JWT token.
|
|
66
|
+
*/
|
|
67
|
+
async function exchangeCodeForToken(code, platformUrl, client = "mcp") {
|
|
68
|
+
const res = await fetch(`${platformUrl}/api/cli/auth/exchange`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ code, client }),
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const body = (await res.json().catch(() => ({})));
|
|
75
|
+
throw new Error(body.error ?? `Falha na autenticacao (${res.status})`);
|
|
76
|
+
}
|
|
77
|
+
return res.json();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Configure all detected IDEs with the given token.
|
|
81
|
+
*/
|
|
82
|
+
async function configureAllIDEs(token, platformUrl) {
|
|
83
|
+
const mcpUrl = resolveMcpServerUrl(platformUrl);
|
|
84
|
+
const installedIDEs = await detectInstalledIDEs();
|
|
85
|
+
if (installedIDEs.length === 0) {
|
|
86
|
+
println(chalk.yellow(" Nenhuma IDE suportada detectada."));
|
|
87
|
+
println(chalk.gray(" Use: npx @tostudy-ai/mcp-setup install --ide <nome> --key <token>"));
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
let configured = 0;
|
|
91
|
+
for (const handler of installedIDEs) {
|
|
92
|
+
process.stdout.write(` ${chalk.gray("Configurando")} ${handler.name}... `);
|
|
93
|
+
try {
|
|
94
|
+
await handler.writeConfig(token, mcpUrl);
|
|
95
|
+
println(chalk.green("OK"));
|
|
96
|
+
configured++;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
println(chalk.red("FALHOU"));
|
|
100
|
+
eprintln(chalk.gray(` ${error instanceof Error ? error.message : String(error)}`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return configured;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Print success banner after IDE configuration.
|
|
107
|
+
*/
|
|
108
|
+
function printSuccessBanner(configuredCount) {
|
|
109
|
+
println();
|
|
110
|
+
if (configuredCount > 0) {
|
|
111
|
+
println(chalk.green(" ╔═══════════════════════════════════════════════╗"));
|
|
112
|
+
println(chalk.green(" ║") +
|
|
113
|
+
chalk.white.bold(` ${configuredCount} IDE${configuredCount > 1 ? "s" : ""} configurada${configuredCount > 1 ? "s" : ""} com sucesso!`.padEnd(42)) +
|
|
114
|
+
chalk.green("║"));
|
|
115
|
+
println(chalk.green(" ╚═══════════════════════════════════════════════╝"));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
println(chalk.yellow(" Nenhuma IDE foi configurada."));
|
|
119
|
+
println(chalk.gray(" Use: npx @tostudy-ai/mcp-setup install --ide <nome> --key <token>"));
|
|
120
|
+
}
|
|
121
|
+
println();
|
|
122
|
+
println(chalk.white(" Proximos passos:"));
|
|
123
|
+
println(chalk.gray(" 1.") + " Reinicie as IDEs configuradas");
|
|
124
|
+
println(chalk.gray(" 2.") + " Use " + chalk.cyan("/courses") + " para ver seus cursos");
|
|
125
|
+
println();
|
|
126
|
+
println(chalk.gray(" ─────────────────────────────────────────"));
|
|
127
|
+
println();
|
|
128
|
+
println(chalk.white(" Prefere estudar pelo terminal?"));
|
|
129
|
+
println(chalk.gray(" ") + chalk.cyan("npm install -g @tostudy-ai/cli"));
|
|
130
|
+
println(chalk.gray(" ") +
|
|
131
|
+
chalk.cyan("tostudy login") +
|
|
132
|
+
chalk.gray(" → ") +
|
|
133
|
+
chalk.cyan("tostudy courses"));
|
|
134
|
+
println();
|
|
135
|
+
}
|
|
61
136
|
/**
|
|
62
137
|
* Main setup flow
|
|
63
138
|
*/
|
|
64
139
|
async function setup(apiKey, platformUrl) {
|
|
65
140
|
printBanner();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
println(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
println();
|
|
78
|
-
// Check if already configured
|
|
79
|
-
if (isTostudyMcpConfigured()) {
|
|
80
|
-
println(chalk.yellow('! Catalyst MCP ja esta configurado.'));
|
|
81
|
-
const overwrite = await confirm('Deseja reconfigurar?', false);
|
|
82
|
-
if (!overwrite) {
|
|
83
|
-
println(chalk.gray('Operacao cancelada.'));
|
|
84
|
-
process.exit(0);
|
|
141
|
+
const url = platformUrl || process.env.TOSTUDY_PLATFORM_URL || DEFAULT_PLATFORM_URL;
|
|
142
|
+
// API key fallback: skip OAuth, use key directly
|
|
143
|
+
if (apiKey || process.env.TOSTUDY_API_KEY) {
|
|
144
|
+
const key = apiKey || process.env.TOSTUDY_API_KEY;
|
|
145
|
+
println(chalk.gray(" Usando API key fornecida..."));
|
|
146
|
+
process.stdout.write(chalk.gray(" Validando... "));
|
|
147
|
+
const valid = await validateApiKey(key, url);
|
|
148
|
+
if (!valid) {
|
|
149
|
+
println(chalk.red("FALHOU"));
|
|
150
|
+
println(chalk.red(" API key invalida ou expirada."));
|
|
151
|
+
process.exit(1);
|
|
85
152
|
}
|
|
153
|
+
println(chalk.green("OK"));
|
|
86
154
|
println();
|
|
155
|
+
const configured = await configureAllIDEs(key, url);
|
|
156
|
+
printSuccessBanner(configured);
|
|
157
|
+
return;
|
|
87
158
|
}
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
159
|
+
// OAuth flow
|
|
160
|
+
println(chalk.gray(" Abrindo browser para autenticacao...\n"));
|
|
161
|
+
let code;
|
|
162
|
+
try {
|
|
163
|
+
const serverPromise = startCallbackServer(OAUTH_PORT);
|
|
164
|
+
const authUrl = `${url}/api/cli/auth/authorize?port=${OAUTH_PORT}`;
|
|
165
|
+
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
166
|
+
execFile(openCmd, [authUrl], (err) => {
|
|
167
|
+
if (err) {
|
|
168
|
+
println(chalk.yellow(" Nao foi possivel abrir o browser automaticamente."));
|
|
169
|
+
println(chalk.gray(` Abra manualmente: ${authUrl}\n`));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
const result = await serverPromise;
|
|
173
|
+
code = result.code;
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
177
|
+
eprintln(chalk.red(` Erro: ${msg}`));
|
|
103
178
|
process.exit(1);
|
|
104
179
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
180
|
+
// Exchange code for JWT
|
|
181
|
+
process.stdout.write(chalk.gray(" Autenticando... "));
|
|
182
|
+
let token;
|
|
183
|
+
let userName;
|
|
108
184
|
try {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
println(chalk.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
185
|
+
const result = await exchangeCodeForToken(code, url, "mcp");
|
|
186
|
+
token = result.token;
|
|
187
|
+
userName = result.userName;
|
|
188
|
+
println(chalk.green("OK"));
|
|
189
|
+
println(chalk.green(` Logado como ${userName}\n`));
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
println(chalk.red("FALHOU"));
|
|
193
|
+
eprintln(chalk.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
117
194
|
process.exit(1);
|
|
118
195
|
}
|
|
119
|
-
//
|
|
120
|
-
println();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
println(chalk.white('Proximos passos:'));
|
|
124
|
-
println(chalk.gray(' 1.') + ' Reinicie o Claude Code');
|
|
125
|
-
println(chalk.gray(' 2.') + ' O servidor MCP iniciara automaticamente');
|
|
126
|
-
println(chalk.gray(' 3.') + ' Use ' + chalk.cyan('/courses') + ' para ver seus cursos');
|
|
127
|
-
println();
|
|
196
|
+
// Configure all detected IDEs
|
|
197
|
+
println(chalk.gray(" Detectando IDEs...\n"));
|
|
198
|
+
const configured = await configureAllIDEs(token, url);
|
|
199
|
+
printSuccessBanner(configured);
|
|
128
200
|
}
|
|
129
201
|
/**
|
|
130
202
|
* Uninstall flow
|
|
@@ -132,25 +204,25 @@ async function setup(apiKey, platformUrl) {
|
|
|
132
204
|
async function uninstall() {
|
|
133
205
|
printBanner();
|
|
134
206
|
if (!isTostudyMcpConfigured()) {
|
|
135
|
-
println(chalk.yellow(
|
|
207
|
+
println(chalk.yellow("Catalyst MCP nao esta configurado."));
|
|
136
208
|
process.exit(0);
|
|
137
209
|
}
|
|
138
|
-
const shouldUninstall = await confirm(
|
|
210
|
+
const shouldUninstall = await confirm("Remover configuracao do Catalyst MCP?", false);
|
|
139
211
|
if (!shouldUninstall) {
|
|
140
|
-
println(chalk.gray(
|
|
212
|
+
println(chalk.gray("Operacao cancelada."));
|
|
141
213
|
process.exit(0);
|
|
142
214
|
}
|
|
143
|
-
process.stdout.write(chalk.gray(
|
|
215
|
+
process.stdout.write(chalk.gray("Removendo configuracao... "));
|
|
144
216
|
try {
|
|
145
217
|
removeTostudyMcpServer();
|
|
146
|
-
println(chalk.green(
|
|
218
|
+
println(chalk.green("OK"));
|
|
147
219
|
println();
|
|
148
|
-
println(chalk.green(
|
|
149
|
-
println(chalk.gray(
|
|
220
|
+
println(chalk.green("Configuracao removida com sucesso."));
|
|
221
|
+
println(chalk.gray("Reinicie o Claude Code para aplicar as mudancas."));
|
|
150
222
|
println();
|
|
151
223
|
}
|
|
152
224
|
catch (error) {
|
|
153
|
-
println(chalk.red(
|
|
225
|
+
println(chalk.red("FALHOU"));
|
|
154
226
|
eprintln(error instanceof Error ? error.message : String(error));
|
|
155
227
|
process.exit(1);
|
|
156
228
|
}
|
|
@@ -168,14 +240,14 @@ function printStep(step, total, title) {
|
|
|
168
240
|
*/
|
|
169
241
|
function printIDEDetection(installedIDEs) {
|
|
170
242
|
if (installedIDEs.length === 0) {
|
|
171
|
-
println(chalk.yellow(
|
|
172
|
-
println(chalk.gray(
|
|
243
|
+
println(chalk.yellow(" Nenhuma IDE suportada detectada."));
|
|
244
|
+
println(chalk.gray(" O setup continuara para Claude Code."));
|
|
173
245
|
}
|
|
174
246
|
else {
|
|
175
|
-
println(chalk.green(
|
|
247
|
+
println(chalk.green(" IDEs detectadas:"));
|
|
176
248
|
for (const ide of installedIDEs) {
|
|
177
|
-
const version = ide.version ? chalk.gray(` (${ide.version})`) :
|
|
178
|
-
println(` ${chalk.green(
|
|
249
|
+
const version = ide.version ? chalk.gray(` (${ide.version})`) : "";
|
|
250
|
+
println(` ${chalk.green("✓")} ${ide.name}${version}`);
|
|
179
251
|
}
|
|
180
252
|
}
|
|
181
253
|
}
|
|
@@ -187,122 +259,127 @@ async function wizard(options) {
|
|
|
187
259
|
const TOTAL_STEPS = 4;
|
|
188
260
|
// Print wizard banner
|
|
189
261
|
println();
|
|
190
|
-
println(chalk.cyan(
|
|
191
|
-
println(chalk.cyan(
|
|
192
|
-
|
|
193
|
-
|
|
262
|
+
println(chalk.cyan(" ╔═══════════════════════════════════════════════╗"));
|
|
263
|
+
println(chalk.cyan(" ║") +
|
|
264
|
+
chalk.white.bold(" Catalyst MCP Setup Wizard ") +
|
|
265
|
+
chalk.cyan("║"));
|
|
266
|
+
println(chalk.cyan(" ║") + chalk.gray(" Configuracao guiada passo a passo ") + chalk.cyan("║"));
|
|
267
|
+
println(chalk.cyan(" ╚═══════════════════════════════════════════════╝"));
|
|
194
268
|
println();
|
|
195
|
-
println(chalk.gray(
|
|
196
|
-
println(chalk.gray(
|
|
269
|
+
println(chalk.gray(" Este assistente vai configurar o Claude Code para"));
|
|
270
|
+
println(chalk.gray(" conectar ao servidor MCP da plataforma Catalyst."));
|
|
197
271
|
println();
|
|
198
272
|
// ━━━ Step 1: Environment Detection ━━━
|
|
199
|
-
printStep(1, TOTAL_STEPS,
|
|
273
|
+
printStep(1, TOTAL_STEPS, "Detectando ambiente");
|
|
200
274
|
// Check Claude Code
|
|
201
|
-
process.stdout.write(
|
|
275
|
+
process.stdout.write(" Verificando Claude Code... ");
|
|
202
276
|
if (!isClaudeInstalled()) {
|
|
203
|
-
println(chalk.red(
|
|
277
|
+
println(chalk.red("NAO ENCONTRADO"));
|
|
204
278
|
println();
|
|
205
|
-
println(chalk.red(
|
|
279
|
+
println(chalk.red(" Claude Code nao esta instalado."));
|
|
206
280
|
println();
|
|
207
|
-
println(
|
|
208
|
-
println(chalk.cyan(
|
|
281
|
+
println(" Por favor, instale primeiro:");
|
|
282
|
+
println(chalk.cyan(" https://claude.ai/download"));
|
|
209
283
|
println();
|
|
210
284
|
process.exit(1);
|
|
211
285
|
}
|
|
212
|
-
println(chalk.green(
|
|
286
|
+
println(chalk.green("OK"));
|
|
213
287
|
const configPath = getClaudeConfigPath();
|
|
214
288
|
println(chalk.gray(` Config: ${configPath}`));
|
|
215
289
|
// Check for other IDEs
|
|
216
|
-
process.stdout.write(
|
|
290
|
+
process.stdout.write(" Detectando IDEs... ");
|
|
217
291
|
const installedIDEs = getInstalledIDEs();
|
|
218
|
-
println(chalk.green(
|
|
292
|
+
println(chalk.green("OK"));
|
|
219
293
|
printIDEDetection(installedIDEs);
|
|
220
294
|
// Check existing configuration
|
|
221
|
-
process.stdout.write(
|
|
295
|
+
process.stdout.write(" Verificando configuracao atual... ");
|
|
222
296
|
const alreadyConfigured = isTostudyMcpConfigured();
|
|
223
297
|
if (alreadyConfigured) {
|
|
224
|
-
println(chalk.yellow(
|
|
298
|
+
println(chalk.yellow("JA CONFIGURADO"));
|
|
225
299
|
println();
|
|
226
|
-
const overwrite = await confirm(
|
|
300
|
+
const overwrite = await confirm(" Deseja reconfigurar?", false);
|
|
227
301
|
if (!overwrite) {
|
|
228
302
|
println();
|
|
229
|
-
println(chalk.gray(
|
|
303
|
+
println(chalk.gray(" Operacao cancelada."));
|
|
230
304
|
process.exit(0);
|
|
231
305
|
}
|
|
232
306
|
}
|
|
233
307
|
else {
|
|
234
|
-
println(chalk.gray(
|
|
308
|
+
println(chalk.gray("Nao configurado"));
|
|
235
309
|
}
|
|
236
310
|
// ━━━ Step 2: API Key Configuration ━━━
|
|
237
|
-
printStep(2, TOTAL_STEPS,
|
|
238
|
-
println(chalk.gray(
|
|
311
|
+
printStep(2, TOTAL_STEPS, "Configurando API Key");
|
|
312
|
+
println(chalk.gray(" A API key conecta o Claude Code a sua conta na plataforma."));
|
|
239
313
|
println();
|
|
240
314
|
let apiKey = options.apiKey || process.env.TOSTUDY_API_KEY;
|
|
241
315
|
if (!apiKey) {
|
|
242
|
-
println(
|
|
316
|
+
println(" Acesse sua API key em:");
|
|
243
317
|
println(chalk.cyan(` ${options.url || DEFAULT_PLATFORM_URL}/student/settings/mcp`));
|
|
244
318
|
println();
|
|
245
319
|
apiKey = await promptApiKey();
|
|
246
320
|
}
|
|
247
321
|
else {
|
|
248
|
-
println(chalk.green(
|
|
322
|
+
println(chalk.green(" ✓ API key fornecida via parametro ou ambiente"));
|
|
249
323
|
}
|
|
250
324
|
const platformUrl = options.url || process.env.TOSTUDY_PLATFORM_URL || DEFAULT_PLATFORM_URL;
|
|
251
325
|
// Validate API key
|
|
252
326
|
println();
|
|
253
|
-
process.stdout.write(chalk.gray(
|
|
327
|
+
process.stdout.write(chalk.gray(" Validando API key... "));
|
|
254
328
|
const valid = await validateApiKey(apiKey, platformUrl);
|
|
255
329
|
if (!valid) {
|
|
256
|
-
println(chalk.red(
|
|
330
|
+
println(chalk.red("FALHOU"));
|
|
257
331
|
println();
|
|
258
|
-
println(chalk.red(
|
|
332
|
+
println(chalk.red(" API key invalida ou expirada."));
|
|
259
333
|
println();
|
|
260
|
-
println(
|
|
334
|
+
println(" Para gerar uma nova API key:");
|
|
261
335
|
println(chalk.cyan(` ${platformUrl}/student/settings/mcp`));
|
|
262
336
|
println();
|
|
263
337
|
process.exit(1);
|
|
264
338
|
}
|
|
265
|
-
println(chalk.green(
|
|
339
|
+
println(chalk.green("OK"));
|
|
266
340
|
// ━━━ Step 3: Save Configuration ━━━
|
|
267
|
-
printStep(3, TOTAL_STEPS,
|
|
268
|
-
process.stdout.write(
|
|
341
|
+
printStep(3, TOTAL_STEPS, "Salvando configuracao");
|
|
342
|
+
process.stdout.write(" Configurando Claude Code... ");
|
|
269
343
|
try {
|
|
270
344
|
addTostudyMcpServer(apiKey, platformUrl);
|
|
271
|
-
println(chalk.green(
|
|
345
|
+
println(chalk.green("OK"));
|
|
272
346
|
}
|
|
273
347
|
catch (error) {
|
|
274
|
-
println(chalk.red(
|
|
348
|
+
println(chalk.red("FALHOU"));
|
|
275
349
|
eprintln();
|
|
276
|
-
eprintln(chalk.red(
|
|
277
|
-
eprintln(
|
|
350
|
+
eprintln(chalk.red(" Erro ao salvar configuracao:"));
|
|
351
|
+
eprintln(" " + (error instanceof Error ? error.message : String(error)));
|
|
278
352
|
process.exit(1);
|
|
279
353
|
}
|
|
280
354
|
// ━━━ Step 4: Diagnostics & Verification ━━━
|
|
281
|
-
printStep(4, TOTAL_STEPS,
|
|
355
|
+
printStep(4, TOTAL_STEPS, "Verificacao final");
|
|
282
356
|
if (!options.skipDiagnostics) {
|
|
283
|
-
process.stdout.write(
|
|
357
|
+
process.stdout.write(" Executando diagnostico... ");
|
|
284
358
|
const report = await runDiagnostics();
|
|
285
|
-
println(chalk.green(
|
|
359
|
+
println(chalk.green("OK"));
|
|
286
360
|
println();
|
|
287
361
|
// Filter out expected issues that we just fixed
|
|
288
|
-
const remainingIssues = report.issues.filter((issue) => ![
|
|
362
|
+
const remainingIssues = report.issues.filter((issue) => !["mcp-not-configured", "config-missing"].includes(issue.id));
|
|
289
363
|
if (remainingIssues.length > 0) {
|
|
290
|
-
println(chalk.yellow(
|
|
364
|
+
println(chalk.yellow(" Avisos encontrados:"));
|
|
291
365
|
for (const issue of remainingIssues) {
|
|
292
|
-
const icon = issue.severity ===
|
|
293
|
-
|
|
366
|
+
const icon = issue.severity === "critical"
|
|
367
|
+
? chalk.red("●")
|
|
368
|
+
: issue.severity === "warning"
|
|
369
|
+
? chalk.yellow("●")
|
|
370
|
+
: chalk.blue("●");
|
|
294
371
|
println(` ${icon} ${issue.title}`);
|
|
295
372
|
}
|
|
296
373
|
println();
|
|
297
374
|
// Auto-repair if enabled
|
|
298
375
|
if (options.autoRepair) {
|
|
299
|
-
process.stdout.write(
|
|
376
|
+
process.stdout.write(" Tentando reparar automaticamente... ");
|
|
300
377
|
const repairReport = repairAllIssues(report, apiKey, platformUrl);
|
|
301
378
|
if (repairReport.repairsSucceeded > 0) {
|
|
302
379
|
println(chalk.green(`${repairReport.repairsSucceeded} corrigido(s)`));
|
|
303
380
|
}
|
|
304
381
|
else {
|
|
305
|
-
println(chalk.yellow(
|
|
382
|
+
println(chalk.yellow("Nenhum reparo aplicado"));
|
|
306
383
|
}
|
|
307
384
|
}
|
|
308
385
|
else {
|
|
@@ -310,39 +387,54 @@ async function wizard(options) {
|
|
|
310
387
|
}
|
|
311
388
|
}
|
|
312
389
|
else {
|
|
313
|
-
println(chalk.green(
|
|
390
|
+
println(chalk.green(" ✓ Nenhum problema encontrado!"));
|
|
314
391
|
}
|
|
315
392
|
}
|
|
316
393
|
else {
|
|
317
|
-
println(chalk.gray(
|
|
394
|
+
println(chalk.gray(" Diagnostico ignorado (--skip-diagnostics)"));
|
|
318
395
|
}
|
|
319
396
|
// ━━━ Success ━━━
|
|
320
397
|
println();
|
|
321
|
-
println(chalk.green(
|
|
322
|
-
println(chalk.green(
|
|
323
|
-
|
|
398
|
+
println(chalk.green(" ╔═══════════════════════════════════════════════╗"));
|
|
399
|
+
println(chalk.green(" ║") +
|
|
400
|
+
chalk.white.bold(" Configuracao concluida com sucesso! ") +
|
|
401
|
+
chalk.green("║"));
|
|
402
|
+
println(chalk.green(" ╚═══════════════════════════════════════════════╝"));
|
|
403
|
+
println();
|
|
404
|
+
println(chalk.white(" Proximos passos:"));
|
|
405
|
+
println();
|
|
406
|
+
println(chalk.gray(" 1.") + " Reinicie o Claude Code");
|
|
407
|
+
println(chalk.gray(" 2.") + " O servidor MCP iniciara automaticamente");
|
|
408
|
+
println(chalk.gray(" 3.") + " Use " + chalk.cyan("/courses") + " para ver seus cursos");
|
|
324
409
|
println();
|
|
325
|
-
println(chalk.
|
|
410
|
+
println(chalk.gray(" Comandos uteis:"));
|
|
411
|
+
println(chalk.gray(" ") +
|
|
412
|
+
chalk.cyan("npx @tostudy-ai/mcp-setup diagnose") +
|
|
413
|
+
chalk.gray(" - Verificar problemas"));
|
|
414
|
+
println(chalk.gray(" ") +
|
|
415
|
+
chalk.cyan("npx @tostudy-ai/mcp-setup repair") +
|
|
416
|
+
chalk.gray(" - Reparar automaticamente"));
|
|
326
417
|
println();
|
|
327
|
-
println(chalk.gray(
|
|
328
|
-
println(chalk.gray(' 2.') + ' O servidor MCP iniciara automaticamente');
|
|
329
|
-
println(chalk.gray(' 3.') + ' Use ' + chalk.cyan('/courses') + ' para ver seus cursos');
|
|
418
|
+
println(chalk.gray(" ─────────────────────────────────────────"));
|
|
330
419
|
println();
|
|
331
|
-
println(chalk.
|
|
332
|
-
println(chalk.gray(
|
|
333
|
-
println(chalk.gray(
|
|
420
|
+
println(chalk.white(" Prefere estudar pelo terminal?"));
|
|
421
|
+
println(chalk.gray(" ") + chalk.cyan("npm install -g @tostudy-ai/cli"));
|
|
422
|
+
println(chalk.gray(" ") +
|
|
423
|
+
chalk.cyan("tostudy login") +
|
|
424
|
+
chalk.gray(" → ") +
|
|
425
|
+
chalk.cyan("tostudy courses"));
|
|
334
426
|
println();
|
|
335
427
|
}
|
|
336
428
|
// CLI setup
|
|
337
429
|
program
|
|
338
|
-
.name(
|
|
339
|
-
.description(
|
|
430
|
+
.name("tostudy-mcp-setup")
|
|
431
|
+
.description("Configura o Claude Code para usar o Catalyst MCP server")
|
|
340
432
|
.version(VERSION);
|
|
341
433
|
// Default command (setup)
|
|
342
434
|
program
|
|
343
|
-
.option(
|
|
344
|
-
.option(
|
|
345
|
-
.option(
|
|
435
|
+
.option("-k, --api-key <key>", "API key (fallback para ambientes sem browser)")
|
|
436
|
+
.option("-u, --url <url>", "URL da plataforma (default: https://tostudy.com)")
|
|
437
|
+
.option("--uninstall", "Remove a configuracao do Catalyst MCP")
|
|
346
438
|
.action(async (options) => {
|
|
347
439
|
try {
|
|
348
440
|
if (options.uninstall) {
|
|
@@ -354,18 +446,18 @@ program
|
|
|
354
446
|
}
|
|
355
447
|
catch (error) {
|
|
356
448
|
eprintln();
|
|
357
|
-
eprintln(chalk.red(
|
|
449
|
+
eprintln(chalk.red("Erro:") + " " + (error instanceof Error ? error.message : String(error)));
|
|
358
450
|
process.exit(1);
|
|
359
451
|
}
|
|
360
452
|
});
|
|
361
453
|
// Wizard command - interactive step-by-step setup
|
|
362
454
|
program
|
|
363
|
-
.command(
|
|
364
|
-
.description(
|
|
365
|
-
.option(
|
|
366
|
-
.option(
|
|
367
|
-
.option(
|
|
368
|
-
.option(
|
|
455
|
+
.command("wizard")
|
|
456
|
+
.description("Assistente interativo de configuracao passo a passo")
|
|
457
|
+
.option("-k, --api-key <key>", "API key da plataforma Catalyst")
|
|
458
|
+
.option("-u, --url <url>", "URL da plataforma (default: https://tostudy.com)")
|
|
459
|
+
.option("--skip-diagnostics", "Pula a verificacao de diagnostico apos setup")
|
|
460
|
+
.option("--auto-repair", "Tenta reparar problemas automaticamente")
|
|
369
461
|
.action(async (options) => {
|
|
370
462
|
try {
|
|
371
463
|
await wizard({
|
|
@@ -377,15 +469,15 @@ program
|
|
|
377
469
|
}
|
|
378
470
|
catch (error) {
|
|
379
471
|
eprintln();
|
|
380
|
-
eprintln(chalk.red(
|
|
472
|
+
eprintln(chalk.red("Erro:") + " " + (error instanceof Error ? error.message : String(error)));
|
|
381
473
|
process.exit(1);
|
|
382
474
|
}
|
|
383
475
|
});
|
|
384
476
|
// Diagnose command
|
|
385
477
|
program
|
|
386
|
-
.command(
|
|
387
|
-
.description(
|
|
388
|
-
.option(
|
|
478
|
+
.command("diagnose")
|
|
479
|
+
.description("Executa diagnostico de problemas na configuracao MCP")
|
|
480
|
+
.option("--json", "Saida em formato JSON")
|
|
389
481
|
.action(async (options) => {
|
|
390
482
|
try {
|
|
391
483
|
const report = await runDiagnostics();
|
|
@@ -399,18 +491,18 @@ program
|
|
|
399
491
|
}
|
|
400
492
|
catch (error) {
|
|
401
493
|
eprintln();
|
|
402
|
-
eprintln(chalk.red(
|
|
494
|
+
eprintln(chalk.red("Erro ao executar diagnostico:"));
|
|
403
495
|
eprintln(error instanceof Error ? error.message : String(error));
|
|
404
496
|
process.exit(1);
|
|
405
497
|
}
|
|
406
498
|
});
|
|
407
499
|
// Repair command
|
|
408
500
|
program
|
|
409
|
-
.command(
|
|
410
|
-
.description(
|
|
411
|
-
.option(
|
|
412
|
-
.option(
|
|
413
|
-
.option(
|
|
501
|
+
.command("repair")
|
|
502
|
+
.description("Repara problemas de configuracao automaticamente")
|
|
503
|
+
.option("-k, --api-key <key>", "API key para reparos que necessitam autenticacao")
|
|
504
|
+
.option("-u, --url <url>", "URL da plataforma (default: https://tostudy.com)")
|
|
505
|
+
.option("--json", "Saida em formato JSON")
|
|
414
506
|
.action(async (options) => {
|
|
415
507
|
try {
|
|
416
508
|
const apiKey = options.apiKey || process.env.TOSTUDY_API_KEY;
|
|
@@ -423,30 +515,41 @@ program
|
|
|
423
515
|
else {
|
|
424
516
|
printRepairReport(repair);
|
|
425
517
|
}
|
|
426
|
-
const hasUnfixedCritical = diagnostic.issues.some((issue) => issue.severity ===
|
|
518
|
+
const hasUnfixedCritical = diagnostic.issues.some((issue) => issue.severity === "critical" &&
|
|
519
|
+
!repair.results.find((r) => r.issueId === issue.id && r.success));
|
|
427
520
|
process.exit(hasUnfixedCritical ? 1 : 0);
|
|
428
521
|
}
|
|
429
522
|
catch (error) {
|
|
430
523
|
eprintln();
|
|
431
|
-
eprintln(chalk.red(
|
|
524
|
+
eprintln(chalk.red("Erro ao executar reparo:"));
|
|
432
525
|
eprintln(error instanceof Error ? error.message : String(error));
|
|
433
526
|
process.exit(1);
|
|
434
527
|
}
|
|
435
528
|
});
|
|
436
529
|
// Install command - non-interactive, used by the web wizard's npx command
|
|
437
|
-
const SUPPORTED_IDES = [
|
|
530
|
+
const SUPPORTED_IDES = [
|
|
531
|
+
"claude-code",
|
|
532
|
+
"cursor",
|
|
533
|
+
"vscode",
|
|
534
|
+
"desktop",
|
|
535
|
+
"windsurf",
|
|
536
|
+
"opencode",
|
|
537
|
+
"codex",
|
|
538
|
+
"antigravity",
|
|
539
|
+
"manual",
|
|
540
|
+
];
|
|
438
541
|
program
|
|
439
|
-
.command(
|
|
440
|
-
.description(
|
|
441
|
-
.requiredOption(
|
|
442
|
-
.requiredOption(
|
|
443
|
-
.option(
|
|
542
|
+
.command("install")
|
|
543
|
+
.description("Install MCP config for a specific IDE (used by the web setup wizard)")
|
|
544
|
+
.requiredOption("--ide <ide>", `Target IDE: ${SUPPORTED_IDES.join(", ")}`)
|
|
545
|
+
.requiredOption("--key <key>", "API key from the platform")
|
|
546
|
+
.option("--url <url>", "Platform URL (default: https://tostudy.com)", DEFAULT_PLATFORM_URL)
|
|
444
547
|
.action(async (options) => {
|
|
445
548
|
try {
|
|
446
549
|
const ide = options.ide;
|
|
447
550
|
if (!SUPPORTED_IDES.includes(ide)) {
|
|
448
551
|
eprintln(chalk.red(`Unknown IDE: ${ide}`));
|
|
449
|
-
eprintln(`Supported: ${SUPPORTED_IDES.join(
|
|
552
|
+
eprintln(`Supported: ${SUPPORTED_IDES.join(", ")}`);
|
|
450
553
|
process.exit(1);
|
|
451
554
|
}
|
|
452
555
|
const handler = getIDEHandler(ide);
|
|
@@ -454,29 +557,31 @@ program
|
|
|
454
557
|
println();
|
|
455
558
|
println(chalk.cyan(` Installing MCP config for ${handler.name}...`));
|
|
456
559
|
// Write config
|
|
457
|
-
process.stdout.write(chalk.gray(
|
|
560
|
+
process.stdout.write(chalk.gray(" Writing config... "));
|
|
458
561
|
await handler.writeConfig(options.key, mcpUrl);
|
|
459
|
-
println(chalk.green(
|
|
460
|
-
if (ide !==
|
|
562
|
+
println(chalk.green("OK"));
|
|
563
|
+
if (ide !== "manual") {
|
|
461
564
|
println(chalk.gray(` Config: ${handler.getConfigPath()}`));
|
|
462
565
|
}
|
|
463
566
|
// Verify heartbeat
|
|
464
|
-
process.stdout.write(chalk.gray(
|
|
567
|
+
process.stdout.write(chalk.gray(" Verifying connection... "));
|
|
465
568
|
const verified = await handler.verify(options.key, options.url);
|
|
466
569
|
if (verified) {
|
|
467
|
-
println(chalk.green(
|
|
570
|
+
println(chalk.green("OK"));
|
|
468
571
|
}
|
|
469
572
|
else {
|
|
470
|
-
println(chalk.yellow(
|
|
471
|
-
println(chalk.gray(
|
|
573
|
+
println(chalk.yellow("SKIPPED (server not reachable)"));
|
|
574
|
+
println(chalk.gray(" The config was saved. Connection will work when the server is available."));
|
|
472
575
|
}
|
|
473
576
|
println();
|
|
474
|
-
println(chalk.green.bold(
|
|
577
|
+
println(chalk.green.bold(" Done! Restart your IDE to activate the MCP server."));
|
|
475
578
|
println();
|
|
476
579
|
}
|
|
477
580
|
catch (error) {
|
|
478
581
|
eprintln();
|
|
479
|
-
eprintln(chalk.red(
|
|
582
|
+
eprintln(chalk.red("Install failed:") +
|
|
583
|
+
" " +
|
|
584
|
+
(error instanceof Error ? error.message : String(error)));
|
|
480
585
|
process.exit(1);
|
|
481
586
|
}
|
|
482
587
|
});
|
|
@@ -485,15 +590,15 @@ program
|
|
|
485
590
|
* The platform runs on port 3700, MCP server on 3701.
|
|
486
591
|
*/
|
|
487
592
|
function resolveMcpServerUrl(platformUrl) {
|
|
488
|
-
if (platformUrl.includes(
|
|
489
|
-
return platformUrl.replace(
|
|
593
|
+
if (platformUrl.includes(":3700")) {
|
|
594
|
+
return platformUrl.replace(":3700", ":3701");
|
|
490
595
|
}
|
|
491
|
-
if (platformUrl.includes(
|
|
492
|
-
return
|
|
596
|
+
if (platformUrl.includes("localhost") && !platformUrl.includes(":")) {
|
|
597
|
+
return "http://localhost:3701";
|
|
493
598
|
}
|
|
494
599
|
// Production: use dedicated MCP subdomain
|
|
495
|
-
if (platformUrl.includes(
|
|
496
|
-
return process.env.MCP_SERVER_URL ||
|
|
600
|
+
if (platformUrl.includes("tostudy")) {
|
|
601
|
+
return process.env.MCP_SERVER_URL || "https://mcp.tostudy.ai";
|
|
497
602
|
}
|
|
498
603
|
return platformUrl;
|
|
499
604
|
}
|