@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/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 'commander';
14
- import chalk from 'chalk';
15
- import { getClaudeConfigPath, isClaudeInstalled, addTostudyMcpServer, removeTostudyMcpServer, isTostudyMcpConfigured, } from './config.js';
16
- import { promptApiKey, confirm } from './prompts.js';
17
- import { getInstalledIDEs } from './detect.js';
18
- import { runDiagnostics, printDiagnosticReport } from './diagnose.js';
19
- import { repairAllIssues, printRepairReport } from './repair.js';
20
- import { getIDEHandler } from './ide-handlers/index.js';
21
- const VERSION = '1.0.0';
22
- const DEFAULT_PLATFORM_URL = 'https://tostudy.com';
23
- function println(message = '') {
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('') + chalk.white.bold(' Catalyst MCP Setup ') + 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: 'POST',
48
+ method: "POST",
46
49
  headers: {
47
- 'Authorization': `Bearer ${apiKey}`,
48
- 'Content-Type': 'application/json',
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('! Nao foi possivel validar a API key (servidor offline?)'));
57
- println(chalk.yellow(' A configuracao sera salva mesmo assim.'));
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
- // Check if Claude Code is installed
67
- if (!isClaudeInstalled()) {
68
- println(chalk.red('X Claude Code nao encontrado.'));
69
- println();
70
- println(' Por favor, instale o Claude Code primeiro:');
71
- println(chalk.cyan(' https://claude.ai/download'));
72
- println();
73
- process.exit(1);
74
- }
75
- const configPath = getClaudeConfigPath();
76
- println(chalk.gray(`Config: ${configPath}`));
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
- // Get API key
89
- const key = apiKey || process.env.TOSTUDY_API_KEY || await promptApiKey();
90
- const url = platformUrl || process.env.TOSTUDY_PLATFORM_URL || DEFAULT_PLATFORM_URL;
91
- // Validate API key
92
- println();
93
- process.stdout.write(chalk.gray('Validando API key... '));
94
- const valid = await validateApiKey(key, url);
95
- if (!valid) {
96
- println(chalk.red('FALHOU'));
97
- println();
98
- println(chalk.red('API key invalida ou expirada.'));
99
- println();
100
- println('Para gerar uma nova API key:');
101
- println(chalk.cyan(` ${url}/student/settings/mcp`));
102
- println();
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
- println(chalk.green('OK'));
106
- // Configure Claude Code
107
- process.stdout.write(chalk.gray('Configurando Claude Code... '));
180
+ // Exchange code for JWT
181
+ process.stdout.write(chalk.gray(" Autenticando... "));
182
+ let token;
183
+ let userName;
108
184
  try {
109
- addTostudyMcpServer(key, url);
110
- println(chalk.green('OK'));
111
- }
112
- catch (error) {
113
- println(chalk.red('FALHOU'));
114
- eprintln();
115
- eprintln(chalk.red('Erro ao salvar configuracao:'));
116
- eprintln(error instanceof Error ? error.message : String(error));
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
- // Success
120
- println();
121
- println(chalk.green.bold('Configuracao concluida!'));
122
- println();
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('Catalyst MCP nao esta configurado.'));
207
+ println(chalk.yellow("Catalyst MCP nao esta configurado."));
136
208
  process.exit(0);
137
209
  }
138
- const shouldUninstall = await confirm('Remover configuracao do Catalyst MCP?', false);
210
+ const shouldUninstall = await confirm("Remover configuracao do Catalyst MCP?", false);
139
211
  if (!shouldUninstall) {
140
- println(chalk.gray('Operacao cancelada.'));
212
+ println(chalk.gray("Operacao cancelada."));
141
213
  process.exit(0);
142
214
  }
143
- process.stdout.write(chalk.gray('Removendo configuracao... '));
215
+ process.stdout.write(chalk.gray("Removendo configuracao... "));
144
216
  try {
145
217
  removeTostudyMcpServer();
146
- println(chalk.green('OK'));
218
+ println(chalk.green("OK"));
147
219
  println();
148
- println(chalk.green('Configuracao removida com sucesso.'));
149
- println(chalk.gray('Reinicie o Claude Code para aplicar as mudancas.'));
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('FALHOU'));
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(' Nenhuma IDE suportada detectada.'));
172
- println(chalk.gray(' O setup continuara para Claude Code.'));
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(' IDEs detectadas:'));
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('')} ${ide.name}${version}`);
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('') + chalk.white.bold(' Catalyst MCP Setup Wizard ') + chalk.cyan('║'));
192
- println(chalk.cyan(' ║') + chalk.gray(' Configuracao guiada passo a passo ') + chalk.cyan('║'));
193
- println(chalk.cyan(' ╚═══════════════════════════════════════════════╝'));
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(' Este assistente vai configurar o Claude Code para'));
196
- println(chalk.gray(' conectar ao servidor MCP da plataforma Catalyst.'));
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, 'Detectando ambiente');
273
+ printStep(1, TOTAL_STEPS, "Detectando ambiente");
200
274
  // Check Claude Code
201
- process.stdout.write(' Verificando Claude Code... ');
275
+ process.stdout.write(" Verificando Claude Code... ");
202
276
  if (!isClaudeInstalled()) {
203
- println(chalk.red('NAO ENCONTRADO'));
277
+ println(chalk.red("NAO ENCONTRADO"));
204
278
  println();
205
- println(chalk.red(' Claude Code nao esta instalado.'));
279
+ println(chalk.red(" Claude Code nao esta instalado."));
206
280
  println();
207
- println(' Por favor, instale primeiro:');
208
- println(chalk.cyan(' https://claude.ai/download'));
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('OK'));
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(' Detectando IDEs... ');
290
+ process.stdout.write(" Detectando IDEs... ");
217
291
  const installedIDEs = getInstalledIDEs();
218
- println(chalk.green('OK'));
292
+ println(chalk.green("OK"));
219
293
  printIDEDetection(installedIDEs);
220
294
  // Check existing configuration
221
- process.stdout.write(' Verificando configuracao atual... ');
295
+ process.stdout.write(" Verificando configuracao atual... ");
222
296
  const alreadyConfigured = isTostudyMcpConfigured();
223
297
  if (alreadyConfigured) {
224
- println(chalk.yellow('JA CONFIGURADO'));
298
+ println(chalk.yellow("JA CONFIGURADO"));
225
299
  println();
226
- const overwrite = await confirm(' Deseja reconfigurar?', false);
300
+ const overwrite = await confirm(" Deseja reconfigurar?", false);
227
301
  if (!overwrite) {
228
302
  println();
229
- println(chalk.gray(' Operacao cancelada.'));
303
+ println(chalk.gray(" Operacao cancelada."));
230
304
  process.exit(0);
231
305
  }
232
306
  }
233
307
  else {
234
- println(chalk.gray('Nao configurado'));
308
+ println(chalk.gray("Nao configurado"));
235
309
  }
236
310
  // ━━━ Step 2: API Key Configuration ━━━
237
- printStep(2, TOTAL_STEPS, 'Configurando API Key');
238
- println(chalk.gray(' A API key conecta o Claude Code a sua conta na plataforma.'));
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(' Acesse sua API key em:');
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(' ✓ API key fornecida via parametro ou ambiente'));
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(' Validando API key... '));
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('FALHOU'));
330
+ println(chalk.red("FALHOU"));
257
331
  println();
258
- println(chalk.red(' API key invalida ou expirada.'));
332
+ println(chalk.red(" API key invalida ou expirada."));
259
333
  println();
260
- println(' Para gerar uma nova API key:');
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('OK'));
339
+ println(chalk.green("OK"));
266
340
  // ━━━ Step 3: Save Configuration ━━━
267
- printStep(3, TOTAL_STEPS, 'Salvando configuracao');
268
- process.stdout.write(' Configurando Claude Code... ');
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('OK'));
345
+ println(chalk.green("OK"));
272
346
  }
273
347
  catch (error) {
274
- println(chalk.red('FALHOU'));
348
+ println(chalk.red("FALHOU"));
275
349
  eprintln();
276
- eprintln(chalk.red(' Erro ao salvar configuracao:'));
277
- eprintln(' ' + (error instanceof Error ? error.message : String(error)));
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, 'Verificacao final');
355
+ printStep(4, TOTAL_STEPS, "Verificacao final");
282
356
  if (!options.skipDiagnostics) {
283
- process.stdout.write(' Executando diagnostico... ');
357
+ process.stdout.write(" Executando diagnostico... ");
284
358
  const report = await runDiagnostics();
285
- println(chalk.green('OK'));
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) => !['mcp-not-configured', 'config-missing'].includes(issue.id));
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(' Avisos encontrados:'));
364
+ println(chalk.yellow(" Avisos encontrados:"));
291
365
  for (const issue of remainingIssues) {
292
- const icon = issue.severity === 'critical' ? chalk.red('●') :
293
- issue.severity === 'warning' ? chalk.yellow('') : chalk.blue('●');
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(' Tentando reparar automaticamente... ');
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('Nenhum reparo aplicado'));
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(' ✓ Nenhum problema encontrado!'));
390
+ println(chalk.green(" ✓ Nenhum problema encontrado!"));
314
391
  }
315
392
  }
316
393
  else {
317
- println(chalk.gray(' Diagnostico ignorado (--skip-diagnostics)'));
394
+ println(chalk.gray(" Diagnostico ignorado (--skip-diagnostics)"));
318
395
  }
319
396
  // ━━━ Success ━━━
320
397
  println();
321
- println(chalk.green(' ╔═══════════════════════════════════════════════╗'));
322
- println(chalk.green('') + chalk.white.bold(' Configuracao concluida com sucesso! ') + chalk.green('║'));
323
- println(chalk.green(' ╚═══════════════════════════════════════════════╝'));
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.white(' Proximos passos:'));
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(' 1.') + ' Reinicie o Claude Code');
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.gray(' Comandos uteis:'));
332
- println(chalk.gray(' ') + chalk.cyan('npx @tostudy-ai/mcp-setup diagnose') + chalk.gray(' - Verificar problemas'));
333
- println(chalk.gray(' ') + chalk.cyan('npx @tostudy-ai/mcp-setup repair') + chalk.gray(' - Reparar automaticamente'));
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('tostudy-mcp-setup')
339
- .description('Configura o Claude Code para usar o Catalyst MCP server')
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('-k, --api-key <key>', 'API key da plataforma Catalyst')
344
- .option('-u, --url <url>', 'URL da plataforma (default: https://tostudy.com)')
345
- .option('--uninstall', 'Remove a configuracao do Catalyst MCP')
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('Erro:') + ' ' + (error instanceof Error ? error.message : String(error)));
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('wizard')
364
- .description('Assistente interativo de configuracao passo a passo')
365
- .option('-k, --api-key <key>', 'API key da plataforma Catalyst')
366
- .option('-u, --url <url>', 'URL da plataforma (default: https://tostudy.com)')
367
- .option('--skip-diagnostics', 'Pula a verificacao de diagnostico apos setup')
368
- .option('--auto-repair', 'Tenta reparar problemas automaticamente')
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('Erro:') + ' ' + (error instanceof Error ? error.message : String(error)));
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('diagnose')
387
- .description('Executa diagnostico de problemas na configuracao MCP')
388
- .option('--json', 'Saida em formato JSON')
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('Erro ao executar diagnostico:'));
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('repair')
410
- .description('Repara problemas de configuracao automaticamente')
411
- .option('-k, --api-key <key>', 'API key para reparos que necessitam autenticacao')
412
- .option('-u, --url <url>', 'URL da plataforma (default: https://tostudy.com)')
413
- .option('--json', 'Saida em formato JSON')
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 === 'critical' && !repair.results.find((r) => r.issueId === issue.id && r.success));
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('Erro ao executar reparo:'));
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 = ['claude-code', 'cursor', 'vscode', 'desktop', 'windsurf', 'opencode', 'codex', 'antigravity', 'manual'];
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('install')
440
- .description('Install MCP config for a specific IDE (used by the web setup wizard)')
441
- .requiredOption('--ide <ide>', `Target IDE: ${SUPPORTED_IDES.join(', ')}`)
442
- .requiredOption('--key <key>', 'API key from the platform')
443
- .option('--url <url>', 'Platform URL (default: https://tostudy.com)', DEFAULT_PLATFORM_URL)
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(' Writing config... '));
560
+ process.stdout.write(chalk.gray(" Writing config... "));
458
561
  await handler.writeConfig(options.key, mcpUrl);
459
- println(chalk.green('OK'));
460
- if (ide !== 'manual') {
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(' Verifying connection... '));
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('OK'));
570
+ println(chalk.green("OK"));
468
571
  }
469
572
  else {
470
- println(chalk.yellow('SKIPPED (server not reachable)'));
471
- println(chalk.gray(' The config was saved. Connection will work when the server is available.'));
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(' Done! Restart your IDE to activate the MCP server.'));
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('Install failed:') + ' ' + (error instanceof Error ? error.message : String(error)));
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(':3700')) {
489
- return platformUrl.replace(':3700', ':3701');
593
+ if (platformUrl.includes(":3700")) {
594
+ return platformUrl.replace(":3700", ":3701");
490
595
  }
491
- if (platformUrl.includes('localhost') && !platformUrl.includes(':')) {
492
- return 'http://localhost:3701';
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('tostudy')) {
496
- return process.env.MCP_SERVER_URL || 'https://mcp.tostudy.ai';
600
+ if (platformUrl.includes("tostudy")) {
601
+ return process.env.MCP_SERVER_URL || "https://mcp.tostudy.ai";
497
602
  }
498
603
  return platformUrl;
499
604
  }