@tostudy-ai/mcp-setup 1.3.0 → 1.4.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/README.md +26 -25
- package/dist/index.d.ts +2 -14
- package/dist/index.js +2983 -613
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/dist/__tests__/e2e-diagnostic-repair-flow.test.d.ts +0 -52
- package/dist/__tests__/e2e-diagnostic-repair-flow.test.d.ts.map +0 -1
- package/dist/__tests__/e2e-diagnostic-repair-flow.test.js +0 -720
- package/dist/__tests__/e2e-diagnostic-repair-flow.test.js.map +0 -1
- package/dist/__tests__/e2e-wizard-flow.test.d.ts +0 -43
- package/dist/__tests__/e2e-wizard-flow.test.d.ts.map +0 -1
- package/dist/__tests__/e2e-wizard-flow.test.js +0 -418
- package/dist/__tests__/e2e-wizard-flow.test.js.map +0 -1
- package/dist/__tests__/ide-handlers.test.d.ts +0 -10
- package/dist/__tests__/ide-handlers.test.d.ts.map +0 -1
- package/dist/__tests__/ide-handlers.test.js +0 -358
- package/dist/__tests__/ide-handlers.test.js.map +0 -1
- package/dist/__tests__/install-command.test.d.ts +0 -10
- package/dist/__tests__/install-command.test.d.ts.map +0 -1
- package/dist/__tests__/install-command.test.js +0 -248
- package/dist/__tests__/install-command.test.js.map +0 -1
- package/dist/callback-page.d.ts +0 -6
- package/dist/callback-page.d.ts.map +0 -1
- package/dist/callback-page.js +0 -96
- package/dist/callback-page.js.map +0 -1
- package/dist/config.d.ts +0 -62
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -167
- package/dist/config.js.map +0 -1
- package/dist/detect.d.ts +0 -42
- package/dist/detect.d.ts.map +0 -1
- package/dist/detect.js +0 -277
- package/dist/detect.js.map +0 -1
- package/dist/diagnose.d.ts +0 -36
- package/dist/diagnose.d.ts.map +0 -1
- package/dist/diagnose.js +0 -502
- package/dist/diagnose.js.map +0 -1
- package/dist/ide-handlers/antigravity.d.ts +0 -16
- package/dist/ide-handlers/antigravity.d.ts.map +0 -1
- package/dist/ide-handlers/antigravity.js +0 -46
- package/dist/ide-handlers/antigravity.js.map +0 -1
- package/dist/ide-handlers/base.d.ts +0 -36
- package/dist/ide-handlers/base.d.ts.map +0 -1
- package/dist/ide-handlers/base.js +0 -66
- package/dist/ide-handlers/base.js.map +0 -1
- package/dist/ide-handlers/claude-code.d.ts +0 -15
- package/dist/ide-handlers/claude-code.d.ts.map +0 -1
- package/dist/ide-handlers/claude-code.js +0 -50
- package/dist/ide-handlers/claude-code.js.map +0 -1
- package/dist/ide-handlers/codex.d.ts +0 -15
- package/dist/ide-handlers/codex.d.ts.map +0 -1
- package/dist/ide-handlers/codex.js +0 -53
- package/dist/ide-handlers/codex.js.map +0 -1
- package/dist/ide-handlers/cursor.d.ts +0 -15
- package/dist/ide-handlers/cursor.d.ts.map +0 -1
- package/dist/ide-handlers/cursor.js +0 -61
- package/dist/ide-handlers/cursor.js.map +0 -1
- package/dist/ide-handlers/desktop.d.ts +0 -16
- package/dist/ide-handlers/desktop.d.ts.map +0 -1
- package/dist/ide-handlers/desktop.js +0 -50
- package/dist/ide-handlers/desktop.js.map +0 -1
- package/dist/ide-handlers/index.d.ts +0 -21
- package/dist/ide-handlers/index.d.ts.map +0 -1
- package/dist/ide-handlers/index.js +0 -55
- package/dist/ide-handlers/index.js.map +0 -1
- package/dist/ide-handlers/manual.d.ts +0 -16
- package/dist/ide-handlers/manual.d.ts.map +0 -1
- package/dist/ide-handlers/manual.js +0 -34
- package/dist/ide-handlers/manual.js.map +0 -1
- package/dist/ide-handlers/opencode.d.ts +0 -15
- package/dist/ide-handlers/opencode.d.ts.map +0 -1
- package/dist/ide-handlers/opencode.js +0 -57
- package/dist/ide-handlers/opencode.js.map +0 -1
- package/dist/ide-handlers/vscode.d.ts +0 -16
- package/dist/ide-handlers/vscode.d.ts.map +0 -1
- package/dist/ide-handlers/vscode.js +0 -62
- package/dist/ide-handlers/vscode.js.map +0 -1
- package/dist/ide-handlers/windsurf.d.ts +0 -16
- package/dist/ide-handlers/windsurf.d.ts.map +0 -1
- package/dist/ide-handlers/windsurf.js +0 -46
- package/dist/ide-handlers/windsurf.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/oauth-server.d.ts +0 -4
- package/dist/oauth-server.d.ts.map +0 -1
- package/dist/oauth-server.js +0 -49
- package/dist/oauth-server.js.map +0 -1
- package/dist/prompts.d.ts +0 -23
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js +0 -68
- package/dist/prompts.js.map +0 -1
- package/dist/repair.d.ts +0 -50
- package/dist/repair.d.ts.map +0 -1
- package/dist/repair.js +0 -633
- package/dist/repair.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,664 +1,3034 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* ToStudy MCP Setup CLI
|
|
4
|
-
*
|
|
5
|
-
* Configures Claude Code to connect to the ToStudy MCP server.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npx @tostudy-ai/mcp-setup
|
|
9
|
-
* npx @tostudy-ai/mcp-setup wizard
|
|
10
|
-
* npx @tostudy-ai/mcp-setup --api-key <key>
|
|
11
|
-
* npx @tostudy-ai/mcp-setup --uninstall
|
|
12
|
-
*/
|
|
1
|
+
// src/index.ts
|
|
13
2
|
import { program } from "commander";
|
|
14
|
-
import
|
|
15
|
-
import { execFile } from "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
3
|
+
import chalk6 from "chalk";
|
|
4
|
+
import { execFile } from "child_process";
|
|
5
|
+
|
|
6
|
+
// src/config.ts
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { homedir, platform } from "os";
|
|
10
|
+
function extractApiKey(config) {
|
|
11
|
+
const authHeader = config.headers?.["Authorization"];
|
|
12
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
13
|
+
return authHeader.replace("Bearer ", "");
|
|
14
|
+
}
|
|
15
|
+
if (config.args) {
|
|
16
|
+
for (let i = 0; i < config.args.length; i++) {
|
|
17
|
+
if (config.args[i] === "--header" && config.args[i + 1]?.startsWith("Authorization:Bearer ")) {
|
|
18
|
+
return config.args[i + 1].replace("Authorization:Bearer ", "");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function extractMcpUrl(config) {
|
|
25
|
+
if (config.url) {
|
|
26
|
+
return config.url;
|
|
27
|
+
}
|
|
28
|
+
if (config.args && config.args[0] === "-y" && config.args[1] === "mcp-remote" && config.args[2]) {
|
|
29
|
+
return config.args[2];
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function getClientOsHeaderValue() {
|
|
34
|
+
switch (platform()) {
|
|
35
|
+
case "darwin":
|
|
36
|
+
return "macos";
|
|
37
|
+
case "win32":
|
|
38
|
+
return "windows";
|
|
39
|
+
default:
|
|
40
|
+
return "linux";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function getClaudeConfigPath() {
|
|
44
|
+
const home = homedir();
|
|
45
|
+
const os = platform();
|
|
46
|
+
switch (os) {
|
|
47
|
+
case "darwin":
|
|
48
|
+
return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
49
|
+
case "win32":
|
|
50
|
+
return join(
|
|
51
|
+
process.env.APPDATA || join(home, "AppData", "Roaming"),
|
|
52
|
+
"Claude",
|
|
53
|
+
"claude_desktop_config.json"
|
|
54
|
+
);
|
|
55
|
+
case "linux":
|
|
56
|
+
return join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
57
|
+
default:
|
|
58
|
+
throw new Error(`Unsupported operating system: ${os}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isClaudeInstalled() {
|
|
62
|
+
const configPath = getClaudeConfigPath();
|
|
63
|
+
const configDir = dirname(configPath);
|
|
64
|
+
return existsSync(configDir);
|
|
65
|
+
}
|
|
66
|
+
function readClaudeConfig() {
|
|
67
|
+
const configPath = getClaudeConfigPath();
|
|
68
|
+
if (!existsSync(configPath)) {
|
|
69
|
+
return { mcpServers: {} };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const content = readFileSync(configPath, "utf-8");
|
|
73
|
+
return JSON.parse(content);
|
|
74
|
+
} catch {
|
|
75
|
+
return { mcpServers: {} };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function writeClaudeConfig(config) {
|
|
79
|
+
const configPath = getClaudeConfigPath();
|
|
80
|
+
const configDir = dirname(configPath);
|
|
81
|
+
if (!existsSync(configDir)) {
|
|
82
|
+
mkdirSync(configDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
if (existsSync(configPath)) {
|
|
85
|
+
const backupPath = `${configPath}.backup`;
|
|
86
|
+
copyFileSync(configPath, backupPath);
|
|
87
|
+
}
|
|
88
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
89
|
+
}
|
|
90
|
+
function addTostudyMcpServer(apiKey, platformUrl, clientInfo = {}) {
|
|
91
|
+
const config = readClaudeConfig();
|
|
92
|
+
if (!config.mcpServers) {
|
|
93
|
+
config.mcpServers = {};
|
|
94
|
+
}
|
|
95
|
+
const ide = clientInfo.ide || "desktop";
|
|
96
|
+
const os = clientInfo.os || getClientOsHeaderValue();
|
|
97
|
+
config.mcpServers["tostudy"] = {
|
|
98
|
+
command: "npx",
|
|
99
|
+
args: [
|
|
100
|
+
"-y",
|
|
101
|
+
"mcp-remote",
|
|
102
|
+
`${platformUrl}/mcp`,
|
|
103
|
+
"--header",
|
|
104
|
+
`Authorization:Bearer ${apiKey}`,
|
|
105
|
+
"--header",
|
|
106
|
+
`X-Tostudy-Client-IDE:${ide}`,
|
|
107
|
+
"--header",
|
|
108
|
+
`X-Tostudy-Client-OS:${os}`
|
|
109
|
+
]
|
|
110
|
+
};
|
|
111
|
+
writeClaudeConfig(config);
|
|
112
|
+
}
|
|
113
|
+
function removeTostudyMcpServer() {
|
|
114
|
+
const config = readClaudeConfig();
|
|
115
|
+
if (config.mcpServers && config.mcpServers["tostudy"]) {
|
|
116
|
+
delete config.mcpServers["tostudy"];
|
|
117
|
+
writeClaudeConfig(config);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isTostudyMcpConfigured() {
|
|
121
|
+
const config = readClaudeConfig();
|
|
122
|
+
return !!(config.mcpServers && config.mcpServers["tostudy"]);
|
|
123
|
+
}
|
|
124
|
+
function getTostudyMcpConfig() {
|
|
125
|
+
const config = readClaudeConfig();
|
|
126
|
+
return config.mcpServers?.["tostudy"] || null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/prompts.ts
|
|
130
|
+
import * as readline from "readline";
|
|
26
131
|
function println(message = "") {
|
|
27
|
-
|
|
132
|
+
process.stdout.write(`${message}
|
|
133
|
+
`);
|
|
28
134
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
135
|
+
function createInterface2() {
|
|
136
|
+
return readline.createInterface({
|
|
137
|
+
input: process.stdin,
|
|
138
|
+
output: process.stdout
|
|
139
|
+
});
|
|
31
140
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
141
|
+
function prompt(question) {
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
const rl = createInterface2();
|
|
144
|
+
rl.question(question, (answer) => {
|
|
145
|
+
rl.close();
|
|
146
|
+
resolve(answer.trim());
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function promptApiKey() {
|
|
151
|
+
println();
|
|
152
|
+
println("Cole sua API key (de /student/settings/mcp):");
|
|
153
|
+
const apiKey = await prompt("> ");
|
|
154
|
+
if (!apiKey) {
|
|
155
|
+
throw new Error("API key n\xE3o pode ser vazia");
|
|
156
|
+
}
|
|
157
|
+
if (apiKey.length < 32) {
|
|
158
|
+
throw new Error("API key parece ser muito curta. Verifique e tente novamente.");
|
|
159
|
+
}
|
|
160
|
+
return apiKey;
|
|
161
|
+
}
|
|
162
|
+
async function confirm(question, defaultValue = false) {
|
|
163
|
+
const defaultStr = defaultValue ? "[S/n]" : "[s/N]";
|
|
164
|
+
const answer = await prompt(`${question} ${defaultStr} `);
|
|
165
|
+
if (!answer) {
|
|
166
|
+
return defaultValue;
|
|
167
|
+
}
|
|
168
|
+
return answer.toLowerCase().startsWith("s") || answer.toLowerCase().startsWith("y");
|
|
169
|
+
}
|
|
170
|
+
async function promptCourseSelection(courses) {
|
|
171
|
+
if (courses.length === 0) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
println();
|
|
175
|
+
println("Cursos ativos:");
|
|
176
|
+
courses.forEach((course, index2) => {
|
|
177
|
+
println(` ${index2 + 1}. ${course.title} \u2014 ${course.progressPercent}% completo`);
|
|
178
|
+
});
|
|
179
|
+
const answer = await prompt(`Selecione um curso [1-${courses.length}] (default: 1): `);
|
|
180
|
+
if (!answer) {
|
|
181
|
+
return courses[0]?.id ?? null;
|
|
182
|
+
}
|
|
183
|
+
const index = Number.parseInt(answer, 10);
|
|
184
|
+
if (!Number.isInteger(index) || index < 1 || index > courses.length) {
|
|
185
|
+
println("Sele\xE7\xE3o inv\xE1lida.");
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return courses[index - 1]?.id ?? null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/detect.ts
|
|
192
|
+
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
193
|
+
import { join as join2 } from "path";
|
|
194
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
195
|
+
function getVSCodeConfigDir(variant) {
|
|
196
|
+
const home = homedir2();
|
|
197
|
+
const os = platform2();
|
|
198
|
+
const folderNames = {
|
|
199
|
+
stable: "Code",
|
|
200
|
+
insiders: "Code - Insiders",
|
|
201
|
+
cursor: "Cursor"
|
|
202
|
+
};
|
|
203
|
+
const folder = folderNames[variant];
|
|
204
|
+
switch (os) {
|
|
205
|
+
case "darwin":
|
|
206
|
+
return join2(home, "Library", "Application Support", folder, "User");
|
|
207
|
+
case "win32":
|
|
208
|
+
return join2(process.env.APPDATA || join2(home, "AppData", "Roaming"), folder, "User");
|
|
209
|
+
case "linux":
|
|
210
|
+
return join2(home, ".config", folder, "User");
|
|
211
|
+
default:
|
|
212
|
+
return "";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function getVSCodeMcpConfigPath(variant) {
|
|
216
|
+
const configDir = getVSCodeConfigDir(variant);
|
|
217
|
+
if (!configDir) return "";
|
|
218
|
+
return join2(configDir, "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
219
|
+
}
|
|
220
|
+
function isVSCodeInstalled(variant) {
|
|
221
|
+
const os = platform2();
|
|
222
|
+
const home = homedir2();
|
|
223
|
+
const appNames = {
|
|
224
|
+
stable: ["Visual Studio Code.app", "code"],
|
|
225
|
+
insiders: ["Visual Studio Code - Insiders.app", "code-insiders"],
|
|
226
|
+
cursor: ["Cursor.app", "cursor"]
|
|
227
|
+
};
|
|
228
|
+
const [macApp, linuxCmd] = appNames[variant];
|
|
229
|
+
switch (os) {
|
|
230
|
+
case "darwin":
|
|
231
|
+
return existsSync2(join2("/Applications", macApp)) || existsSync2(join2(home, "Applications", macApp));
|
|
232
|
+
case "win32": {
|
|
233
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
234
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
235
|
+
const localAppData = process.env.LOCALAPPDATA || join2(home, "AppData", "Local");
|
|
236
|
+
const winPaths = variant === "cursor" ? [join2(localAppData, "Programs", "cursor", "Cursor.exe")] : [
|
|
237
|
+
join2(programFiles, "Microsoft VS Code", "Code.exe"),
|
|
238
|
+
join2(programFilesX86, "Microsoft VS Code", "Code.exe"),
|
|
239
|
+
join2(localAppData, "Programs", "Microsoft VS Code", "Code.exe")
|
|
240
|
+
];
|
|
241
|
+
if (variant === "insiders") {
|
|
242
|
+
winPaths.push(
|
|
243
|
+
join2(programFiles, "Microsoft VS Code Insiders", "Code - Insiders.exe"),
|
|
244
|
+
join2(localAppData, "Programs", "Microsoft VS Code Insiders", "Code - Insiders.exe")
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return winPaths.some((p) => existsSync2(p));
|
|
62
248
|
}
|
|
249
|
+
case "linux":
|
|
250
|
+
return existsSync2(getVSCodeConfigDir(variant)) || existsSync2(join2("/usr/bin", linuxCmd)) || existsSync2(join2("/usr/local/bin", linuxCmd)) || existsSync2(join2(home, ".local", "bin", linuxCmd));
|
|
251
|
+
default:
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
63
254
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
255
|
+
function getJetBrainsConfigDir() {
|
|
256
|
+
const home = homedir2();
|
|
257
|
+
const os = platform2();
|
|
258
|
+
switch (os) {
|
|
259
|
+
case "darwin":
|
|
260
|
+
return join2(home, "Library", "Application Support", "JetBrains");
|
|
261
|
+
case "win32":
|
|
262
|
+
return join2(process.env.APPDATA || join2(home, "AppData", "Roaming"), "JetBrains");
|
|
263
|
+
case "linux":
|
|
264
|
+
return join2(home, ".config", "JetBrains");
|
|
265
|
+
default:
|
|
266
|
+
return "";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function getInstalledJetBrainsIDEs() {
|
|
270
|
+
const configDir = getJetBrainsConfigDir();
|
|
271
|
+
if (!configDir || !existsSync2(configDir)) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const entries = readdirSync(configDir, { withFileTypes: true });
|
|
276
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => {
|
|
277
|
+
return /^(IntelliJIdea|WebStorm|PyCharm|PhpStorm|GoLand|Rider|CLion|RubyMine|DataGrip|Fleet)\d{4}\.\d/i.test(name);
|
|
72
278
|
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
279
|
+
} catch {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function isJetBrainsInstalled() {
|
|
284
|
+
const installedIDEs = getInstalledJetBrainsIDEs();
|
|
285
|
+
return installedIDEs.length > 0;
|
|
286
|
+
}
|
|
287
|
+
function getPrimaryJetBrainsIDE() {
|
|
288
|
+
const installedIDEs = getInstalledJetBrainsIDEs();
|
|
289
|
+
if (installedIDEs.length === 0) return null;
|
|
290
|
+
const sorted = installedIDEs.sort((a, b) => {
|
|
291
|
+
const versionA = a.match(/\d{4}\.\d/)?.[0] || "0";
|
|
292
|
+
const versionB = b.match(/\d{4}\.\d/)?.[0] || "0";
|
|
293
|
+
return versionB.localeCompare(versionA);
|
|
294
|
+
});
|
|
295
|
+
return sorted[0];
|
|
296
|
+
}
|
|
297
|
+
function getJetBrainsMcpConfigPath(ideDir) {
|
|
298
|
+
const configDir = getJetBrainsConfigDir();
|
|
299
|
+
if (!configDir) return "";
|
|
300
|
+
const targetDir = ideDir || getPrimaryJetBrainsIDE();
|
|
301
|
+
if (!targetDir) return "";
|
|
302
|
+
return join2(configDir, targetDir, "options", "mcp-servers.json");
|
|
303
|
+
}
|
|
304
|
+
function detectVSCode() {
|
|
305
|
+
const installed = isVSCodeInstalled("stable");
|
|
306
|
+
return {
|
|
307
|
+
type: "vscode",
|
|
308
|
+
name: "Visual Studio Code",
|
|
309
|
+
configPath: getVSCodeMcpConfigPath("stable"),
|
|
310
|
+
installed
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function detectVSCodeInsiders() {
|
|
314
|
+
const installed = isVSCodeInstalled("insiders");
|
|
315
|
+
return {
|
|
316
|
+
type: "vscode-insiders",
|
|
317
|
+
name: "Visual Studio Code - Insiders",
|
|
318
|
+
configPath: getVSCodeMcpConfigPath("insiders"),
|
|
319
|
+
installed
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function detectCursor() {
|
|
323
|
+
const installed = isVSCodeInstalled("cursor");
|
|
324
|
+
return {
|
|
325
|
+
type: "cursor",
|
|
326
|
+
name: "Cursor",
|
|
327
|
+
configPath: getVSCodeMcpConfigPath("cursor"),
|
|
328
|
+
installed
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function detectJetBrains() {
|
|
332
|
+
const installed = isJetBrainsInstalled();
|
|
333
|
+
const primaryIDE = getPrimaryJetBrainsIDE();
|
|
334
|
+
let name = "JetBrains IDE";
|
|
335
|
+
let version;
|
|
336
|
+
if (primaryIDE) {
|
|
337
|
+
const match = primaryIDE.match(/^(\w+?)(\d{4}\.\d)/);
|
|
338
|
+
if (match) {
|
|
339
|
+
const ideNames = {
|
|
340
|
+
"IntelliJIdea": "IntelliJ IDEA",
|
|
341
|
+
"WebStorm": "WebStorm",
|
|
342
|
+
"PyCharm": "PyCharm",
|
|
343
|
+
"PhpStorm": "PhpStorm",
|
|
344
|
+
"GoLand": "GoLand",
|
|
345
|
+
"Rider": "Rider",
|
|
346
|
+
"CLion": "CLion",
|
|
347
|
+
"RubyMine": "RubyMine",
|
|
348
|
+
"DataGrip": "DataGrip",
|
|
349
|
+
"Fleet": "Fleet"
|
|
350
|
+
};
|
|
351
|
+
name = ideNames[match[1]] || match[1];
|
|
352
|
+
version = match[2];
|
|
76
353
|
}
|
|
77
|
-
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
type: "jetbrains",
|
|
357
|
+
name,
|
|
358
|
+
version,
|
|
359
|
+
configPath: getJetBrainsMcpConfigPath(),
|
|
360
|
+
installed
|
|
361
|
+
};
|
|
78
362
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.split(/[,\s]+/)
|
|
108
|
-
.map((s) => parseInt(s, 10) - 1)
|
|
109
|
-
.filter((i) => i >= 0 && i < installedIDEs.length);
|
|
110
|
-
if (indices.length === 0) {
|
|
111
|
-
println(chalk.yellow(" Nenhuma IDE selecionada."));
|
|
112
|
-
return 0;
|
|
113
|
-
}
|
|
114
|
-
selectedIDEs = indices.map((i) => installedIDEs[i]);
|
|
115
|
-
}
|
|
116
|
-
println();
|
|
117
|
-
// For Claude Code, ask global vs local
|
|
118
|
-
const claudeCodeHandler = selectedIDEs.find((h) => h.id === "claude-code");
|
|
119
|
-
let claudeCodeScope = "user";
|
|
120
|
-
if (claudeCodeHandler) {
|
|
121
|
-
const scopeAnswer = await prompt(" Claude Code: configurar global ou local? (g/l, default: global) ");
|
|
122
|
-
claudeCodeScope = scopeAnswer.trim().toLowerCase().startsWith("l") ? "project" : "user";
|
|
123
|
-
println();
|
|
124
|
-
}
|
|
125
|
-
let configured = 0;
|
|
126
|
-
for (const handler of selectedIDEs) {
|
|
127
|
-
process.stdout.write(` ${chalk.gray("Configurando")} ${handler.name}... `);
|
|
128
|
-
try {
|
|
129
|
-
if (handler.id === "claude-code") {
|
|
130
|
-
await writeClaudeCodeConfig(handler, token, mcpUrl, claudeCodeScope);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
await handler.writeConfig(token, mcpUrl);
|
|
134
|
-
}
|
|
135
|
-
println(chalk.green("OK"));
|
|
136
|
-
configured++;
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
println(chalk.red("FALHOU"));
|
|
140
|
-
eprintln(chalk.gray(` ${error instanceof Error ? error.message : String(error)}`));
|
|
141
|
-
}
|
|
363
|
+
function detectAllIDEs() {
|
|
364
|
+
return [
|
|
365
|
+
detectVSCode(),
|
|
366
|
+
detectVSCodeInsiders(),
|
|
367
|
+
detectCursor(),
|
|
368
|
+
detectJetBrains()
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
function getInstalledIDEs() {
|
|
372
|
+
return detectAllIDEs().filter((ide) => ide.installed);
|
|
373
|
+
}
|
|
374
|
+
if (process.argv[1]?.endsWith("detect.js")) {
|
|
375
|
+
const installedIDEs = getInstalledIDEs();
|
|
376
|
+
if (installedIDEs.length === 0) {
|
|
377
|
+
process.stdout.write("No supported IDEs detected.\n");
|
|
378
|
+
process.stdout.write("\nSupported IDEs:\n");
|
|
379
|
+
process.stdout.write(" - Visual Studio Code\n");
|
|
380
|
+
process.stdout.write(" - Visual Studio Code - Insiders\n");
|
|
381
|
+
process.stdout.write(" - Cursor\n");
|
|
382
|
+
process.stdout.write(" - JetBrains IDEs (IntelliJ, WebStorm, PyCharm, etc.)\n");
|
|
383
|
+
} else {
|
|
384
|
+
process.stdout.write("Detected IDEs:\n");
|
|
385
|
+
for (const ide of installedIDEs) {
|
|
386
|
+
const versionStr = ide.version ? ` (${ide.version})` : "";
|
|
387
|
+
process.stdout.write(` \u2713 ${ide.name}${versionStr}
|
|
388
|
+
`);
|
|
389
|
+
process.stdout.write(` Config: ${ide.configPath || "N/A"}
|
|
390
|
+
`);
|
|
142
391
|
}
|
|
143
|
-
|
|
392
|
+
}
|
|
393
|
+
process.stdout.write("\n--- JSON Output ---\n");
|
|
394
|
+
process.stdout.write(JSON.stringify(installedIDEs, null, 2) + "\n");
|
|
144
395
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
396
|
+
|
|
397
|
+
// src/diagnose.ts
|
|
398
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync } from "fs";
|
|
399
|
+
import { createServer } from "net";
|
|
400
|
+
import chalk from "chalk";
|
|
401
|
+
var DEFAULT_MCP_PORT = 3701;
|
|
402
|
+
function println2(message = "") {
|
|
403
|
+
process.stdout.write(`${message}
|
|
404
|
+
`);
|
|
405
|
+
}
|
|
406
|
+
async function isPortAvailable(port) {
|
|
407
|
+
return new Promise((resolve) => {
|
|
408
|
+
const server = createServer();
|
|
409
|
+
server.once("error", () => {
|
|
410
|
+
resolve(false);
|
|
411
|
+
});
|
|
412
|
+
server.once("listening", () => {
|
|
413
|
+
server.close();
|
|
414
|
+
resolve(true);
|
|
415
|
+
});
|
|
416
|
+
server.listen(port, "127.0.0.1");
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function checkClaudeInstallation() {
|
|
420
|
+
if (!isClaudeInstalled()) {
|
|
421
|
+
return {
|
|
422
|
+
id: "claude-not-installed",
|
|
423
|
+
severity: "critical",
|
|
424
|
+
title: "Claude Code nao encontrado",
|
|
425
|
+
description: "O diretorio de configuracao do Claude Code nao foi encontrado no sistema.",
|
|
426
|
+
suggestion: "Instale o Claude Code em https://claude.ai/download",
|
|
427
|
+
autoFixable: false
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
function checkConfigFile() {
|
|
433
|
+
const configPath = getClaudeConfigPath();
|
|
434
|
+
if (!existsSync3(configPath)) {
|
|
435
|
+
return {
|
|
436
|
+
id: "config-missing",
|
|
437
|
+
severity: "warning",
|
|
438
|
+
title: "Arquivo de configuracao nao existe",
|
|
439
|
+
description: `O arquivo ${configPath} nao foi encontrado. Sera criado durante o setup.`,
|
|
440
|
+
suggestion: "Execute o comando de setup para criar a configuracao.",
|
|
441
|
+
autoFixable: true
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const stats = statSync(configPath);
|
|
446
|
+
if (!stats.isFile()) {
|
|
447
|
+
return {
|
|
448
|
+
id: "config-not-file",
|
|
449
|
+
severity: "critical",
|
|
450
|
+
title: "Configuracao nao e um arquivo",
|
|
451
|
+
description: `${configPath} existe mas nao e um arquivo valido.`,
|
|
452
|
+
suggestion: "Remova o item e execute o setup novamente.",
|
|
453
|
+
autoFixable: false
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
return {
|
|
458
|
+
id: "config-unreadable",
|
|
459
|
+
severity: "critical",
|
|
460
|
+
title: "Arquivo de configuracao inacessivel",
|
|
461
|
+
description: `Nao foi possivel acessar ${configPath}. Verifique as permissoes.`,
|
|
462
|
+
suggestion: "Verifique as permissoes do arquivo ou recrie-o.",
|
|
463
|
+
autoFixable: false
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
function checkConfigJson() {
|
|
469
|
+
const configPath = getClaudeConfigPath();
|
|
470
|
+
if (!existsSync3(configPath)) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
475
|
+
JSON.parse(content);
|
|
476
|
+
} catch {
|
|
477
|
+
return {
|
|
478
|
+
id: "config-invalid-json",
|
|
479
|
+
severity: "critical",
|
|
480
|
+
title: "JSON de configuracao invalido",
|
|
481
|
+
description: "O arquivo de configuracao contem JSON invalido.",
|
|
482
|
+
suggestion: 'Execute "mcp-setup repair" para corrigir ou remova o arquivo e execute o setup novamente.',
|
|
483
|
+
autoFixable: true
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
function checkMcpConfiguration() {
|
|
489
|
+
if (!isTostudyMcpConfigured()) {
|
|
490
|
+
return {
|
|
491
|
+
id: "mcp-not-configured",
|
|
492
|
+
severity: "warning",
|
|
493
|
+
title: "ToStudy MCP nao configurado",
|
|
494
|
+
description: "O servidor MCP da ToStudy nao esta configurado no Claude Code.",
|
|
495
|
+
suggestion: "Execute o comando de setup para configurar o MCP.",
|
|
496
|
+
autoFixable: true
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
function checkApiKeyFormat() {
|
|
502
|
+
const mcpConfig = getTostudyMcpConfig();
|
|
503
|
+
if (!mcpConfig) {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
const apiKey = extractApiKey(mcpConfig);
|
|
507
|
+
if (!apiKey) {
|
|
508
|
+
return {
|
|
509
|
+
id: "api-key-missing",
|
|
510
|
+
severity: "critical",
|
|
511
|
+
title: "API key nao configurada",
|
|
512
|
+
description: "A configuracao do MCP nao possui uma API key.",
|
|
513
|
+
suggestion: "Execute o setup novamente com uma API key valida.",
|
|
514
|
+
autoFixable: true
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (apiKey.length < 32) {
|
|
518
|
+
return {
|
|
519
|
+
id: "api-key-too-short",
|
|
520
|
+
severity: "warning",
|
|
521
|
+
title: "API key parece ser muito curta",
|
|
522
|
+
description: `A API key tem apenas ${apiKey.length} caracteres. API keys validas geralmente tem 32+ caracteres.`,
|
|
523
|
+
suggestion: "Verifique se copiou a API key completa de /student/settings/mcp.",
|
|
524
|
+
autoFixable: true
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
function checkServerUrl() {
|
|
530
|
+
const mcpConfig = getTostudyMcpConfig();
|
|
531
|
+
if (!mcpConfig) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
const url = extractMcpUrl(mcpConfig);
|
|
535
|
+
if (!url) {
|
|
536
|
+
return {
|
|
537
|
+
id: "server-url-missing",
|
|
538
|
+
severity: "critical",
|
|
539
|
+
title: "URL do servidor MCP nao configurada",
|
|
540
|
+
description: "A configuracao do MCP nao possui uma URL de servidor.",
|
|
541
|
+
suggestion: "Execute o setup novamente para configurar a URL correta.",
|
|
542
|
+
autoFixable: true
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const parsedUrl = new URL(url);
|
|
547
|
+
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
548
|
+
return {
|
|
549
|
+
id: "server-url-invalid-protocol",
|
|
550
|
+
severity: "critical",
|
|
551
|
+
title: "Protocolo de URL invalido",
|
|
552
|
+
description: `O protocolo "${parsedUrl.protocol}" nao e suportado. Use http: ou https:.`,
|
|
553
|
+
suggestion: "Execute o setup novamente com a URL correta.",
|
|
554
|
+
autoFixable: true
|
|
555
|
+
};
|
|
154
556
|
}
|
|
155
|
-
|
|
156
|
-
|
|
557
|
+
const validPaths = ["/mcp/sse", "/mcp"];
|
|
558
|
+
if (!validPaths.some((p) => parsedUrl.pathname.endsWith(p))) {
|
|
559
|
+
return {
|
|
560
|
+
id: "server-url-missing-sse-path",
|
|
561
|
+
severity: "warning",
|
|
562
|
+
title: "URL do servidor pode estar incorreta",
|
|
563
|
+
description: `A URL ${url} nao termina com /mcp como esperado.`,
|
|
564
|
+
suggestion: "Verifique se a URL esta correta ou execute o setup novamente.",
|
|
565
|
+
autoFixable: true
|
|
566
|
+
};
|
|
157
567
|
}
|
|
158
|
-
|
|
159
|
-
|
|
568
|
+
} catch {
|
|
569
|
+
return {
|
|
570
|
+
id: "server-url-invalid",
|
|
571
|
+
severity: "critical",
|
|
572
|
+
title: "URL do servidor invalida",
|
|
573
|
+
description: `A URL "${url}" nao e uma URL valida.`,
|
|
574
|
+
suggestion: "Execute o setup novamente com uma URL valida.",
|
|
575
|
+
autoFixable: true
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
async function checkPortConflict() {
|
|
581
|
+
const isAvailable = await isPortAvailable(DEFAULT_MCP_PORT);
|
|
582
|
+
if (!isAvailable) {
|
|
583
|
+
return {
|
|
584
|
+
id: "port-conflict",
|
|
585
|
+
severity: "warning",
|
|
586
|
+
title: `Porta ${DEFAULT_MCP_PORT} em uso`,
|
|
587
|
+
description: `A porta padrao do MCP server (${DEFAULT_MCP_PORT}) esta sendo usada por outro processo.`,
|
|
588
|
+
suggestion: "Verifique qual processo esta usando a porta ou use uma porta alternativa.",
|
|
589
|
+
autoFixable: false
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
async function checkServerConnectivity() {
|
|
595
|
+
const mcpConfig = getTostudyMcpConfig();
|
|
596
|
+
if (!mcpConfig) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
const mcpUrl = extractMcpUrl(mcpConfig);
|
|
600
|
+
const apiKey = extractApiKey(mcpConfig);
|
|
601
|
+
if (!mcpUrl) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
const baseUrl = mcpUrl.replace(/\/mcp(\/sse)?$/, "");
|
|
605
|
+
try {
|
|
606
|
+
const controller = new AbortController();
|
|
607
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
608
|
+
const response = await fetch(`${baseUrl}/api/mcp/heartbeat`, {
|
|
609
|
+
method: "POST",
|
|
610
|
+
headers: {
|
|
611
|
+
Authorization: apiKey ? `Bearer ${apiKey}` : "",
|
|
612
|
+
"Content-Type": "application/json"
|
|
613
|
+
},
|
|
614
|
+
body: JSON.stringify({ timestamp: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
615
|
+
signal: controller.signal
|
|
160
616
|
});
|
|
617
|
+
clearTimeout(timeout);
|
|
618
|
+
if (response.status === 401 || response.status === 403) {
|
|
619
|
+
return {
|
|
620
|
+
id: "auth-failed",
|
|
621
|
+
severity: "critical",
|
|
622
|
+
title: "Falha de autenticacao",
|
|
623
|
+
description: "O servidor MCP rejeitou a API key. Ela pode estar expirada ou invalida.",
|
|
624
|
+
suggestion: "Gere uma nova API key em /student/settings/mcp e execute o setup novamente.",
|
|
625
|
+
autoFixable: true
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
if (!response.ok && response.status !== 204) {
|
|
629
|
+
return {
|
|
630
|
+
id: "server-error",
|
|
631
|
+
severity: "warning",
|
|
632
|
+
title: `Servidor retornou erro ${response.status}`,
|
|
633
|
+
description: `O servidor MCP retornou um status de erro: ${response.status}.`,
|
|
634
|
+
suggestion: "O servidor pode estar temporariamente indisponivel. Tente novamente mais tarde.",
|
|
635
|
+
autoFixable: false
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
} catch (error) {
|
|
639
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
640
|
+
return {
|
|
641
|
+
id: "server-timeout",
|
|
642
|
+
severity: "warning",
|
|
643
|
+
title: "Timeout de conexao",
|
|
644
|
+
description: "O servidor MCP nao respondeu em 5 segundos.",
|
|
645
|
+
suggestion: "Verifique sua conexao de internet ou se o servidor esta online.",
|
|
646
|
+
autoFixable: false
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
id: "server-unreachable",
|
|
651
|
+
severity: "warning",
|
|
652
|
+
title: "Servidor MCP inacessivel",
|
|
653
|
+
description: "Nao foi possivel conectar ao servidor MCP.",
|
|
654
|
+
suggestion: "Verifique sua conexao de internet ou se a URL do servidor esta correta.",
|
|
655
|
+
autoFixable: false
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return null;
|
|
161
659
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const openArgs = process.platform === "win32" ? ["", authUrl] : [authUrl];
|
|
224
|
-
execFile(openCmd, openArgs, (err) => {
|
|
225
|
-
if (err) {
|
|
226
|
-
println(chalk.yellow(" Não foi possível abrir o browser automaticamente."));
|
|
227
|
-
println(chalk.gray(` Abra manualmente: ${authUrl}\n`));
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
const result = await serverPromise;
|
|
231
|
-
code = result.code;
|
|
232
|
-
}
|
|
233
|
-
catch (err) {
|
|
234
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
235
|
-
eprintln(chalk.red(` Erro: ${msg}`));
|
|
236
|
-
process.exit(1);
|
|
237
|
-
}
|
|
238
|
-
// Exchange code for JWT
|
|
239
|
-
process.stdout.write(chalk.gray(" Autenticando... "));
|
|
240
|
-
let token;
|
|
241
|
-
let userName;
|
|
242
|
-
try {
|
|
243
|
-
const result = await exchangeCodeForToken(code, url, "mcp");
|
|
244
|
-
token = result.token;
|
|
245
|
-
userName = result.userName;
|
|
246
|
-
println(chalk.green("OK"));
|
|
247
|
-
println(chalk.green(` Logado como ${userName}\n`));
|
|
248
|
-
}
|
|
249
|
-
catch (err) {
|
|
250
|
-
println(chalk.red("FALHOU"));
|
|
251
|
-
eprintln(chalk.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
252
|
-
process.exit(1);
|
|
253
|
-
}
|
|
254
|
-
// Configure all detected IDEs
|
|
255
|
-
println(chalk.gray(" Detectando IDEs...\n"));
|
|
256
|
-
const configured = await configureIDEs(token, url);
|
|
257
|
-
printSuccessBanner(configured);
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Uninstall flow
|
|
261
|
-
*/
|
|
262
|
-
async function uninstall() {
|
|
263
|
-
printBanner();
|
|
264
|
-
if (!isTostudyMcpConfigured()) {
|
|
265
|
-
println(chalk.yellow("ToStudy MCP nao esta configurado."));
|
|
266
|
-
process.exit(0);
|
|
660
|
+
function checkIDEInstallation() {
|
|
661
|
+
const installedIDEs = getInstalledIDEs();
|
|
662
|
+
if (installedIDEs.length === 0) {
|
|
663
|
+
return {
|
|
664
|
+
id: "no-ide-detected",
|
|
665
|
+
severity: "info",
|
|
666
|
+
title: "Nenhuma IDE suportada detectada",
|
|
667
|
+
description: "Nao foi encontrada nenhuma IDE compativel (VS Code, Cursor, JetBrains).",
|
|
668
|
+
suggestion: "Instale uma IDE suportada para usar a integracao completa.",
|
|
669
|
+
autoFixable: false
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function checkDuplicateServers() {
|
|
675
|
+
const config = readClaudeConfig();
|
|
676
|
+
if (!config.mcpServers) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
const servers = Object.entries(config.mcpServers);
|
|
680
|
+
const anaServers = servers.filter(
|
|
681
|
+
([key, server]) => key.toLowerCase().includes("ana") || key.toLowerCase().includes("catalyst") || server.url && server.url.includes("tostudy.com")
|
|
682
|
+
);
|
|
683
|
+
if (anaServers.length > 1) {
|
|
684
|
+
return {
|
|
685
|
+
id: "duplicate-servers",
|
|
686
|
+
severity: "warning",
|
|
687
|
+
title: "Multiplos servidores ToStudy configurados",
|
|
688
|
+
description: `Foram encontrados ${anaServers.length} servidores relacionados a ToStudy: ${anaServers.map(([k]) => k).join(", ")}.`,
|
|
689
|
+
suggestion: 'Execute "mcp-setup repair" para remover duplicatas ou limpe manualmente.',
|
|
690
|
+
autoFixable: true
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
async function runDiagnostics() {
|
|
696
|
+
const issues = [];
|
|
697
|
+
const syncChecks = [
|
|
698
|
+
checkClaudeInstallation(),
|
|
699
|
+
checkConfigFile(),
|
|
700
|
+
checkConfigJson(),
|
|
701
|
+
checkMcpConfiguration(),
|
|
702
|
+
checkApiKeyFormat(),
|
|
703
|
+
checkServerUrl(),
|
|
704
|
+
checkIDEInstallation(),
|
|
705
|
+
checkDuplicateServers()
|
|
706
|
+
];
|
|
707
|
+
for (const issue of syncChecks) {
|
|
708
|
+
if (issue) {
|
|
709
|
+
issues.push(issue);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const hasConfigIssues = issues.some(
|
|
713
|
+
(issue) => ["config-invalid-json", "claude-not-installed"].includes(issue.id)
|
|
714
|
+
);
|
|
715
|
+
if (!hasConfigIssues) {
|
|
716
|
+
const asyncChecks = await Promise.all([checkPortConflict(), checkServerConnectivity()]);
|
|
717
|
+
for (const issue of asyncChecks) {
|
|
718
|
+
if (issue) {
|
|
719
|
+
issues.push(issue);
|
|
720
|
+
}
|
|
267
721
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
722
|
+
}
|
|
723
|
+
const severityOrder = {
|
|
724
|
+
critical: 0,
|
|
725
|
+
warning: 1,
|
|
726
|
+
info: 2
|
|
727
|
+
};
|
|
728
|
+
issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
729
|
+
const hasCritical = issues.some((issue) => issue.severity === "critical");
|
|
730
|
+
return {
|
|
731
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
732
|
+
claudeInstalled: isClaudeInstalled(),
|
|
733
|
+
mcpConfigured: isTostudyMcpConfigured(),
|
|
734
|
+
issues,
|
|
735
|
+
passed: !hasCritical
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function getSeverityIcon(severity) {
|
|
739
|
+
switch (severity) {
|
|
740
|
+
case "critical":
|
|
741
|
+
return chalk.red("\u25CF");
|
|
742
|
+
case "warning":
|
|
743
|
+
return chalk.yellow("\u25CF");
|
|
744
|
+
case "info":
|
|
745
|
+
return chalk.blue("\u25CF");
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function getSeverityLabel(severity) {
|
|
749
|
+
switch (severity) {
|
|
750
|
+
case "critical":
|
|
751
|
+
return chalk.red("CRITICO");
|
|
752
|
+
case "warning":
|
|
753
|
+
return chalk.yellow("AVISO");
|
|
754
|
+
case "info":
|
|
755
|
+
return chalk.blue("INFO");
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function printDiagnosticReport(report) {
|
|
759
|
+
println2();
|
|
760
|
+
println2(chalk.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
761
|
+
println2(
|
|
762
|
+
chalk.cyan(" \u2551") + chalk.white.bold(" ToStudy MCP Diagnostics ") + chalk.cyan("\u2551")
|
|
763
|
+
);
|
|
764
|
+
println2(chalk.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
765
|
+
println2();
|
|
766
|
+
println2(chalk.gray("Status:"));
|
|
767
|
+
println2(
|
|
768
|
+
` Claude Code: ${report.claudeInstalled ? chalk.green("\u2713 Instalado") : chalk.red("\u2717 Nao encontrado")}`
|
|
769
|
+
);
|
|
770
|
+
println2(
|
|
771
|
+
` MCP ToStudy: ${report.mcpConfigured ? chalk.green("\u2713 Configurado") : chalk.yellow("\u25CB Nao configurado")}`
|
|
772
|
+
);
|
|
773
|
+
println2();
|
|
774
|
+
if (report.issues.length === 0) {
|
|
775
|
+
println2(chalk.green.bold("\u2713 Nenhum problema encontrado!"));
|
|
776
|
+
println2();
|
|
777
|
+
println2(chalk.gray("Tudo parece estar funcionando corretamente."));
|
|
778
|
+
println2();
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const criticalCount = report.issues.filter((i) => i.severity === "critical").length;
|
|
782
|
+
const warningCount = report.issues.filter((i) => i.severity === "warning").length;
|
|
783
|
+
const infoCount = report.issues.filter((i) => i.severity === "info").length;
|
|
784
|
+
println2(chalk.white.bold("Problemas encontrados:"));
|
|
785
|
+
if (criticalCount > 0) println2(` ${chalk.red("\u25CF")} ${criticalCount} critico(s)`);
|
|
786
|
+
if (warningCount > 0) println2(` ${chalk.yellow("\u25CF")} ${warningCount} aviso(s)`);
|
|
787
|
+
if (infoCount > 0) println2(` ${chalk.blue("\u25CF")} ${infoCount} informativo(s)`);
|
|
788
|
+
println2();
|
|
789
|
+
for (const issue of report.issues) {
|
|
790
|
+
println2(
|
|
791
|
+
`${getSeverityIcon(issue.severity)} ${getSeverityLabel(issue.severity)}: ${chalk.white(issue.title)}`
|
|
792
|
+
);
|
|
793
|
+
println2(chalk.gray(` ${issue.description}`));
|
|
794
|
+
println2(chalk.cyan(` \u2192 ${issue.suggestion}`));
|
|
795
|
+
if (issue.autoFixable) {
|
|
796
|
+
println2(chalk.green(` \u2713 Corrigivel automaticamente`));
|
|
272
797
|
}
|
|
273
|
-
|
|
798
|
+
println2();
|
|
799
|
+
}
|
|
800
|
+
if (report.passed) {
|
|
801
|
+
println2(chalk.yellow("\u26A0 Alguns avisos encontrados, mas o sistema deve funcionar."));
|
|
802
|
+
} else {
|
|
803
|
+
println2(chalk.red("\u2717 Problemas criticos precisam ser resolvidos."));
|
|
804
|
+
println2(chalk.gray(' Execute "npx @tostudy-ai/mcp-setup" para corrigir a configuracao.'));
|
|
805
|
+
}
|
|
806
|
+
println2();
|
|
807
|
+
}
|
|
808
|
+
function exportDiagnosticReportJson(report) {
|
|
809
|
+
return JSON.stringify(report, null, 2);
|
|
810
|
+
}
|
|
811
|
+
if (process.argv[1]?.endsWith("diagnose.js")) {
|
|
812
|
+
(async () => {
|
|
274
813
|
try {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
catch (error) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Print wizard step header
|
|
290
|
-
*/
|
|
291
|
-
function printStep(step, total, title) {
|
|
292
|
-
println();
|
|
293
|
-
println(chalk.cyan(`━━━ Passo ${step}/${total}: ${title} ━━━`));
|
|
294
|
-
println();
|
|
814
|
+
const report = await runDiagnostics();
|
|
815
|
+
if (process.argv.includes("--json")) {
|
|
816
|
+
println2(exportDiagnosticReportJson(report));
|
|
817
|
+
} else {
|
|
818
|
+
printDiagnosticReport(report);
|
|
819
|
+
}
|
|
820
|
+
process.exit(report.passed ? 0 : 1);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
process.stderr.write(chalk.red("Erro ao executar diagnostico:\n"));
|
|
823
|
+
process.stderr.write((error instanceof Error ? error.message : String(error)) + "\n");
|
|
824
|
+
process.exit(1);
|
|
825
|
+
}
|
|
826
|
+
})();
|
|
295
827
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
828
|
+
|
|
829
|
+
// src/repair.ts
|
|
830
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, copyFileSync as copyFileSync2 } from "fs";
|
|
831
|
+
import { dirname as dirname2 } from "path";
|
|
832
|
+
import chalk2 from "chalk";
|
|
833
|
+
var DEFAULT_PLATFORM_URL = "https://tostudy.ai";
|
|
834
|
+
function println3(message = "") {
|
|
835
|
+
process.stdout.write(`${message}
|
|
836
|
+
`);
|
|
837
|
+
}
|
|
838
|
+
function repairMissingConfig() {
|
|
839
|
+
const configPath = getClaudeConfigPath();
|
|
840
|
+
const configDir = dirname2(configPath);
|
|
841
|
+
try {
|
|
842
|
+
if (!existsSync4(configDir)) {
|
|
843
|
+
mkdirSync2(configDir, { recursive: true });
|
|
310
844
|
}
|
|
845
|
+
const initialConfig = {
|
|
846
|
+
mcpServers: {}
|
|
847
|
+
};
|
|
848
|
+
writeFileSync2(configPath, JSON.stringify(initialConfig, null, 2), "utf-8");
|
|
849
|
+
return {
|
|
850
|
+
issueId: "config-missing",
|
|
851
|
+
success: true,
|
|
852
|
+
message: "Arquivo de configuracao criado com sucesso.",
|
|
853
|
+
action: `Criado: ${configPath}`
|
|
854
|
+
};
|
|
855
|
+
} catch (error) {
|
|
856
|
+
return {
|
|
857
|
+
issueId: "config-missing",
|
|
858
|
+
success: false,
|
|
859
|
+
message: `Falha ao criar arquivo de configuracao: ${error instanceof Error ? error.message : String(error)}`
|
|
860
|
+
};
|
|
861
|
+
}
|
|
311
862
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
863
|
+
function repairInvalidJson() {
|
|
864
|
+
const configPath = getClaudeConfigPath();
|
|
865
|
+
try {
|
|
866
|
+
if (existsSync4(configPath)) {
|
|
867
|
+
const backupPath = `${configPath}.corrupted.${Date.now()}`;
|
|
868
|
+
copyFileSync2(configPath, backupPath);
|
|
869
|
+
const freshConfig = {
|
|
870
|
+
mcpServers: {}
|
|
871
|
+
};
|
|
872
|
+
writeFileSync2(configPath, JSON.stringify(freshConfig, null, 2), "utf-8");
|
|
873
|
+
return {
|
|
874
|
+
issueId: "config-invalid-json",
|
|
875
|
+
success: true,
|
|
876
|
+
message: "Arquivo de configuracao recriado. Backup salvo.",
|
|
877
|
+
action: `Backup: ${backupPath}`
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
issueId: "config-invalid-json",
|
|
882
|
+
success: false,
|
|
883
|
+
message: "Arquivo de configuracao nao existe."
|
|
884
|
+
};
|
|
885
|
+
} catch (error) {
|
|
886
|
+
return {
|
|
887
|
+
issueId: "config-invalid-json",
|
|
888
|
+
success: false,
|
|
889
|
+
message: `Falha ao reparar JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function repairMcpNotConfigured(apiKey, platformUrl = DEFAULT_PLATFORM_URL) {
|
|
894
|
+
try {
|
|
895
|
+
addTostudyMcpServer(apiKey, platformUrl);
|
|
896
|
+
return {
|
|
897
|
+
issueId: "mcp-not-configured",
|
|
898
|
+
success: true,
|
|
899
|
+
message: "Servidor MCP da ToStudy configurado com sucesso.",
|
|
900
|
+
action: "Servidor tostudy adicionado"
|
|
901
|
+
};
|
|
902
|
+
} catch (error) {
|
|
903
|
+
return {
|
|
904
|
+
issueId: "mcp-not-configured",
|
|
905
|
+
success: false,
|
|
906
|
+
message: `Falha ao configurar MCP: ${error instanceof Error ? error.message : String(error)}`
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function repairApiKeyMissing(apiKey, platformUrl = DEFAULT_PLATFORM_URL) {
|
|
911
|
+
try {
|
|
912
|
+
addTostudyMcpServer(apiKey, platformUrl);
|
|
913
|
+
return {
|
|
914
|
+
issueId: "api-key-missing",
|
|
915
|
+
success: true,
|
|
916
|
+
message: "API key configurada com sucesso.",
|
|
917
|
+
action: "Header Authorization adicionado"
|
|
918
|
+
};
|
|
919
|
+
} catch (error) {
|
|
920
|
+
return {
|
|
921
|
+
issueId: "api-key-missing",
|
|
922
|
+
success: false,
|
|
923
|
+
message: `Falha ao configurar API key: ${error instanceof Error ? error.message : String(error)}`
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function repairApiKeyFormat() {
|
|
928
|
+
const config = readClaudeConfig();
|
|
929
|
+
const mcpConfig = config.mcpServers?.["tostudy"];
|
|
930
|
+
if (!mcpConfig) {
|
|
931
|
+
return {
|
|
932
|
+
issueId: "api-key-invalid-format",
|
|
933
|
+
success: false,
|
|
934
|
+
message: "Servidor MCP nao esta configurado."
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
try {
|
|
938
|
+
if (mcpConfig.headers) {
|
|
939
|
+
const authHeader = mcpConfig.headers["Authorization"] || "";
|
|
940
|
+
if (authHeader && !authHeader.startsWith("Bearer ")) {
|
|
941
|
+
mcpConfig.headers["Authorization"] = `Bearer ${authHeader}`;
|
|
942
|
+
writeClaudeConfig(config);
|
|
943
|
+
return {
|
|
944
|
+
issueId: "api-key-invalid-format",
|
|
945
|
+
success: true,
|
|
946
|
+
message: "Formato de API key corrigido.",
|
|
947
|
+
action: 'Prefixo "Bearer " adicionado'
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (mcpConfig.args) {
|
|
952
|
+
for (let i = 0; i < mcpConfig.args.length; i++) {
|
|
953
|
+
if (mcpConfig.args[i] === "--header" && mcpConfig.args[i + 1]?.startsWith("Authorization:")) {
|
|
954
|
+
const value = mcpConfig.args[i + 1].replace("Authorization:", "");
|
|
955
|
+
if (value && !value.startsWith("Bearer ")) {
|
|
956
|
+
mcpConfig.args[i + 1] = `Authorization:Bearer ${value}`;
|
|
957
|
+
writeClaudeConfig(config);
|
|
958
|
+
return {
|
|
959
|
+
issueId: "api-key-invalid-format",
|
|
960
|
+
success: true,
|
|
961
|
+
message: "Formato de API key corrigido.",
|
|
962
|
+
action: 'Prefixo "Bearer " adicionado'
|
|
963
|
+
};
|
|
964
|
+
}
|
|
363
965
|
}
|
|
966
|
+
}
|
|
364
967
|
}
|
|
365
|
-
|
|
366
|
-
|
|
968
|
+
return {
|
|
969
|
+
issueId: "api-key-invalid-format",
|
|
970
|
+
success: false,
|
|
971
|
+
message: "API key nao encontrada para corrigir."
|
|
972
|
+
};
|
|
973
|
+
} catch (error) {
|
|
974
|
+
return {
|
|
975
|
+
issueId: "api-key-invalid-format",
|
|
976
|
+
success: false,
|
|
977
|
+
message: `Falha ao corrigir formato: ${error instanceof Error ? error.message : String(error)}`
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function repairServerUrl(platformUrl = DEFAULT_PLATFORM_URL) {
|
|
982
|
+
const config = readClaudeConfig();
|
|
983
|
+
const mcpConfig = config.mcpServers?.["tostudy"];
|
|
984
|
+
if (!mcpConfig) {
|
|
985
|
+
return {
|
|
986
|
+
issueId: "server-url-missing",
|
|
987
|
+
success: false,
|
|
988
|
+
message: "Servidor MCP nao esta configurado."
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
try {
|
|
992
|
+
const apiKey = extractApiKey(mcpConfig);
|
|
993
|
+
if (!apiKey) {
|
|
994
|
+
return {
|
|
995
|
+
issueId: "server-url-missing",
|
|
996
|
+
success: false,
|
|
997
|
+
message: "API key nao encontrada para reconstruir configuracao."
|
|
998
|
+
};
|
|
367
999
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
1000
|
+
addTostudyMcpServer(apiKey, platformUrl);
|
|
1001
|
+
return {
|
|
1002
|
+
issueId: "server-url-missing",
|
|
1003
|
+
success: true,
|
|
1004
|
+
message: "URL do servidor configurada.",
|
|
1005
|
+
action: `URL: ${platformUrl}/mcp`
|
|
1006
|
+
};
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
return {
|
|
1009
|
+
issueId: "server-url-missing",
|
|
1010
|
+
success: false,
|
|
1011
|
+
message: `Falha ao configurar URL: ${error instanceof Error ? error.message : String(error)}`
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function repairServerUrlProtocol(platformUrl = DEFAULT_PLATFORM_URL) {
|
|
1016
|
+
const config = readClaudeConfig();
|
|
1017
|
+
const mcpConfig = config.mcpServers?.["tostudy"];
|
|
1018
|
+
if (!mcpConfig) {
|
|
1019
|
+
return {
|
|
1020
|
+
issueId: "server-url-invalid-protocol",
|
|
1021
|
+
success: false,
|
|
1022
|
+
message: "Servidor MCP nao esta configurado."
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
const url = extractMcpUrl(mcpConfig);
|
|
1026
|
+
if (!url) {
|
|
1027
|
+
return {
|
|
1028
|
+
issueId: "server-url-invalid-protocol",
|
|
1029
|
+
success: false,
|
|
1030
|
+
message: "URL do servidor nao configurada."
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
const fixedUrl = url.replace(/^[a-z]+:/, "https:");
|
|
1035
|
+
const basePlatformUrl = fixedUrl.replace(/\/mcp(\/sse)?$/, "");
|
|
1036
|
+
const apiKey = extractApiKey(mcpConfig);
|
|
373
1037
|
if (!apiKey) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const icon = issue.severity === "critical"
|
|
425
|
-
? chalk.red("●")
|
|
426
|
-
: issue.severity === "warning"
|
|
427
|
-
? chalk.yellow("●")
|
|
428
|
-
: chalk.blue("●");
|
|
429
|
-
println(` ${icon} ${issue.title}`);
|
|
430
|
-
}
|
|
431
|
-
println();
|
|
432
|
-
// Auto-repair if enabled
|
|
433
|
-
if (options.autoRepair) {
|
|
434
|
-
process.stdout.write(" Tentando reparar automaticamente... ");
|
|
435
|
-
const repairReport = repairAllIssues(report, apiKey, platformUrl);
|
|
436
|
-
if (repairReport.repairsSucceeded > 0) {
|
|
437
|
-
println(chalk.green(`${repairReport.repairsSucceeded} corrigido(s)`));
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
println(chalk.yellow("Nenhum reparo aplicado"));
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
println(chalk.gray(' Dica: Execute "npx @tostudy-ai/mcp-setup diagnose" para mais detalhes.'));
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
448
|
-
println(chalk.green(" ✓ Nenhum problema encontrado!"));
|
|
449
|
-
}
|
|
1038
|
+
return {
|
|
1039
|
+
issueId: "server-url-invalid-protocol",
|
|
1040
|
+
success: false,
|
|
1041
|
+
message: "API key nao encontrada para reconstruir configuracao."
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
addTostudyMcpServer(apiKey, basePlatformUrl);
|
|
1045
|
+
return {
|
|
1046
|
+
issueId: "server-url-invalid-protocol",
|
|
1047
|
+
success: true,
|
|
1048
|
+
message: "Protocolo da URL corrigido para HTTPS.",
|
|
1049
|
+
action: `URL: ${basePlatformUrl}/mcp`
|
|
1050
|
+
};
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
return {
|
|
1053
|
+
issueId: "server-url-invalid-protocol",
|
|
1054
|
+
success: false,
|
|
1055
|
+
message: `Falha ao corrigir protocolo: ${error instanceof Error ? error.message : String(error)}`
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
function repairServerUrlPath() {
|
|
1060
|
+
const config = readClaudeConfig();
|
|
1061
|
+
const mcpConfig = config.mcpServers?.["tostudy"];
|
|
1062
|
+
if (!mcpConfig) {
|
|
1063
|
+
return {
|
|
1064
|
+
issueId: "server-url-missing-sse-path",
|
|
1065
|
+
success: false,
|
|
1066
|
+
message: "Servidor MCP nao esta configurado."
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
const url = extractMcpUrl(mcpConfig);
|
|
1070
|
+
if (!url) {
|
|
1071
|
+
return {
|
|
1072
|
+
issueId: "server-url-missing-sse-path",
|
|
1073
|
+
success: false,
|
|
1074
|
+
message: "URL do servidor nao configurada."
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
const parsedUrl = new URL(url);
|
|
1079
|
+
parsedUrl.pathname = parsedUrl.pathname.replace(/\/(mcp(\/sse)?)?$/, "");
|
|
1080
|
+
const basePlatformUrl = parsedUrl.toString().replace(/\/$/, "");
|
|
1081
|
+
const apiKey = extractApiKey(mcpConfig);
|
|
1082
|
+
if (!apiKey) {
|
|
1083
|
+
return {
|
|
1084
|
+
issueId: "server-url-missing-sse-path",
|
|
1085
|
+
success: false,
|
|
1086
|
+
message: "API key nao encontrada para reconstruir configuracao."
|
|
1087
|
+
};
|
|
450
1088
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
1089
|
+
addTostudyMcpServer(apiKey, basePlatformUrl);
|
|
1090
|
+
return {
|
|
1091
|
+
issueId: "server-url-missing-sse-path",
|
|
1092
|
+
success: true,
|
|
1093
|
+
message: "Path /mcp corrigido na URL.",
|
|
1094
|
+
action: `URL: ${basePlatformUrl}/mcp`
|
|
1095
|
+
};
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
return {
|
|
1098
|
+
issueId: "server-url-missing-sse-path",
|
|
1099
|
+
success: false,
|
|
1100
|
+
message: `Falha ao corrigir path: ${error instanceof Error ? error.message : String(error)}`
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
function repairDuplicateServers() {
|
|
1105
|
+
const config = readClaudeConfig();
|
|
1106
|
+
if (!config.mcpServers) {
|
|
1107
|
+
return {
|
|
1108
|
+
issueId: "duplicate-servers",
|
|
1109
|
+
success: false,
|
|
1110
|
+
message: "Nenhum servidor MCP configurado."
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const servers = Object.entries(config.mcpServers);
|
|
1115
|
+
const anaServers = servers.filter(
|
|
1116
|
+
([key, server]) => key.toLowerCase().includes("ana") || key.toLowerCase().includes("catalyst") || server.url && server.url.includes("tostudy.com")
|
|
1117
|
+
);
|
|
1118
|
+
if (anaServers.length <= 1) {
|
|
1119
|
+
return {
|
|
1120
|
+
issueId: "duplicate-servers",
|
|
1121
|
+
success: true,
|
|
1122
|
+
message: "Nenhuma duplicata encontrada."
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
const primaryKey = anaServers.find(([key]) => key === "tostudy")?.[0] || anaServers[0][0];
|
|
1126
|
+
const removedKeys = [];
|
|
1127
|
+
for (const [key] of anaServers) {
|
|
1128
|
+
if (key !== primaryKey) {
|
|
1129
|
+
delete config.mcpServers[key];
|
|
1130
|
+
removedKeys.push(key);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
writeClaudeConfig(config);
|
|
1134
|
+
return {
|
|
1135
|
+
issueId: "duplicate-servers",
|
|
1136
|
+
success: true,
|
|
1137
|
+
message: `${removedKeys.length} servidor(es) duplicado(s) removido(s).`,
|
|
1138
|
+
action: `Removidos: ${removedKeys.join(", ")}`
|
|
1139
|
+
};
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
return {
|
|
1142
|
+
issueId: "duplicate-servers",
|
|
1143
|
+
success: false,
|
|
1144
|
+
message: `Falha ao remover duplicatas: ${error instanceof Error ? error.message : String(error)}`
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
var REPAIR_FUNCTIONS = {
|
|
1149
|
+
"config-missing": () => repairMissingConfig(),
|
|
1150
|
+
"config-invalid-json": () => repairInvalidJson(),
|
|
1151
|
+
"mcp-not-configured": (apiKey, platformUrl) => {
|
|
1152
|
+
if (!apiKey) {
|
|
1153
|
+
return {
|
|
1154
|
+
issueId: "mcp-not-configured",
|
|
1155
|
+
success: false,
|
|
1156
|
+
message: "API key necessaria para configurar o MCP."
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
return repairMcpNotConfigured(apiKey, platformUrl);
|
|
1160
|
+
},
|
|
1161
|
+
"api-key-missing": (apiKey, platformUrl) => {
|
|
1162
|
+
if (!apiKey) {
|
|
1163
|
+
return {
|
|
1164
|
+
issueId: "api-key-missing",
|
|
1165
|
+
success: false,
|
|
1166
|
+
message: "Nova API key necessaria."
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
return repairApiKeyMissing(apiKey, platformUrl);
|
|
1170
|
+
},
|
|
1171
|
+
"api-key-invalid-format": () => repairApiKeyFormat(),
|
|
1172
|
+
"api-key-too-short": (apiKey, platformUrl) => {
|
|
1173
|
+
if (!apiKey) {
|
|
1174
|
+
return {
|
|
1175
|
+
issueId: "api-key-too-short",
|
|
1176
|
+
success: false,
|
|
1177
|
+
message: "Nova API key necessaria."
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
return repairApiKeyMissing(apiKey, platformUrl);
|
|
1181
|
+
},
|
|
1182
|
+
"server-url-missing": (_, platformUrl) => repairServerUrl(platformUrl),
|
|
1183
|
+
"server-url-invalid": (_, platformUrl) => repairServerUrl(platformUrl),
|
|
1184
|
+
"server-url-invalid-protocol": () => repairServerUrlProtocol(),
|
|
1185
|
+
"server-url-missing-sse-path": () => repairServerUrlPath(),
|
|
1186
|
+
"auth-failed": (apiKey, platformUrl) => {
|
|
1187
|
+
if (!apiKey) {
|
|
1188
|
+
return {
|
|
1189
|
+
issueId: "auth-failed",
|
|
1190
|
+
success: false,
|
|
1191
|
+
message: "Nova API key necessaria. Gere em /student/settings/mcp."
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
return repairMcpNotConfigured(apiKey, platformUrl);
|
|
1195
|
+
},
|
|
1196
|
+
"duplicate-servers": () => repairDuplicateServers()
|
|
1197
|
+
};
|
|
1198
|
+
var ISSUES_REQUIRING_API_KEY = [
|
|
1199
|
+
"mcp-not-configured",
|
|
1200
|
+
"api-key-missing",
|
|
1201
|
+
"api-key-too-short",
|
|
1202
|
+
"auth-failed"
|
|
1203
|
+
];
|
|
1204
|
+
function issueRequiresUserInput(issueId) {
|
|
1205
|
+
return ISSUES_REQUIRING_API_KEY.includes(issueId);
|
|
1206
|
+
}
|
|
1207
|
+
function repairIssue(issue, apiKey, platformUrl) {
|
|
1208
|
+
const repairFn = REPAIR_FUNCTIONS[issue.id];
|
|
1209
|
+
if (!repairFn) {
|
|
1210
|
+
return {
|
|
1211
|
+
issueId: issue.id,
|
|
1212
|
+
success: false,
|
|
1213
|
+
message: `Sem funcao de reparo disponivel para: ${issue.id}`
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
return repairFn(apiKey, platformUrl);
|
|
1217
|
+
}
|
|
1218
|
+
function repairAllIssues(report, apiKey, platformUrl = DEFAULT_PLATFORM_URL) {
|
|
1219
|
+
const results = [];
|
|
1220
|
+
const requiresUserInput = [];
|
|
1221
|
+
let succeeded = 0;
|
|
1222
|
+
let failed = 0;
|
|
1223
|
+
const fixableIssues = report.issues.filter((issue) => issue.autoFixable);
|
|
1224
|
+
for (const issue of fixableIssues) {
|
|
1225
|
+
if (issueRequiresUserInput(issue.id) && !apiKey) {
|
|
1226
|
+
requiresUserInput.push(issue.id);
|
|
1227
|
+
results.push({
|
|
1228
|
+
issueId: issue.id,
|
|
1229
|
+
success: false,
|
|
1230
|
+
message: "Requer API key para reparar."
|
|
1231
|
+
});
|
|
1232
|
+
failed++;
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
const result = repairIssue(issue, apiKey, platformUrl);
|
|
1236
|
+
results.push(result);
|
|
1237
|
+
if (result.success) {
|
|
1238
|
+
succeeded++;
|
|
1239
|
+
} else {
|
|
1240
|
+
failed++;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return {
|
|
1244
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1245
|
+
repairsAttempted: fixableIssues.length,
|
|
1246
|
+
repairsSucceeded: succeeded,
|
|
1247
|
+
repairsFailed: failed,
|
|
1248
|
+
results,
|
|
1249
|
+
requiresUserInput
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
async function diagnoseAndRepair(apiKey, platformUrl = DEFAULT_PLATFORM_URL) {
|
|
1253
|
+
const diagnostic = await runDiagnostics();
|
|
1254
|
+
const repair = repairAllIssues(diagnostic, apiKey, platformUrl);
|
|
1255
|
+
return { diagnostic, repair };
|
|
1256
|
+
}
|
|
1257
|
+
function printRepairReport(report) {
|
|
1258
|
+
println3();
|
|
1259
|
+
println3(chalk2.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
1260
|
+
println3(
|
|
1261
|
+
chalk2.cyan(" \u2551") + chalk2.white.bold(" ToStudy MCP Auto-Repair ") + chalk2.cyan("\u2551")
|
|
1262
|
+
);
|
|
1263
|
+
println3(chalk2.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
1264
|
+
println3();
|
|
1265
|
+
if (report.repairsAttempted === 0) {
|
|
1266
|
+
println3(chalk2.green.bold("\u2713 Nenhum reparo necessario!"));
|
|
1267
|
+
println3();
|
|
1268
|
+
println3(chalk2.gray("Nenhum problema corrigivel automaticamente foi encontrado."));
|
|
1269
|
+
println3();
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
println3(chalk2.white.bold("Resumo:"));
|
|
1273
|
+
println3(` Reparos tentados: ${report.repairsAttempted}`);
|
|
1274
|
+
println3(` ${chalk2.green("\u2713 Sucesso:")} ${report.repairsSucceeded}`);
|
|
1275
|
+
if (report.repairsFailed > 0) {
|
|
1276
|
+
println3(` ${chalk2.red("\u2717 Falha:")} ${report.repairsFailed}`);
|
|
1277
|
+
}
|
|
1278
|
+
println3();
|
|
1279
|
+
println3(chalk2.white.bold("Detalhes:"));
|
|
1280
|
+
println3();
|
|
1281
|
+
for (const result of report.results) {
|
|
1282
|
+
if (result.success) {
|
|
1283
|
+
println3(` ${chalk2.green("\u2713")} ${result.message}`);
|
|
1284
|
+
if (result.action) {
|
|
1285
|
+
println3(chalk2.gray(` ${result.action}`));
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
println3(` ${chalk2.red("\u2717")} ${result.message}`);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
println3();
|
|
1292
|
+
if (report.requiresUserInput.length > 0) {
|
|
1293
|
+
println3(chalk2.yellow("\u26A0 Alguns reparos requerem interacao:"));
|
|
1294
|
+
println3();
|
|
1295
|
+
println3(" Para reparar problemas de API key, execute:");
|
|
1296
|
+
println3(chalk2.cyan(" npx @tostudy-ai/mcp-setup repair --api-key <sua-api-key>"));
|
|
1297
|
+
println3();
|
|
1298
|
+
println3(" Para gerar uma nova API key:");
|
|
1299
|
+
println3(chalk2.cyan(" https://tostudy.ai/student/settings/mcp"));
|
|
1300
|
+
println3();
|
|
1301
|
+
}
|
|
1302
|
+
if (report.repairsFailed === 0 && report.requiresUserInput.length === 0) {
|
|
1303
|
+
println3(chalk2.green.bold("\u2713 Todos os reparos concluidos com sucesso!"));
|
|
1304
|
+
println3(chalk2.gray(" Reinicie o Claude Code para aplicar as mudancas."));
|
|
1305
|
+
} else if (report.repairsSucceeded > 0) {
|
|
1306
|
+
println3(chalk2.yellow("\u26A0 Alguns reparos foram concluidos, mas outros falharam."));
|
|
1307
|
+
println3(chalk2.gray(" Execute o diagnostico novamente para verificar o status."));
|
|
1308
|
+
} else {
|
|
1309
|
+
println3(chalk2.red("\u2717 Nenhum reparo foi concluido com sucesso."));
|
|
1310
|
+
println3(chalk2.gray(" Verifique os erros acima e tente novamente."));
|
|
1311
|
+
}
|
|
1312
|
+
println3();
|
|
1313
|
+
}
|
|
1314
|
+
if (process.argv[1]?.endsWith("repair.js")) {
|
|
1315
|
+
(async () => {
|
|
497
1316
|
try {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
1317
|
+
let apiKey;
|
|
1318
|
+
let platformUrl = DEFAULT_PLATFORM_URL;
|
|
1319
|
+
let jsonOutput = false;
|
|
1320
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
1321
|
+
const arg = process.argv[i];
|
|
1322
|
+
if (arg === "--api-key" && process.argv[i + 1]) {
|
|
1323
|
+
apiKey = process.argv[++i];
|
|
1324
|
+
} else if (arg === "--url" && process.argv[i + 1]) {
|
|
1325
|
+
platformUrl = process.argv[++i];
|
|
1326
|
+
} else if (arg === "--json") {
|
|
1327
|
+
jsonOutput = true;
|
|
1328
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
1329
|
+
println3("ToStudy MCP Auto-Repair");
|
|
1330
|
+
println3();
|
|
1331
|
+
println3("Usage: node repair.js [options]");
|
|
1332
|
+
println3();
|
|
1333
|
+
println3("Options:");
|
|
1334
|
+
println3(" --api-key <key> API key para reparos que necessitam autenticacao");
|
|
1335
|
+
println3(" --url <url> URL da plataforma (default: https://tostudy.ai)");
|
|
1336
|
+
println3(" --json Saida em formato JSON");
|
|
1337
|
+
println3(" --help, -h Mostra esta ajuda");
|
|
1338
|
+
println3();
|
|
1339
|
+
process.exit(0);
|
|
503
1340
|
}
|
|
1341
|
+
}
|
|
1342
|
+
if (!apiKey) {
|
|
1343
|
+
apiKey = process.env.TOSTUDY_API_KEY;
|
|
1344
|
+
}
|
|
1345
|
+
const { diagnostic, repair } = await diagnoseAndRepair(apiKey, platformUrl);
|
|
1346
|
+
if (jsonOutput) {
|
|
1347
|
+
println3(JSON.stringify({ diagnostic, repair }, null, 2));
|
|
1348
|
+
} else {
|
|
1349
|
+
printRepairReport(repair);
|
|
1350
|
+
}
|
|
1351
|
+
const hasUnfixedCritical = diagnostic.issues.some(
|
|
1352
|
+
(issue) => issue.severity === "critical" && !repair.results.find((r) => r.issueId === issue.id && r.success)
|
|
1353
|
+
);
|
|
1354
|
+
process.exit(hasUnfixedCritical ? 1 : 0);
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
process.stderr.write(chalk2.red("Erro ao executar reparo:\n"));
|
|
1357
|
+
process.stderr.write((error instanceof Error ? error.message : String(error)) + "\n");
|
|
1358
|
+
process.exit(1);
|
|
504
1359
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
1360
|
+
})();
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/ide-handlers/claude-code.ts
|
|
1364
|
+
import { execFileSync } from "child_process";
|
|
1365
|
+
|
|
1366
|
+
// src/ide-handlers/base.ts
|
|
1367
|
+
import { platform as platform3 } from "os";
|
|
1368
|
+
function buildMcpRemoteArgs(apiKey, mcpUrl, ide) {
|
|
1369
|
+
const osHeader = getClientOsHeaderValue2();
|
|
1370
|
+
return [
|
|
1371
|
+
"-y",
|
|
1372
|
+
"mcp-remote",
|
|
1373
|
+
`${mcpUrl}/mcp`,
|
|
1374
|
+
"--header",
|
|
1375
|
+
`Authorization:Bearer ${apiKey}`,
|
|
1376
|
+
"--header",
|
|
1377
|
+
`X-Tostudy-Client-IDE:${ide}`,
|
|
1378
|
+
"--header",
|
|
1379
|
+
`X-Tostudy-Client-OS:${osHeader}`
|
|
1380
|
+
];
|
|
1381
|
+
}
|
|
1382
|
+
function buildTostudyServerEntry(apiKey, mcpUrl, ide) {
|
|
1383
|
+
return {
|
|
1384
|
+
command: "npx",
|
|
1385
|
+
args: buildMcpRemoteArgs(apiKey, mcpUrl, ide)
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function getClientOsHeaderValue2() {
|
|
1389
|
+
switch (platform3()) {
|
|
1390
|
+
case "darwin":
|
|
1391
|
+
return "macos";
|
|
1392
|
+
case "win32":
|
|
1393
|
+
return "windows";
|
|
1394
|
+
default:
|
|
1395
|
+
return "linux";
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
async function verifyHeartbeat(apiKey, platformUrl) {
|
|
1399
|
+
try {
|
|
1400
|
+
const response = await fetch(`${platformUrl}/api/mcp/heartbeat`, {
|
|
1401
|
+
method: "POST",
|
|
1402
|
+
headers: {
|
|
1403
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1404
|
+
"Content-Type": "application/json"
|
|
1405
|
+
},
|
|
1406
|
+
body: JSON.stringify({ timestamp: (/* @__PURE__ */ new Date()).toISOString() })
|
|
1407
|
+
});
|
|
1408
|
+
return response.ok || response.status === 204;
|
|
1409
|
+
} catch {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// src/ide-handlers/claude-code.ts
|
|
1415
|
+
function parseClaudeCodeScope(answer) {
|
|
1416
|
+
return answer.trim().toLowerCase().startsWith("l") ? "project" : "user";
|
|
1417
|
+
}
|
|
1418
|
+
async function promptClaudeCodeScope(promptFn = prompt) {
|
|
1419
|
+
const answer = await promptFn(
|
|
1420
|
+
" Claude Code: configurar global ou local? (g/l, padr\xE3o: global) "
|
|
1421
|
+
);
|
|
1422
|
+
return parseClaudeCodeScope(answer);
|
|
1423
|
+
}
|
|
1424
|
+
function buildClaudeCodeAddArgs(apiKey, mcpUrl, scope) {
|
|
1425
|
+
return [
|
|
1426
|
+
"mcp",
|
|
1427
|
+
"add",
|
|
1428
|
+
"tostudy",
|
|
1429
|
+
"--scope",
|
|
1430
|
+
scope,
|
|
1431
|
+
"--",
|
|
1432
|
+
"npx",
|
|
1433
|
+
...buildMcpRemoteArgs(apiKey, mcpUrl, "claude-code")
|
|
1434
|
+
];
|
|
1435
|
+
}
|
|
1436
|
+
function buildClaudeCodeRemoveArgs(scope) {
|
|
1437
|
+
return ["mcp", "remove", "tostudy", "--scope", scope];
|
|
1438
|
+
}
|
|
1439
|
+
var ClaudeCodeHandler = class {
|
|
1440
|
+
id = "claude-code";
|
|
1441
|
+
name = "Claude Code";
|
|
1442
|
+
async detect() {
|
|
1443
|
+
try {
|
|
1444
|
+
execFileSync("which", ["claude"], { stdio: "ignore" });
|
|
1445
|
+
return true;
|
|
1446
|
+
} catch {
|
|
1447
|
+
try {
|
|
1448
|
+
execFileSync("where", ["claude"], { stdio: "ignore" });
|
|
1449
|
+
return true;
|
|
1450
|
+
} catch {
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
509
1453
|
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
.option("--auto-repair", "Tenta reparar problemas automaticamente")
|
|
519
|
-
.action(async (options) => {
|
|
1454
|
+
}
|
|
1455
|
+
getConfigPath() {
|
|
1456
|
+
return "Terminal (claude mcp add)";
|
|
1457
|
+
}
|
|
1458
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1459
|
+
const scope = await promptClaudeCodeScope();
|
|
1460
|
+
const addArgs = buildClaudeCodeAddArgs(apiKey, mcpUrl, scope);
|
|
1461
|
+
const removeArgs = buildClaudeCodeRemoveArgs(scope);
|
|
520
1462
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
process.exit(1);
|
|
1463
|
+
try {
|
|
1464
|
+
execFileSync("claude", removeArgs, { stdio: "ignore" });
|
|
1465
|
+
} catch {
|
|
1466
|
+
}
|
|
1467
|
+
execFileSync("claude", addArgs, { stdio: "inherit" });
|
|
1468
|
+
} catch (error) {
|
|
1469
|
+
throw new Error(
|
|
1470
|
+
`Failed to run 'claude mcp add'. Is Claude Code installed?
|
|
1471
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1472
|
+
);
|
|
532
1473
|
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
1474
|
+
}
|
|
1475
|
+
async verify(apiKey, mcpUrl) {
|
|
1476
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
// src/ide-handlers/cursor.ts
|
|
1481
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
1482
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
1483
|
+
import { homedir as homedir3, platform as platform4 } from "os";
|
|
1484
|
+
var CursorHandler = class {
|
|
1485
|
+
id = "cursor";
|
|
1486
|
+
name = "Cursor";
|
|
1487
|
+
async detect() {
|
|
1488
|
+
const home = homedir3();
|
|
1489
|
+
const os = platform4();
|
|
1490
|
+
switch (os) {
|
|
1491
|
+
case "darwin":
|
|
1492
|
+
return existsSync5(join3("/Applications", "Cursor.app")) || existsSync5(join3(home, "Applications", "Cursor.app"));
|
|
1493
|
+
case "win32": {
|
|
1494
|
+
const localAppData = process.env.LOCALAPPDATA || join3(home, "AppData", "Local");
|
|
1495
|
+
return existsSync5(join3(localAppData, "Programs", "cursor", "Cursor.exe"));
|
|
1496
|
+
}
|
|
1497
|
+
case "linux":
|
|
1498
|
+
return existsSync5(join3(home, ".cursor"));
|
|
1499
|
+
default:
|
|
1500
|
+
return false;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
getConfigPath() {
|
|
1504
|
+
const home = homedir3();
|
|
1505
|
+
return join3(home, ".cursor", "mcp.json");
|
|
1506
|
+
}
|
|
1507
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1508
|
+
const configPath = this.getConfigPath();
|
|
1509
|
+
const configDir = dirname3(configPath);
|
|
1510
|
+
if (!existsSync5(configDir)) {
|
|
1511
|
+
mkdirSync3(configDir, { recursive: true });
|
|
1512
|
+
}
|
|
1513
|
+
let config = {};
|
|
1514
|
+
if (existsSync5(configPath)) {
|
|
1515
|
+
try {
|
|
1516
|
+
config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
const mcpServers = config.mcpServers ?? {};
|
|
1521
|
+
mcpServers["tostudy"] = buildTostudyServerEntry(apiKey, mcpUrl, this.id);
|
|
1522
|
+
config.mcpServers = mcpServers;
|
|
1523
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1524
|
+
}
|
|
1525
|
+
async verify(apiKey, mcpUrl) {
|
|
1526
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
// src/ide-handlers/vscode.ts
|
|
1531
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
1532
|
+
import { join as join4, dirname as dirname4 } from "path";
|
|
1533
|
+
import { homedir as homedir4, platform as platform5 } from "os";
|
|
1534
|
+
var VSCodeHandler = class {
|
|
1535
|
+
id = "vscode";
|
|
1536
|
+
name = "VS Code";
|
|
1537
|
+
async detect() {
|
|
1538
|
+
const home = homedir4();
|
|
1539
|
+
const os = platform5();
|
|
1540
|
+
switch (os) {
|
|
1541
|
+
case "darwin":
|
|
1542
|
+
return existsSync6(join4("/Applications", "Visual Studio Code.app")) || existsSync6(join4(home, "Applications", "Visual Studio Code.app"));
|
|
1543
|
+
case "win32": {
|
|
1544
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
1545
|
+
const localAppData = process.env.LOCALAPPDATA || join4(home, "AppData", "Local");
|
|
1546
|
+
return existsSync6(join4(programFiles, "Microsoft VS Code", "Code.exe")) || existsSync6(join4(localAppData, "Programs", "Microsoft VS Code", "Code.exe"));
|
|
1547
|
+
}
|
|
1548
|
+
case "linux":
|
|
1549
|
+
return existsSync6("/usr/bin/code") || existsSync6("/usr/local/bin/code");
|
|
1550
|
+
default:
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
getConfigPath() {
|
|
1555
|
+
return join4(process.cwd(), ".vscode", "mcp.json");
|
|
1556
|
+
}
|
|
1557
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1558
|
+
const configPath = this.getConfigPath();
|
|
1559
|
+
const configDir = dirname4(configPath);
|
|
1560
|
+
if (!existsSync6(configDir)) {
|
|
1561
|
+
mkdirSync4(configDir, { recursive: true });
|
|
1562
|
+
}
|
|
1563
|
+
let config = {};
|
|
1564
|
+
if (existsSync6(configPath)) {
|
|
1565
|
+
try {
|
|
1566
|
+
config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
1567
|
+
} catch {
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
const servers = config.servers ?? {};
|
|
1571
|
+
servers["tostudy"] = buildTostudyServerEntry(apiKey, mcpUrl, this.id);
|
|
1572
|
+
config.servers = servers;
|
|
1573
|
+
writeFileSync4(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1574
|
+
}
|
|
1575
|
+
async verify(apiKey, mcpUrl) {
|
|
1576
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
// src/ide-handlers/desktop.ts
|
|
1581
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, copyFileSync as copyFileSync3 } from "fs";
|
|
1582
|
+
import { dirname as dirname5 } from "path";
|
|
1583
|
+
var DesktopHandler = class {
|
|
1584
|
+
id = "desktop";
|
|
1585
|
+
name = "Claude Desktop";
|
|
1586
|
+
async detect() {
|
|
1587
|
+
return isClaudeInstalled();
|
|
1588
|
+
}
|
|
1589
|
+
getConfigPath() {
|
|
1590
|
+
return getClaudeConfigPath();
|
|
1591
|
+
}
|
|
1592
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1593
|
+
const configPath = this.getConfigPath();
|
|
1594
|
+
const configDir = dirname5(configPath);
|
|
1595
|
+
if (!existsSync7(configDir)) {
|
|
1596
|
+
mkdirSync5(configDir, { recursive: true });
|
|
1597
|
+
}
|
|
1598
|
+
let config = {};
|
|
1599
|
+
if (existsSync7(configPath)) {
|
|
1600
|
+
try {
|
|
1601
|
+
config = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
1602
|
+
} catch {
|
|
1603
|
+
}
|
|
1604
|
+
const backupPath = `${configPath}.backup`;
|
|
1605
|
+
copyFileSync3(configPath, backupPath);
|
|
1606
|
+
}
|
|
1607
|
+
const mcpServers = config.mcpServers ?? {};
|
|
1608
|
+
mcpServers["tostudy"] = buildTostudyServerEntry(apiKey, mcpUrl, this.id);
|
|
1609
|
+
config.mcpServers = mcpServers;
|
|
1610
|
+
writeFileSync5(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1611
|
+
}
|
|
1612
|
+
async verify(apiKey, mcpUrl) {
|
|
1613
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
// src/ide-handlers/windsurf.ts
|
|
1618
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
1619
|
+
import { join as join5, dirname as dirname6 } from "path";
|
|
1620
|
+
import { homedir as homedir5 } from "os";
|
|
1621
|
+
var WindsurfHandler = class {
|
|
1622
|
+
id = "windsurf";
|
|
1623
|
+
name = "Windsurf";
|
|
1624
|
+
async detect() {
|
|
1625
|
+
const home = homedir5();
|
|
1626
|
+
return existsSync8(join5(home, ".codeium", "windsurf")) || existsSync8(join5(home, ".codeium", "windsurf", "mcp_config.json"));
|
|
1627
|
+
}
|
|
1628
|
+
getConfigPath() {
|
|
1629
|
+
return join5(homedir5(), ".codeium", "windsurf", "mcp_config.json");
|
|
1630
|
+
}
|
|
1631
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1632
|
+
const configPath = this.getConfigPath();
|
|
1633
|
+
const configDir = dirname6(configPath);
|
|
1634
|
+
if (!existsSync8(configDir)) {
|
|
1635
|
+
mkdirSync6(configDir, { recursive: true });
|
|
1636
|
+
}
|
|
1637
|
+
let config = {};
|
|
1638
|
+
if (existsSync8(configPath)) {
|
|
1639
|
+
try {
|
|
1640
|
+
config = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
1641
|
+
} catch {
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
const mcpServers = config.mcpServers ?? {};
|
|
1645
|
+
mcpServers.tostudy = buildTostudyServerEntry(apiKey, mcpUrl, this.id);
|
|
1646
|
+
config.mcpServers = mcpServers;
|
|
1647
|
+
writeFileSync6(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1648
|
+
}
|
|
1649
|
+
async verify(apiKey, mcpUrl) {
|
|
1650
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
// src/ide-handlers/opencode.ts
|
|
1655
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
|
|
1656
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1657
|
+
import { join as join6, dirname as dirname7 } from "path";
|
|
1658
|
+
import { homedir as homedir6 } from "os";
|
|
1659
|
+
var OpenCodeHandler = class {
|
|
1660
|
+
id = "opencode";
|
|
1661
|
+
name = "OpenCode";
|
|
1662
|
+
async detect() {
|
|
540
1663
|
try {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1664
|
+
execFileSync2("which", ["opencode"], { stdio: "ignore" });
|
|
1665
|
+
return true;
|
|
1666
|
+
} catch {
|
|
1667
|
+
try {
|
|
1668
|
+
execFileSync2("where", ["opencode"], { stdio: "ignore" });
|
|
1669
|
+
return true;
|
|
1670
|
+
} catch {
|
|
1671
|
+
return existsSync9(join6(homedir6(), ".opencode"));
|
|
1672
|
+
}
|
|
549
1673
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
1674
|
+
}
|
|
1675
|
+
getConfigPath() {
|
|
1676
|
+
return join6(homedir6(), ".opencode", "opencode.json");
|
|
1677
|
+
}
|
|
1678
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1679
|
+
const configPath = this.getConfigPath();
|
|
1680
|
+
const configDir = dirname7(configPath);
|
|
1681
|
+
if (!existsSync9(configDir)) {
|
|
1682
|
+
mkdirSync7(configDir, { recursive: true });
|
|
555
1683
|
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
1684
|
+
let config = {};
|
|
1685
|
+
if (existsSync9(configPath)) {
|
|
1686
|
+
try {
|
|
1687
|
+
config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
const mcp = config.mcp ?? {};
|
|
1692
|
+
mcp["tostudy"] = buildTostudyServerEntry(apiKey, mcpUrl, this.id);
|
|
1693
|
+
config.mcp = mcp;
|
|
1694
|
+
writeFileSync7(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1695
|
+
}
|
|
1696
|
+
async verify(apiKey, mcpUrl) {
|
|
1697
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
// src/ide-handlers/codex.ts
|
|
1702
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
1703
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1704
|
+
import { join as join7 } from "path";
|
|
1705
|
+
import { homedir as homedir7 } from "os";
|
|
1706
|
+
var CodexHandler = class {
|
|
1707
|
+
id = "codex";
|
|
1708
|
+
name = "Codex";
|
|
1709
|
+
async detect() {
|
|
565
1710
|
try {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
1711
|
+
execFileSync3("which", ["codex"], { stdio: "ignore" });
|
|
1712
|
+
return true;
|
|
1713
|
+
} catch {
|
|
1714
|
+
try {
|
|
1715
|
+
execFileSync3("where", ["codex"], { stdio: "ignore" });
|
|
1716
|
+
return true;
|
|
1717
|
+
} catch {
|
|
1718
|
+
return existsSync10(join7(homedir7(), ".codex"));
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
getConfigPath() {
|
|
1723
|
+
return join7(homedir7(), ".codex", "config.toml");
|
|
1724
|
+
}
|
|
1725
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1726
|
+
const args = buildMcpRemoteArgs(apiKey, mcpUrl, this.id);
|
|
1727
|
+
try {
|
|
1728
|
+
try {
|
|
1729
|
+
execFileSync3("codex", ["mcp", "remove", "tostudy"], { stdio: "ignore" });
|
|
1730
|
+
} catch {
|
|
1731
|
+
}
|
|
1732
|
+
execFileSync3("codex", ["mcp", "add", "tostudy", "--", "npx", ...args], { stdio: "inherit" });
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
throw new Error(
|
|
1735
|
+
`Failed to run 'codex mcp add'. Is Codex installed?
|
|
1736
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
async verify(apiKey, mcpUrl) {
|
|
1741
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
|
|
1745
|
+
// src/ide-handlers/antigravity.ts
|
|
1746
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
|
|
1747
|
+
import { join as join8, dirname as dirname8 } from "path";
|
|
1748
|
+
import { homedir as homedir8 } from "os";
|
|
1749
|
+
var AntigravityHandler = class {
|
|
1750
|
+
id = "antigravity";
|
|
1751
|
+
name = "Antigravity";
|
|
1752
|
+
async detect() {
|
|
1753
|
+
const home = homedir8();
|
|
1754
|
+
return existsSync11(join8(home, ".gemini", "antigravity")) || existsSync11(join8(home, ".gemini", "antigravity", "mcp_config.json"));
|
|
1755
|
+
}
|
|
1756
|
+
getConfigPath() {
|
|
1757
|
+
return join8(homedir8(), ".gemini", "antigravity", "mcp_config.json");
|
|
1758
|
+
}
|
|
1759
|
+
async writeConfig(apiKey, mcpUrl) {
|
|
1760
|
+
const configPath = this.getConfigPath();
|
|
1761
|
+
const configDir = dirname8(configPath);
|
|
1762
|
+
if (!existsSync11(configDir)) {
|
|
1763
|
+
mkdirSync8(configDir, { recursive: true });
|
|
1764
|
+
}
|
|
1765
|
+
let config = {};
|
|
1766
|
+
if (existsSync11(configPath)) {
|
|
1767
|
+
try {
|
|
1768
|
+
config = JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
1769
|
+
} catch {
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
const mcpServers = config.mcpServers ?? {};
|
|
1773
|
+
mcpServers.tostudy = buildTostudyServerEntry(apiKey, mcpUrl, this.id);
|
|
1774
|
+
config.mcpServers = mcpServers;
|
|
1775
|
+
writeFileSync8(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1776
|
+
}
|
|
1777
|
+
async verify(apiKey, mcpUrl) {
|
|
1778
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
|
|
1782
|
+
// src/ide-handlers/manual.ts
|
|
1783
|
+
import chalk3 from "chalk";
|
|
1784
|
+
var TOKEN_PLACEHOLDER = "<SUBSTITUA_PELO_SEU_TOKEN_TOSTUDY>";
|
|
1785
|
+
var ManualHandler = class {
|
|
1786
|
+
id = "manual";
|
|
1787
|
+
name = "Manual";
|
|
1788
|
+
async detect() {
|
|
1789
|
+
return true;
|
|
1790
|
+
}
|
|
1791
|
+
getConfigPath() {
|
|
1792
|
+
return "stdout";
|
|
1793
|
+
}
|
|
1794
|
+
async writeConfig(_apiKey, mcpUrl) {
|
|
1795
|
+
const config = {
|
|
1796
|
+
mcpServers: {
|
|
1797
|
+
tostudy: buildTostudyServerEntry(TOKEN_PLACEHOLDER, mcpUrl, this.id)
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
process.stdout.write("\n");
|
|
1801
|
+
process.stdout.write(
|
|
1802
|
+
chalk3.cyan(
|
|
1803
|
+
"Add the following to your MCP config and replace the token placeholder manually:\n"
|
|
1804
|
+
)
|
|
1805
|
+
);
|
|
1806
|
+
process.stdout.write("\n");
|
|
1807
|
+
process.stdout.write(JSON.stringify(config, null, 2));
|
|
1808
|
+
process.stdout.write("\n\n");
|
|
1809
|
+
}
|
|
1810
|
+
async verify(apiKey, mcpUrl) {
|
|
1811
|
+
return verifyHeartbeat(apiKey, mcpUrl);
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1815
|
+
// src/ide-handlers/index.ts
|
|
1816
|
+
var handlers = {
|
|
1817
|
+
"claude-code": () => new ClaudeCodeHandler(),
|
|
1818
|
+
"cursor": () => new CursorHandler(),
|
|
1819
|
+
"vscode": () => new VSCodeHandler(),
|
|
1820
|
+
"desktop": () => new DesktopHandler(),
|
|
1821
|
+
"windsurf": () => new WindsurfHandler(),
|
|
1822
|
+
"opencode": () => new OpenCodeHandler(),
|
|
1823
|
+
"codex": () => new CodexHandler(),
|
|
1824
|
+
"antigravity": () => new AntigravityHandler(),
|
|
1825
|
+
"manual": () => new ManualHandler()
|
|
1826
|
+
};
|
|
1827
|
+
function getIDEHandler(ide) {
|
|
1828
|
+
const factory = handlers[ide];
|
|
1829
|
+
if (!factory) {
|
|
1830
|
+
throw new Error(`Unknown IDE: ${ide}. Supported: ${Object.keys(handlers).join(", ")}`);
|
|
1831
|
+
}
|
|
1832
|
+
return factory();
|
|
1833
|
+
}
|
|
1834
|
+
function getAllIDEHandlers() {
|
|
1835
|
+
return Object.values(handlers).map((factory) => factory());
|
|
1836
|
+
}
|
|
1837
|
+
async function detectInstalledIDEs() {
|
|
1838
|
+
const all = getAllIDEHandlers();
|
|
1839
|
+
const results = [];
|
|
1840
|
+
for (const handler of all) {
|
|
1841
|
+
if (await handler.detect()) {
|
|
1842
|
+
results.push(handler);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return results;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/messages.ts
|
|
1849
|
+
var NEXT_STEPS_TITLE = " Pr\xF3ximos passos:";
|
|
1850
|
+
var ASSISTANT_COURSES_PROMPT = ' Pe\xE7a ao assistente: "mostre meus cursos"';
|
|
1851
|
+
var COURSES_TOOL_HINT = ' Use a ferramenta "courses" para ver seus cursos';
|
|
1852
|
+
var API_KEY_DEPRECATION_WARNING = "\u26A0 The --api-key flag is deprecated and will be removed in a future version. Use the default OAuth flow instead: npx @tostudy-ai/mcp-setup";
|
|
1853
|
+
|
|
1854
|
+
// src/oauth-server.ts
|
|
1855
|
+
import http from "http";
|
|
1856
|
+
|
|
1857
|
+
// src/callback-page.ts
|
|
1858
|
+
function getCallbackPageHtml() {
|
|
1859
|
+
return `<!DOCTYPE html>
|
|
1860
|
+
<html lang="pt-BR">
|
|
1861
|
+
<head>
|
|
1862
|
+
<meta charset="utf-8">
|
|
1863
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1864
|
+
<title>ToStudy \u2014 Autenticado</title>
|
|
1865
|
+
<style>
|
|
1866
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1867
|
+
body {
|
|
1868
|
+
background: #0a0a0a;
|
|
1869
|
+
color: #fafafa;
|
|
1870
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1871
|
+
display: flex;
|
|
1872
|
+
align-items: center;
|
|
1873
|
+
justify-content: center;
|
|
1874
|
+
min-height: 100vh;
|
|
1875
|
+
}
|
|
1876
|
+
.container {
|
|
1877
|
+
text-align: center;
|
|
1878
|
+
padding: 2rem;
|
|
1879
|
+
}
|
|
1880
|
+
.checkmark-circle {
|
|
1881
|
+
width: 80px;
|
|
1882
|
+
height: 80px;
|
|
1883
|
+
border-radius: 50%;
|
|
1884
|
+
border: 3px solid #22d3ee;
|
|
1885
|
+
display: flex;
|
|
1886
|
+
align-items: center;
|
|
1887
|
+
justify-content: center;
|
|
1888
|
+
margin: 0 auto 1.5rem;
|
|
1889
|
+
animation: scaleIn 0.4s ease-out;
|
|
1890
|
+
}
|
|
1891
|
+
.checkmark {
|
|
1892
|
+
width: 40px;
|
|
1893
|
+
height: 40px;
|
|
1894
|
+
stroke: #22d3ee;
|
|
1895
|
+
stroke-width: 3;
|
|
1896
|
+
fill: none;
|
|
1897
|
+
stroke-linecap: round;
|
|
1898
|
+
stroke-linejoin: round;
|
|
1899
|
+
stroke-dasharray: 50;
|
|
1900
|
+
stroke-dashoffset: 50;
|
|
1901
|
+
animation: draw 0.5s ease-out 0.3s forwards;
|
|
1902
|
+
}
|
|
1903
|
+
h1 {
|
|
1904
|
+
font-size: 1.5rem;
|
|
1905
|
+
font-weight: 600;
|
|
1906
|
+
margin-bottom: 0.5rem;
|
|
1907
|
+
}
|
|
1908
|
+
.subtitle {
|
|
1909
|
+
color: #a1a1aa;
|
|
1910
|
+
font-size: 0.95rem;
|
|
1911
|
+
margin-bottom: 0.25rem;
|
|
1912
|
+
}
|
|
1913
|
+
.brand {
|
|
1914
|
+
color: #22d3ee;
|
|
1915
|
+
font-size: 0.85rem;
|
|
1916
|
+
margin-top: 2rem;
|
|
1917
|
+
letter-spacing: 0.05em;
|
|
1918
|
+
}
|
|
1919
|
+
@keyframes scaleIn {
|
|
1920
|
+
from { transform: scale(0); opacity: 0; }
|
|
1921
|
+
to { transform: scale(1); opacity: 1; }
|
|
1922
|
+
}
|
|
1923
|
+
@keyframes draw {
|
|
1924
|
+
to { stroke-dashoffset: 0; }
|
|
1925
|
+
}
|
|
1926
|
+
</style>
|
|
1927
|
+
</head>
|
|
1928
|
+
<body>
|
|
1929
|
+
<div class="container">
|
|
1930
|
+
<div class="checkmark-circle">
|
|
1931
|
+
<svg class="checkmark" viewBox="0 0 40 40">
|
|
1932
|
+
<polyline points="12,20 18,26 28,14" />
|
|
1933
|
+
</svg>
|
|
1934
|
+
</div>
|
|
1935
|
+
<h1>Autenticado com sucesso!</h1>
|
|
1936
|
+
<p class="subtitle">Pode voltar ao terminal.</p>
|
|
1937
|
+
<p class="subtitle" id="close-msg">Esta aba ser\xE1 fechada automaticamente.</p>
|
|
1938
|
+
<p class="brand">ToStudy</p>
|
|
1939
|
+
</div>
|
|
1940
|
+
<script>
|
|
1941
|
+
setTimeout(function() {
|
|
1942
|
+
window.close();
|
|
1943
|
+
document.getElementById('close-msg').textContent = 'Pode fechar esta aba manualmente.';
|
|
1944
|
+
}, 3000);
|
|
1945
|
+
</script>
|
|
1946
|
+
</body>
|
|
1947
|
+
</html>`;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// src/oauth-server.ts
|
|
1951
|
+
function startCallbackServer(port) {
|
|
1952
|
+
return new Promise((resolve, reject) => {
|
|
1953
|
+
const server = http.createServer((req, res) => {
|
|
1954
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
1955
|
+
if (url.pathname === "/callback") {
|
|
1956
|
+
const code = url.searchParams.get("code");
|
|
1957
|
+
if (code) {
|
|
1958
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1959
|
+
res.end(getCallbackPageHtml());
|
|
1960
|
+
server.close();
|
|
1961
|
+
resolve({ code });
|
|
1962
|
+
} else {
|
|
1963
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
1964
|
+
res.end("Missing code");
|
|
575
1965
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
1966
|
+
} else {
|
|
1967
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1968
|
+
res.end("Not found");
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
server.listen(port, () => {
|
|
1972
|
+
});
|
|
1973
|
+
server.on("error", (err) => {
|
|
1974
|
+
if (err.code === "EADDRINUSE") {
|
|
1975
|
+
reject(new Error(`Porta ${port} em uso. Tente novamente ou use --api-key.`));
|
|
1976
|
+
} else {
|
|
1977
|
+
reject(err);
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
const timeoutId = setTimeout(() => {
|
|
1981
|
+
server.close();
|
|
1982
|
+
reject(new Error("Login timeout \u2014 nenhuma resposta em 2 minutos."));
|
|
1983
|
+
}, 12e4);
|
|
1984
|
+
timeoutId.unref();
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
// src/commands/init.ts
|
|
1989
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
1990
|
+
import { dirname as dirname9, join as join10 } from "path";
|
|
1991
|
+
import chalk4 from "chalk";
|
|
1992
|
+
|
|
1993
|
+
// ../mcp-cli-core/src/detect-ide.ts
|
|
1994
|
+
var IDE_DETECTION_RULES = [
|
|
1995
|
+
{
|
|
1996
|
+
id: "claude-code",
|
|
1997
|
+
name: "Claude Code",
|
|
1998
|
+
envVars: ["CLAUDE_CODE"]
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
id: "cursor",
|
|
2002
|
+
name: "Cursor",
|
|
2003
|
+
envVars: ["CURSOR_TRACE_ID", "CURSOR_CHANNEL"]
|
|
2004
|
+
},
|
|
2005
|
+
{
|
|
2006
|
+
id: "codex",
|
|
2007
|
+
name: "Codex",
|
|
2008
|
+
envVars: ["CODEX"]
|
|
2009
|
+
},
|
|
2010
|
+
{
|
|
2011
|
+
id: "windsurf",
|
|
2012
|
+
name: "Windsurf",
|
|
2013
|
+
envVars: ["WINDSURF_PID"]
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
id: "vscode",
|
|
2017
|
+
name: "VS Code",
|
|
2018
|
+
envVars: ["VSCODE_PID", "VSCODE_IPC_HOOK"]
|
|
2019
|
+
}
|
|
2020
|
+
];
|
|
2021
|
+
async function detectRunningIDE() {
|
|
2022
|
+
for (const rule of IDE_DETECTION_RULES) {
|
|
2023
|
+
if (rule.envVars.some((envVar) => process.env[envVar] !== void 0)) {
|
|
2024
|
+
return {
|
|
2025
|
+
id: rule.id,
|
|
2026
|
+
name: rule.name,
|
|
2027
|
+
detected: true
|
|
2028
|
+
};
|
|
579
2029
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
2030
|
+
}
|
|
2031
|
+
if (process.env.TERM_PROGRAM?.toLowerCase().includes("vscode")) {
|
|
2032
|
+
return {
|
|
2033
|
+
id: "vscode",
|
|
2034
|
+
name: "VS Code",
|
|
2035
|
+
detected: true
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// ../mcp-cli-core/src/detect-node.ts
|
|
2042
|
+
import * as childProcess from "child_process";
|
|
2043
|
+
|
|
2044
|
+
// ../mcp-cli-core/src/detect-workspace.ts
|
|
2045
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2046
|
+
import { join as join9 } from "path";
|
|
2047
|
+
async function detectWorkspaceScenario(cwd) {
|
|
2048
|
+
const hasAgentsMd = existsSync12(join9(cwd, "AGENTS.md")) || existsSync12(join9(cwd, "CLAUDE.md"));
|
|
2049
|
+
const hasCursorRules = existsSync12(join9(cwd, ".cursor", "rules"));
|
|
2050
|
+
const hasCopilotInstructions = existsSync12(join9(cwd, ".github", "copilot-instructions.md"));
|
|
2051
|
+
return {
|
|
2052
|
+
scenario: hasAgentsMd ? "existing" : "empty",
|
|
2053
|
+
cwd,
|
|
2054
|
+
hasAgentsMd,
|
|
2055
|
+
hasCursorRules,
|
|
2056
|
+
hasCopilotInstructions
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// ../mcp-cli-core/src/generate-instructions.ts
|
|
2061
|
+
var CLI_COMMANDS = [
|
|
2062
|
+
{ command: "tostudy start", description: "Carrega o pr\xF3ximo m\xF3dulo", category: "learning" },
|
|
2063
|
+
{ command: "tostudy next", description: "Avan\xE7a para a pr\xF3xima li\xE7\xE3o", category: "learning" },
|
|
2064
|
+
{
|
|
2065
|
+
command: "tostudy hint",
|
|
2066
|
+
description: "Mostra dicas progressivas para o exerc\xEDcio atual",
|
|
2067
|
+
category: "learning"
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
command: "tostudy validate",
|
|
2071
|
+
description: "Valida o exerc\xEDcio antes de avan\xE7ar",
|
|
2072
|
+
category: "learning"
|
|
2073
|
+
},
|
|
2074
|
+
{ command: "tostudy progress", description: "Mostra o progresso do curso", category: "learning" },
|
|
2075
|
+
{
|
|
2076
|
+
command: "tostudy workspace",
|
|
2077
|
+
description: "Cria ou atualiza o workspace local",
|
|
2078
|
+
category: "workspace"
|
|
2079
|
+
}
|
|
2080
|
+
];
|
|
2081
|
+
function formatModuleMarker(module, currentModule) {
|
|
2082
|
+
if (module.completed) {
|
|
2083
|
+
return "\u2705";
|
|
2084
|
+
}
|
|
2085
|
+
if (module.order === currentModule) {
|
|
2086
|
+
return "\u{1F504}";
|
|
2087
|
+
}
|
|
2088
|
+
return "\u2B1A";
|
|
2089
|
+
}
|
|
2090
|
+
function formatModuleList(modules, currentModule) {
|
|
2091
|
+
return [...modules].sort((left, right) => left.order - right.order).map((module) => {
|
|
2092
|
+
const suffix = module.order === currentModule ? " (atual)" : "";
|
|
2093
|
+
return `${module.order}. ${formatModuleMarker(module, currentModule)} ${module.title}${suffix}`;
|
|
2094
|
+
}).join("\n");
|
|
2095
|
+
}
|
|
2096
|
+
function formatToolList(tools) {
|
|
2097
|
+
return tools.map((tool) => `- \`${tool.command}\` \u2014 ${tool.description}`).join("\n");
|
|
2098
|
+
}
|
|
2099
|
+
function generateMcpContent(course) {
|
|
2100
|
+
const learningTools = course.availableTools.filter((tool) => tool.category === "learning");
|
|
2101
|
+
const workspaceTools = course.availableTools.filter((tool) => tool.category === "workspace");
|
|
2102
|
+
const hintTool = learningTools.find((tool) => tool.command.includes("hint"));
|
|
2103
|
+
const validateTool = learningTools.find((tool) => tool.command.includes("validate"));
|
|
2104
|
+
return `# ${course.title} \u2014 Guia de Estudo
|
|
2105
|
+
|
|
2106
|
+
## Progresso Atual
|
|
2107
|
+
|
|
2108
|
+
- M\xF3dulo ${course.currentModule} de ${course.totalModules} (${course.progressPercent}% completo)
|
|
2109
|
+
- Li\xE7\xE3o atual: ${course.currentModule}.${course.currentLesson} \u2014 ${course.currentLessonTitle}
|
|
2110
|
+
|
|
2111
|
+
## Tools Dispon\xEDveis (MCP)
|
|
2112
|
+
|
|
2113
|
+
### Aprendizado
|
|
2114
|
+
|
|
2115
|
+
${formatToolList(learningTools)}
|
|
2116
|
+
|
|
2117
|
+
${workspaceTools.length > 0 ? `### Workspace
|
|
2118
|
+
|
|
2119
|
+
${formatToolList(workspaceTools)}
|
|
2120
|
+
|
|
2121
|
+
` : ""}## Como Guiar o Aluno
|
|
2122
|
+
|
|
2123
|
+
- Apresente o conte\xFAdo da li\xE7\xE3o antes de propor exerc\xEDcios
|
|
2124
|
+
${hintTool ? `- Use \`${hintTool.command}\` antes de entregar a resposta` : ""}
|
|
2125
|
+
${validateTool ? `- Valide com \`${validateTool.command}\` antes de avan\xE7ar` : ""}
|
|
2126
|
+
- Respeite a sequ\xEAncia dos m\xF3dulos
|
|
2127
|
+
|
|
2128
|
+
## M\xF3dulos
|
|
2129
|
+
|
|
2130
|
+
${formatModuleList(course.modules, course.currentModule)}
|
|
2131
|
+
|
|
2132
|
+
---
|
|
2133
|
+
|
|
2134
|
+
_ToStudy Platform \u2014 Criado por ${course.creator.name}_
|
|
2135
|
+
_Atualizado via \`tostudy sync\`_`;
|
|
2136
|
+
}
|
|
2137
|
+
function generateCliContent(course) {
|
|
2138
|
+
const learningCommands = CLI_COMMANDS.filter((tool) => tool.category === "learning");
|
|
2139
|
+
const workspaceCommands = CLI_COMMANDS.filter((tool) => tool.category === "workspace");
|
|
2140
|
+
return `# ${course.title} \u2014 Guia de Estudo
|
|
2141
|
+
|
|
2142
|
+
## Progresso Atual
|
|
2143
|
+
|
|
2144
|
+
- M\xF3dulo ${course.currentModule} de ${course.totalModules} (${course.progressPercent}% completo)
|
|
2145
|
+
- Li\xE7\xE3o atual: ${course.currentModule}.${course.currentLesson} \u2014 ${course.currentLessonTitle}
|
|
2146
|
+
|
|
2147
|
+
## Comandos CLI Dispon\xEDveis
|
|
2148
|
+
|
|
2149
|
+
### Aprendizado
|
|
2150
|
+
|
|
2151
|
+
${formatToolList(learningCommands)}
|
|
2152
|
+
|
|
2153
|
+
### Workspace
|
|
2154
|
+
|
|
2155
|
+
${formatToolList(workspaceCommands)}
|
|
2156
|
+
|
|
2157
|
+
## Como Guiar o Aluno
|
|
2158
|
+
|
|
2159
|
+
- Instrua o aluno a rodar comandos no terminal
|
|
2160
|
+
- Leia a sa\xEDda dos comandos para orientar o pr\xF3ximo passo
|
|
2161
|
+
- Use \`tostudy hint\` antes de dar respostas diretas
|
|
2162
|
+
- Respeite a sequ\xEAncia dos m\xF3dulos
|
|
2163
|
+
|
|
2164
|
+
## M\xF3dulos
|
|
2165
|
+
|
|
2166
|
+
${formatModuleList(course.modules, course.currentModule)}
|
|
2167
|
+
|
|
2168
|
+
---
|
|
2169
|
+
|
|
2170
|
+
_ToStudy Platform \u2014 Criado por ${course.creator.name}_
|
|
2171
|
+
_Atualizado via \`tostudy sync\`_`;
|
|
2172
|
+
}
|
|
2173
|
+
function generateInstructions(course, options) {
|
|
2174
|
+
if (options.mode === "cli") {
|
|
2175
|
+
return generateCliContent(course);
|
|
2176
|
+
}
|
|
2177
|
+
return generateMcpContent(course);
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// ../mcp-cli-core/src/render-for-ide.ts
|
|
2181
|
+
function getAgentsMdPath(scenario) {
|
|
2182
|
+
return scenario === "existing" ? ".tostudy/AGENTS.md" : "AGENTS.md";
|
|
2183
|
+
}
|
|
2184
|
+
function renderCursorMdc(content) {
|
|
2185
|
+
const title = content.match(/^# (.+)$/m)?.[1] ?? "ToStudy Course Guide";
|
|
2186
|
+
return {
|
|
2187
|
+
filePath: ".cursor/rules/tostudy.mdc",
|
|
2188
|
+
content: `---
|
|
2189
|
+
description: ${title}
|
|
2190
|
+
globs: ["**/*"]
|
|
2191
|
+
alwaysApply: true
|
|
2192
|
+
---
|
|
2193
|
+
|
|
2194
|
+
${content}`
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
function renderForIDE(content, ide, scenario) {
|
|
2198
|
+
switch (ide) {
|
|
2199
|
+
case "cursor":
|
|
2200
|
+
return renderCursorMdc(content);
|
|
2201
|
+
case "copilot":
|
|
2202
|
+
return {
|
|
2203
|
+
filePath: ".github/copilot-instructions.md",
|
|
2204
|
+
content
|
|
2205
|
+
};
|
|
2206
|
+
case "claude-code":
|
|
2207
|
+
case "codex":
|
|
2208
|
+
case "vscode":
|
|
2209
|
+
case "windsurf":
|
|
2210
|
+
case "opencode":
|
|
2211
|
+
case "antigravity":
|
|
2212
|
+
case "manual":
|
|
2213
|
+
default:
|
|
2214
|
+
return {
|
|
2215
|
+
filePath: getAgentsMdPath(scenario),
|
|
2216
|
+
content
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
// src/commands/shared.ts
|
|
2222
|
+
var COURSE_ID_COMMENT_PREFIX = "<!-- tostudy-course-id:";
|
|
2223
|
+
var DEFAULT_AVAILABLE_TOOLS = [
|
|
2224
|
+
{ command: "start_module", description: "Carrega o pr\xF3ximo m\xF3dulo", category: "learning" },
|
|
2225
|
+
{ command: "next_lesson", description: "Avan\xE7a para a pr\xF3xima li\xE7\xE3o", category: "learning" },
|
|
2226
|
+
{ command: "get_hint", description: "Mostra dicas progressivas", category: "learning" },
|
|
2227
|
+
{ command: "validate_solution", description: "Valida o exerc\xEDcio atual", category: "learning" },
|
|
2228
|
+
{ command: "get_progress", description: "Mostra o progresso do curso", category: "learning" },
|
|
2229
|
+
{
|
|
2230
|
+
command: "setup_workspace",
|
|
2231
|
+
description: "Configura o workspace do aluno",
|
|
2232
|
+
category: "workspace"
|
|
2233
|
+
}
|
|
2234
|
+
];
|
|
2235
|
+
function normalizeCourseContext(course) {
|
|
2236
|
+
if ("availableTools" in course) {
|
|
2237
|
+
return course;
|
|
2238
|
+
}
|
|
2239
|
+
return {
|
|
2240
|
+
id: course.id,
|
|
2241
|
+
title: course.title,
|
|
2242
|
+
description: course.description ?? "",
|
|
2243
|
+
modules: course.modules,
|
|
2244
|
+
currentModule: course.currentModule,
|
|
2245
|
+
currentLesson: course.currentLesson,
|
|
2246
|
+
currentLessonTitle: course.currentLessonTitle,
|
|
2247
|
+
progressPercent: course.progressPercent,
|
|
2248
|
+
totalModules: course.totalModules || course.modules.length || 1,
|
|
2249
|
+
creator: { name: course.creator.name },
|
|
2250
|
+
availableTools: DEFAULT_AVAILABLE_TOOLS
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
async function fetchCourses(apiKey, platformUrl) {
|
|
2254
|
+
try {
|
|
2255
|
+
const baseUrl = platformUrl.replace(/\/$/, "");
|
|
2256
|
+
const response = await fetch(`${baseUrl}/api/mcp/courses`, {
|
|
2257
|
+
headers: {
|
|
2258
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2259
|
+
"Content-Type": "application/json"
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
if (!response.ok) {
|
|
2263
|
+
return [];
|
|
585
2264
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
2265
|
+
const data = await response.json();
|
|
2266
|
+
return (Array.isArray(data.courses) ? data.courses : []).map(
|
|
2267
|
+
(course) => normalizeCourseContext(course)
|
|
2268
|
+
);
|
|
2269
|
+
} catch {
|
|
2270
|
+
return [];
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
async function fetchCourseContext(courseId, apiKey, platformUrl) {
|
|
2274
|
+
const courses = await fetchCourses(apiKey, platformUrl);
|
|
2275
|
+
return courses.find((course) => course.id === courseId) ?? null;
|
|
2276
|
+
}
|
|
2277
|
+
async function detectInstructionMode(apiKey, platformUrl, verifyHeartbeat2) {
|
|
2278
|
+
return await verifyHeartbeat2(apiKey, platformUrl) ? "mcp" : "cli";
|
|
2279
|
+
}
|
|
2280
|
+
function appendCourseMetadata(content, courseId) {
|
|
2281
|
+
return `${content}
|
|
2282
|
+
|
|
2283
|
+
${COURSE_ID_COMMENT_PREFIX} ${courseId} -->`;
|
|
2284
|
+
}
|
|
2285
|
+
function extractCourseId(content) {
|
|
2286
|
+
const match = content.match(/<!-- tostudy-course-id:\s*([a-zA-Z0-9-_]+)\s*-->/);
|
|
2287
|
+
return match?.[1] ?? null;
|
|
2288
|
+
}
|
|
2289
|
+
function inferInstructionTarget(filePath) {
|
|
2290
|
+
if (filePath === ".cursor/rules/tostudy.mdc") {
|
|
2291
|
+
return { ide: "cursor", scenario: "empty" };
|
|
2292
|
+
}
|
|
2293
|
+
if (filePath === ".github/copilot-instructions.md") {
|
|
2294
|
+
return { ide: "copilot", scenario: "empty" };
|
|
2295
|
+
}
|
|
2296
|
+
if (filePath === ".tostudy/AGENTS.md") {
|
|
2297
|
+
return { ide: "codex", scenario: "existing" };
|
|
2298
|
+
}
|
|
2299
|
+
return { ide: "manual", scenario: "empty" };
|
|
2300
|
+
}
|
|
2301
|
+
function resolveDetectedIde(ide) {
|
|
2302
|
+
return ide?.id ?? "manual";
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// src/commands/init.ts
|
|
2306
|
+
function println4(message = "") {
|
|
2307
|
+
process.stdout.write(`${message}
|
|
2308
|
+
`);
|
|
2309
|
+
}
|
|
2310
|
+
async function runInit(options, dependencies = {}) {
|
|
2311
|
+
const cwd = dependencies.cwd ?? process.cwd();
|
|
2312
|
+
const detectRunningIDE2 = dependencies.detectRunningIDE ?? detectRunningIDE;
|
|
2313
|
+
const detectWorkspaceScenario2 = dependencies.detectWorkspaceScenario ?? detectWorkspaceScenario;
|
|
2314
|
+
const verify = dependencies.verifyHeartbeat ?? verifyHeartbeat;
|
|
2315
|
+
const fetchCourses2 = dependencies.fetchCourses ?? fetchCourses;
|
|
2316
|
+
const fetchCourse = dependencies.fetchCourse ?? fetchCourseContext;
|
|
2317
|
+
const promptForCourse = dependencies.promptForCourse ?? promptCourseSelection;
|
|
2318
|
+
const confirmOverwrite = dependencies.confirmOverwrite ?? confirm;
|
|
2319
|
+
let selectedCourseId = options.courseId;
|
|
2320
|
+
let course = selectedCourseId ? await fetchCourse(selectedCourseId, options.apiKey, options.platformUrl) : null;
|
|
2321
|
+
if (!selectedCourseId) {
|
|
2322
|
+
const availableCourses = await fetchCourses2(options.apiKey, options.platformUrl);
|
|
2323
|
+
if (availableCourses.length === 0) {
|
|
2324
|
+
println4(
|
|
2325
|
+
chalk4.yellow(
|
|
2326
|
+
' Nenhum curso MCP ativo encontrado. Use "--course <id>" ou configure um curso compat\xEDvel na plataforma.'
|
|
2327
|
+
)
|
|
2328
|
+
);
|
|
2329
|
+
return { ok: false, reason: "course_required" };
|
|
2330
|
+
}
|
|
2331
|
+
if (availableCourses.length === 1) {
|
|
2332
|
+
course = availableCourses[0] ?? null;
|
|
2333
|
+
selectedCourseId = course?.id;
|
|
2334
|
+
println4(chalk4.cyan(` Curso selecionado automaticamente: ${course?.title}`));
|
|
2335
|
+
} else {
|
|
2336
|
+
const promptedCourseId = await promptForCourse(availableCourses);
|
|
2337
|
+
if (!promptedCourseId) {
|
|
2338
|
+
println4(chalk4.yellow(" Sele\xE7\xE3o de curso cancelada."));
|
|
2339
|
+
return { ok: false, reason: "cancelled" };
|
|
2340
|
+
}
|
|
2341
|
+
selectedCourseId = promptedCourseId;
|
|
2342
|
+
course = availableCourses.find((availableCourse) => availableCourse.id === selectedCourseId) ?? null;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
const ide = await detectRunningIDE2();
|
|
2346
|
+
const workspace = await detectWorkspaceScenario2(cwd);
|
|
2347
|
+
const mode = await detectInstructionMode(options.apiKey, options.platformUrl, verify);
|
|
2348
|
+
if (!course) {
|
|
2349
|
+
println4(chalk4.red(` N\xE3o foi poss\xEDvel carregar o curso ${selectedCourseId ?? "selecionado"}.`));
|
|
2350
|
+
return { ok: false, reason: "course_not_found" };
|
|
2351
|
+
}
|
|
2352
|
+
const instructionContent = appendCourseMetadata(
|
|
2353
|
+
generateInstructions(course, {
|
|
2354
|
+
mode,
|
|
2355
|
+
ide: resolveDetectedIde(ide),
|
|
2356
|
+
locale: "pt-BR"
|
|
2357
|
+
}),
|
|
2358
|
+
course.id
|
|
2359
|
+
);
|
|
2360
|
+
const rendered = renderForIDE(instructionContent, resolveDetectedIde(ide), workspace.scenario);
|
|
2361
|
+
const outputPath = join10(cwd, rendered.filePath);
|
|
2362
|
+
if (existsSync13(outputPath)) {
|
|
2363
|
+
const shouldOverwrite = await confirmOverwrite(
|
|
2364
|
+
` ${rendered.filePath} j\xE1 existe. Sobrescrever?`,
|
|
2365
|
+
true
|
|
2366
|
+
);
|
|
2367
|
+
if (!shouldOverwrite) {
|
|
2368
|
+
println4(chalk4.yellow(" Cancelado pelo usu\xE1rio."));
|
|
2369
|
+
return { ok: false, reason: "cancelled" };
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
mkdirSync9(dirname9(outputPath), { recursive: true });
|
|
2373
|
+
writeFileSync9(outputPath, rendered.content, "utf-8");
|
|
2374
|
+
println4(chalk4.green(` \u2713 Workspace configurado em ${rendered.filePath}`));
|
|
2375
|
+
return {
|
|
2376
|
+
ok: true,
|
|
2377
|
+
outputPath,
|
|
2378
|
+
mode,
|
|
2379
|
+
courseId: course.id
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// src/commands/sync.ts
|
|
2384
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
2385
|
+
import { dirname as dirname10, join as join11 } from "path";
|
|
2386
|
+
import chalk5 from "chalk";
|
|
2387
|
+
function println5(message = "") {
|
|
2388
|
+
process.stdout.write(`${message}
|
|
2389
|
+
`);
|
|
2390
|
+
}
|
|
2391
|
+
var INSTRUCTION_CANDIDATES = [
|
|
2392
|
+
"AGENTS.md",
|
|
2393
|
+
".tostudy/AGENTS.md",
|
|
2394
|
+
".cursor/rules/tostudy.mdc",
|
|
2395
|
+
".github/copilot-instructions.md"
|
|
598
2396
|
];
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
2397
|
+
function findExistingInstruction(cwd) {
|
|
2398
|
+
for (const candidate of INSTRUCTION_CANDIDATES) {
|
|
2399
|
+
const fullPath = join11(cwd, candidate);
|
|
2400
|
+
if (existsSync14(fullPath)) {
|
|
2401
|
+
return {
|
|
2402
|
+
relativePath: candidate,
|
|
2403
|
+
content: readFileSync9(fullPath, "utf-8")
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
async function runSync(options, dependencies = {}) {
|
|
2410
|
+
const cwd = dependencies.cwd ?? process.cwd();
|
|
2411
|
+
const verify = dependencies.verifyHeartbeat ?? verifyHeartbeat;
|
|
2412
|
+
const fetchCourse = dependencies.fetchCourse ?? fetchCourseContext;
|
|
2413
|
+
const existing = findExistingInstruction(cwd);
|
|
2414
|
+
if (!existing) {
|
|
2415
|
+
println5(
|
|
2416
|
+
chalk5.yellow(' Nenhum arquivo ToStudy encontrado. Rode "tostudy-mcp-setup init" primeiro.')
|
|
2417
|
+
);
|
|
2418
|
+
return { ok: false, reason: "instruction_missing" };
|
|
2419
|
+
}
|
|
2420
|
+
const courseId = options.courseId ?? extractCourseId(existing.content);
|
|
2421
|
+
if (!courseId) {
|
|
2422
|
+
println5(
|
|
2423
|
+
chalk5.yellow(
|
|
2424
|
+
' Metadata do curso ausente. Rode "tostudy-mcp-setup init --course <id>" novamente.'
|
|
2425
|
+
)
|
|
2426
|
+
);
|
|
2427
|
+
return { ok: false, reason: "course_metadata_missing" };
|
|
2428
|
+
}
|
|
2429
|
+
const mode = await detectInstructionMode(options.apiKey, options.platformUrl, verify);
|
|
2430
|
+
const course = await fetchCourse(courseId, options.apiKey, options.platformUrl);
|
|
2431
|
+
if (!course) {
|
|
2432
|
+
println5(chalk5.red(` N\xE3o foi poss\xEDvel sincronizar o curso ${courseId}.`));
|
|
2433
|
+
return { ok: false, reason: "course_not_found" };
|
|
2434
|
+
}
|
|
2435
|
+
const target = inferInstructionTarget(existing.relativePath);
|
|
2436
|
+
const instructionContent = appendCourseMetadata(
|
|
2437
|
+
generateInstructions(course, {
|
|
2438
|
+
mode,
|
|
2439
|
+
ide: target.ide,
|
|
2440
|
+
locale: "pt-BR"
|
|
2441
|
+
}),
|
|
2442
|
+
course.id
|
|
2443
|
+
);
|
|
2444
|
+
const rendered = renderForIDE(instructionContent, target.ide, target.scenario);
|
|
2445
|
+
const outputPath = join11(cwd, rendered.filePath);
|
|
2446
|
+
mkdirSync10(dirname10(outputPath), { recursive: true });
|
|
2447
|
+
writeFileSync10(outputPath, rendered.content, "utf-8");
|
|
2448
|
+
println5(chalk5.green(` \u2713 ${rendered.filePath} sincronizado`));
|
|
2449
|
+
return {
|
|
2450
|
+
ok: true,
|
|
2451
|
+
outputPath,
|
|
2452
|
+
courseId: course.id,
|
|
2453
|
+
mode
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/index.ts
|
|
2458
|
+
var VERSION = "1.3.0";
|
|
2459
|
+
var DEFAULT_PLATFORM_URL2 = "https://tostudy.ai";
|
|
2460
|
+
var OAUTH_PORT = 9877;
|
|
2461
|
+
function println6(message = "") {
|
|
2462
|
+
process.stdout.write(`${message}
|
|
2463
|
+
`);
|
|
2464
|
+
}
|
|
2465
|
+
function eprintln(message = "") {
|
|
2466
|
+
process.stderr.write(`${message}
|
|
2467
|
+
`);
|
|
2468
|
+
}
|
|
2469
|
+
function printBanner() {
|
|
2470
|
+
println6();
|
|
2471
|
+
println6(chalk6.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2472
|
+
println6(
|
|
2473
|
+
chalk6.cyan(" \u2551") + chalk6.white.bold(" ToStudy MCP Setup ") + chalk6.cyan("\u2551")
|
|
2474
|
+
);
|
|
2475
|
+
println6(chalk6.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
2476
|
+
println6();
|
|
2477
|
+
}
|
|
2478
|
+
async function validateApiKey(apiKey, platformUrl) {
|
|
2479
|
+
try {
|
|
2480
|
+
const response = await fetch(`${platformUrl}/api/mcp/heartbeat`, {
|
|
2481
|
+
method: "POST",
|
|
2482
|
+
headers: {
|
|
2483
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2484
|
+
"Content-Type": "application/json"
|
|
2485
|
+
},
|
|
2486
|
+
body: JSON.stringify({ timestamp: (/* @__PURE__ */ new Date()).toISOString() })
|
|
2487
|
+
});
|
|
2488
|
+
return response.ok || response.status === 204;
|
|
2489
|
+
} catch {
|
|
2490
|
+
println6(chalk6.yellow("! N\xE3o foi poss\xEDvel validar a API key (servidor offline?)"));
|
|
2491
|
+
println6(chalk6.yellow(" A configura\xE7\xE3o ser\xE1 salva mesmo assim."));
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
async function exchangeCodeForToken(code, platformUrl, client = "mcp") {
|
|
2496
|
+
const res = await fetch(`${platformUrl}/api/cli/auth/exchange`, {
|
|
2497
|
+
method: "POST",
|
|
2498
|
+
headers: { "Content-Type": "application/json" },
|
|
2499
|
+
body: JSON.stringify({ code, client })
|
|
2500
|
+
});
|
|
2501
|
+
if (!res.ok) {
|
|
2502
|
+
const body = await res.json().catch(() => ({}));
|
|
2503
|
+
throw new Error(body.error ?? `Falha na autentica\xE7\xE3o (${res.status})`);
|
|
2504
|
+
}
|
|
2505
|
+
return res.json();
|
|
2506
|
+
}
|
|
2507
|
+
async function configureIDEs(token, platformUrl) {
|
|
2508
|
+
const mcpUrl = resolveMcpServerUrl(platformUrl);
|
|
2509
|
+
const allDetected = await detectInstalledIDEs();
|
|
2510
|
+
const installedIDEs = allDetected.filter((h) => h.id !== "manual");
|
|
2511
|
+
if (installedIDEs.length === 0) {
|
|
2512
|
+
println6(chalk6.yellow(" Nenhuma IDE suportada detectada."));
|
|
2513
|
+
println6(chalk6.gray(" Use: npx @tostudy-ai/mcp-setup install --ide <nome> --key <token>"));
|
|
2514
|
+
return 0;
|
|
2515
|
+
}
|
|
2516
|
+
println6(chalk6.white(" IDEs detectadas:\n"));
|
|
2517
|
+
for (let i = 0; i < installedIDEs.length; i++) {
|
|
2518
|
+
println6(` ${chalk6.cyan(String(i + 1))}. ${installedIDEs[i].name}`);
|
|
2519
|
+
}
|
|
2520
|
+
println6(` ${chalk6.cyan("a")}. Todas`);
|
|
2521
|
+
println6();
|
|
2522
|
+
const answer = await prompt(
|
|
2523
|
+
" Quais configurar? (n\xFAmeros separados por v\xEDrgula, ou 'a' para todas) "
|
|
2524
|
+
);
|
|
2525
|
+
const trimmed = answer.trim().toLowerCase();
|
|
2526
|
+
let selectedIDEs;
|
|
2527
|
+
if (!trimmed || trimmed === "a" || trimmed === "all") {
|
|
2528
|
+
selectedIDEs = installedIDEs;
|
|
2529
|
+
} else {
|
|
2530
|
+
const indices = trimmed.split(/[,\s]+/).map((s) => parseInt(s, 10) - 1).filter((i) => i >= 0 && i < installedIDEs.length);
|
|
2531
|
+
if (indices.length === 0) {
|
|
2532
|
+
println6(chalk6.yellow(" Nenhuma IDE selecionada."));
|
|
2533
|
+
return 0;
|
|
2534
|
+
}
|
|
2535
|
+
selectedIDEs = indices.map((i) => installedIDEs[i]);
|
|
2536
|
+
}
|
|
2537
|
+
println6();
|
|
2538
|
+
const claudeCodeHandler = selectedIDEs.find((h) => h.id === "claude-code");
|
|
2539
|
+
let claudeCodeScope = "user";
|
|
2540
|
+
if (claudeCodeHandler) {
|
|
2541
|
+
claudeCodeScope = await promptClaudeCodeScope();
|
|
2542
|
+
println6();
|
|
2543
|
+
}
|
|
2544
|
+
let configured = 0;
|
|
2545
|
+
for (const handler of selectedIDEs) {
|
|
2546
|
+
process.stdout.write(` ${chalk6.gray("Configurando")} ${handler.name}... `);
|
|
606
2547
|
try {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
2548
|
+
if (handler.id === "claude-code") {
|
|
2549
|
+
await writeClaudeCodeConfig(handler, token, mcpUrl, claudeCodeScope);
|
|
2550
|
+
} else {
|
|
2551
|
+
await handler.writeConfig(token, mcpUrl);
|
|
2552
|
+
}
|
|
2553
|
+
println6(chalk6.green("OK"));
|
|
2554
|
+
configured++;
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
println6(chalk6.red("FALHOU"));
|
|
2557
|
+
eprintln(chalk6.gray(` ${error instanceof Error ? error.message : String(error)}`));
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
return configured;
|
|
2561
|
+
}
|
|
2562
|
+
async function writeClaudeCodeConfig(_handler, token, mcpUrl, scope) {
|
|
2563
|
+
const { execFileSync: execFileSync5 } = await import("child_process");
|
|
2564
|
+
const addArgs = buildClaudeCodeAddArgs(token, mcpUrl, scope);
|
|
2565
|
+
const removeArgs = buildClaudeCodeRemoveArgs(scope);
|
|
2566
|
+
try {
|
|
2567
|
+
execFileSync5("claude", removeArgs, { stdio: "ignore" });
|
|
2568
|
+
} catch {
|
|
2569
|
+
}
|
|
2570
|
+
execFileSync5("claude", addArgs, { stdio: "inherit" });
|
|
2571
|
+
}
|
|
2572
|
+
function printSuccessBanner(configuredCount) {
|
|
2573
|
+
println6();
|
|
2574
|
+
if (configuredCount > 0) {
|
|
2575
|
+
println6(chalk6.green(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2576
|
+
println6(
|
|
2577
|
+
chalk6.green(" \u2551") + chalk6.white.bold(
|
|
2578
|
+
` ${configuredCount} IDE${configuredCount > 1 ? "s" : ""} configurada${configuredCount > 1 ? "s" : ""} com sucesso!`.padEnd(
|
|
2579
|
+
42
|
|
2580
|
+
)
|
|
2581
|
+
) + chalk6.green("\u2551")
|
|
2582
|
+
);
|
|
2583
|
+
println6(chalk6.green(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
2584
|
+
} else {
|
|
2585
|
+
println6(chalk6.yellow(" Nenhuma IDE foi configurada."));
|
|
2586
|
+
println6(chalk6.gray(" Use: npx @tostudy-ai/mcp-setup install --ide <nome> --key <token>"));
|
|
2587
|
+
}
|
|
2588
|
+
println6();
|
|
2589
|
+
println6(chalk6.white(NEXT_STEPS_TITLE));
|
|
2590
|
+
println6(chalk6.gray(" 1.") + " Reinicie as IDEs configuradas");
|
|
2591
|
+
println6(chalk6.gray(" 2.") + ASSISTANT_COURSES_PROMPT.trimStart());
|
|
2592
|
+
println6();
|
|
2593
|
+
println6(chalk6.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2594
|
+
println6();
|
|
2595
|
+
println6(chalk6.white(" Prefere estudar pelo terminal?"));
|
|
2596
|
+
println6(chalk6.gray(" ") + chalk6.cyan("npm install -g @tostudy-ai/cli"));
|
|
2597
|
+
println6(
|
|
2598
|
+
chalk6.gray(" ") + chalk6.cyan("tostudy login") + chalk6.gray(" \u2192 ") + chalk6.cyan("tostudy courses")
|
|
2599
|
+
);
|
|
2600
|
+
println6();
|
|
2601
|
+
}
|
|
2602
|
+
async function setup(apiKey, platformUrl) {
|
|
2603
|
+
printBanner();
|
|
2604
|
+
const url = platformUrl || process.env.TOSTUDY_PLATFORM_URL || DEFAULT_PLATFORM_URL2;
|
|
2605
|
+
if (apiKey || process.env.TOSTUDY_API_KEY) {
|
|
2606
|
+
const key = apiKey || process.env.TOSTUDY_API_KEY;
|
|
2607
|
+
println6(chalk6.yellow(` ${API_KEY_DEPRECATION_WARNING}`));
|
|
2608
|
+
println6();
|
|
2609
|
+
println6(chalk6.gray(" Usando API key fornecida..."));
|
|
2610
|
+
process.stdout.write(chalk6.gray(" Validando... "));
|
|
2611
|
+
const valid = await validateApiKey(key, url);
|
|
2612
|
+
if (!valid) {
|
|
2613
|
+
println6(chalk6.red("FALHOU"));
|
|
2614
|
+
println6(chalk6.red(" API key inv\xE1lida ou expirada."));
|
|
2615
|
+
process.exit(1);
|
|
2616
|
+
}
|
|
2617
|
+
println6(chalk6.green("OK"));
|
|
2618
|
+
println6();
|
|
2619
|
+
const configured2 = await configureIDEs(key, url);
|
|
2620
|
+
printSuccessBanner(configured2);
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
println6(chalk6.gray(" Abrindo browser para autentica\xE7\xE3o...\n"));
|
|
2624
|
+
let code;
|
|
2625
|
+
try {
|
|
2626
|
+
const serverPromise = startCallbackServer(OAUTH_PORT);
|
|
2627
|
+
const authUrl = `${url}/api/cli/auth/authorize?port=${OAUTH_PORT}`;
|
|
2628
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
2629
|
+
const openArgs = process.platform === "win32" ? ["", authUrl] : [authUrl];
|
|
2630
|
+
execFile(openCmd, openArgs, (err) => {
|
|
2631
|
+
if (err) {
|
|
2632
|
+
println6(chalk6.yellow(" N\xE3o foi poss\xEDvel abrir o browser automaticamente."));
|
|
2633
|
+
println6(chalk6.gray(` Abra manualmente: ${authUrl}
|
|
2634
|
+
`));
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
const result = await serverPromise;
|
|
2638
|
+
code = result.code;
|
|
2639
|
+
} catch (err) {
|
|
2640
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2641
|
+
eprintln(chalk6.red(` Erro: ${msg}`));
|
|
2642
|
+
process.exit(1);
|
|
2643
|
+
}
|
|
2644
|
+
process.stdout.write(chalk6.gray(" Autenticando... "));
|
|
2645
|
+
let token;
|
|
2646
|
+
let userName;
|
|
2647
|
+
try {
|
|
2648
|
+
const result = await exchangeCodeForToken(code, url, "mcp");
|
|
2649
|
+
token = result.token;
|
|
2650
|
+
userName = result.userName;
|
|
2651
|
+
println6(chalk6.green("OK"));
|
|
2652
|
+
println6(chalk6.green(` Logado como ${userName}
|
|
2653
|
+
`));
|
|
2654
|
+
} catch (err) {
|
|
2655
|
+
println6(chalk6.red("FALHOU"));
|
|
2656
|
+
eprintln(chalk6.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
2657
|
+
process.exit(1);
|
|
2658
|
+
}
|
|
2659
|
+
println6(chalk6.gray(" Detectando IDEs...\n"));
|
|
2660
|
+
const configured = await configureIDEs(token, url);
|
|
2661
|
+
printSuccessBanner(configured);
|
|
2662
|
+
}
|
|
2663
|
+
async function uninstall() {
|
|
2664
|
+
printBanner();
|
|
2665
|
+
if (!isTostudyMcpConfigured()) {
|
|
2666
|
+
println6(chalk6.yellow("ToStudy MCP n\xE3o est\xE1 configurado."));
|
|
2667
|
+
process.exit(0);
|
|
2668
|
+
}
|
|
2669
|
+
const shouldUninstall = await confirm("Remover configura\xE7\xE3o do ToStudy MCP?", false);
|
|
2670
|
+
if (!shouldUninstall) {
|
|
2671
|
+
println6(chalk6.gray("Opera\xE7\xE3o cancelada."));
|
|
2672
|
+
process.exit(0);
|
|
2673
|
+
}
|
|
2674
|
+
process.stdout.write(chalk6.gray("Removendo configura\xE7\xE3o... "));
|
|
2675
|
+
try {
|
|
2676
|
+
removeTostudyMcpServer();
|
|
2677
|
+
println6(chalk6.green("OK"));
|
|
2678
|
+
println6();
|
|
2679
|
+
println6(chalk6.green("Configura\xE7\xE3o removida com sucesso."));
|
|
2680
|
+
println6(chalk6.gray("Reinicie o Claude Code para aplicar as mudan\xE7as."));
|
|
2681
|
+
println6();
|
|
2682
|
+
} catch (error) {
|
|
2683
|
+
println6(chalk6.red("FALHOU"));
|
|
2684
|
+
eprintln(error instanceof Error ? error.message : String(error));
|
|
2685
|
+
process.exit(1);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
function printStep(step, total, title) {
|
|
2689
|
+
println6();
|
|
2690
|
+
println6(chalk6.cyan(`\u2501\u2501\u2501 Passo ${step}/${total}: ${title} \u2501\u2501\u2501`));
|
|
2691
|
+
println6();
|
|
2692
|
+
}
|
|
2693
|
+
function printIDEDetection(installedIDEs) {
|
|
2694
|
+
if (installedIDEs.length === 0) {
|
|
2695
|
+
println6(chalk6.yellow(" Nenhuma IDE suportada detectada."));
|
|
2696
|
+
println6(chalk6.gray(" O setup continuar\xE1 para Claude Code."));
|
|
2697
|
+
} else {
|
|
2698
|
+
println6(chalk6.green(" IDEs detectadas:"));
|
|
2699
|
+
for (const ide of installedIDEs) {
|
|
2700
|
+
const version = ide.version ? chalk6.gray(` (${ide.version})`) : "";
|
|
2701
|
+
println6(` ${chalk6.green("\u2713")} ${ide.name}${version}`);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
async function wizard(options) {
|
|
2706
|
+
const TOTAL_STEPS = 4;
|
|
2707
|
+
println6();
|
|
2708
|
+
println6(chalk6.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2709
|
+
println6(
|
|
2710
|
+
chalk6.cyan(" \u2551") + chalk6.white.bold(" ToStudy MCP Setup Wizard ") + chalk6.cyan("\u2551")
|
|
2711
|
+
);
|
|
2712
|
+
println6(
|
|
2713
|
+
chalk6.cyan(" \u2551") + chalk6.gray(" Configura\xE7\xE3o guiada passo a passo ") + chalk6.cyan("\u2551")
|
|
2714
|
+
);
|
|
2715
|
+
println6(chalk6.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
2716
|
+
println6();
|
|
2717
|
+
println6(chalk6.gray(" Este assistente vai configurar o Claude Code para"));
|
|
2718
|
+
println6(chalk6.gray(" conectar ao servidor MCP da plataforma ToStudy."));
|
|
2719
|
+
println6();
|
|
2720
|
+
printStep(1, TOTAL_STEPS, "Detectando ambiente");
|
|
2721
|
+
process.stdout.write(" Verificando Claude Code... ");
|
|
2722
|
+
if (!isClaudeInstalled()) {
|
|
2723
|
+
println6(chalk6.red("N\xC3O ENCONTRADO"));
|
|
2724
|
+
println6();
|
|
2725
|
+
println6(chalk6.red(" Claude Code n\xE3o est\xE1 instalado."));
|
|
2726
|
+
println6();
|
|
2727
|
+
println6(" Por favor, instale primeiro:");
|
|
2728
|
+
println6(chalk6.cyan(" https://claude.ai/download"));
|
|
2729
|
+
println6();
|
|
2730
|
+
process.exit(1);
|
|
2731
|
+
}
|
|
2732
|
+
println6(chalk6.green("OK"));
|
|
2733
|
+
const configPath = getClaudeConfigPath();
|
|
2734
|
+
println6(chalk6.gray(` Config: ${configPath}`));
|
|
2735
|
+
process.stdout.write(" Detectando IDEs... ");
|
|
2736
|
+
const installedIDEs = getInstalledIDEs();
|
|
2737
|
+
println6(chalk6.green("OK"));
|
|
2738
|
+
printIDEDetection(installedIDEs);
|
|
2739
|
+
process.stdout.write(" Verificando configura\xE7\xE3o atual... ");
|
|
2740
|
+
const alreadyConfigured = isTostudyMcpConfigured();
|
|
2741
|
+
if (alreadyConfigured) {
|
|
2742
|
+
println6(chalk6.yellow("J\xC1 CONFIGURADO"));
|
|
2743
|
+
println6();
|
|
2744
|
+
const overwrite = await confirm(" Deseja reconfigurar?", false);
|
|
2745
|
+
if (!overwrite) {
|
|
2746
|
+
println6();
|
|
2747
|
+
println6(chalk6.gray(" Opera\xE7\xE3o cancelada."));
|
|
2748
|
+
process.exit(0);
|
|
2749
|
+
}
|
|
2750
|
+
} else {
|
|
2751
|
+
println6(chalk6.gray("N\xE3o configurado"));
|
|
2752
|
+
}
|
|
2753
|
+
printStep(2, TOTAL_STEPS, "Configurando API Key");
|
|
2754
|
+
println6(chalk6.gray(" A API key conecta o Claude Code a sua conta na plataforma."));
|
|
2755
|
+
println6();
|
|
2756
|
+
let apiKey = options.apiKey || process.env.TOSTUDY_API_KEY;
|
|
2757
|
+
if (!apiKey) {
|
|
2758
|
+
println6(" Acesse sua API key em:");
|
|
2759
|
+
println6(chalk6.cyan(` ${options.url || DEFAULT_PLATFORM_URL2}/student/settings/mcp`));
|
|
2760
|
+
println6();
|
|
2761
|
+
apiKey = await promptApiKey();
|
|
2762
|
+
} else {
|
|
2763
|
+
println6(chalk6.yellow(` ${API_KEY_DEPRECATION_WARNING}`));
|
|
2764
|
+
println6();
|
|
2765
|
+
println6(chalk6.green(" \u2713 API key fornecida via par\xE2metro ou ambiente"));
|
|
2766
|
+
}
|
|
2767
|
+
const platformUrl = options.url || process.env.TOSTUDY_PLATFORM_URL || DEFAULT_PLATFORM_URL2;
|
|
2768
|
+
println6();
|
|
2769
|
+
process.stdout.write(chalk6.gray(" Validando API key... "));
|
|
2770
|
+
const valid = await validateApiKey(apiKey, platformUrl);
|
|
2771
|
+
if (!valid) {
|
|
2772
|
+
println6(chalk6.red("FALHOU"));
|
|
2773
|
+
println6();
|
|
2774
|
+
println6(chalk6.red(" API key inv\xE1lida ou expirada."));
|
|
2775
|
+
println6();
|
|
2776
|
+
println6(" Para gerar uma nova API key:");
|
|
2777
|
+
println6(chalk6.cyan(` ${platformUrl}/student/settings/mcp`));
|
|
2778
|
+
println6();
|
|
2779
|
+
process.exit(1);
|
|
2780
|
+
}
|
|
2781
|
+
println6(chalk6.green("OK"));
|
|
2782
|
+
printStep(3, TOTAL_STEPS, "Salvando configura\xE7\xE3o");
|
|
2783
|
+
process.stdout.write(" Configurando Claude Code... ");
|
|
2784
|
+
try {
|
|
2785
|
+
addTostudyMcpServer(apiKey, platformUrl);
|
|
2786
|
+
println6(chalk6.green("OK"));
|
|
2787
|
+
} catch (error) {
|
|
2788
|
+
println6(chalk6.red("FALHOU"));
|
|
2789
|
+
eprintln();
|
|
2790
|
+
eprintln(chalk6.red(" Erro ao salvar configura\xE7\xE3o:"));
|
|
2791
|
+
eprintln(" " + (error instanceof Error ? error.message : String(error)));
|
|
2792
|
+
process.exit(1);
|
|
2793
|
+
}
|
|
2794
|
+
printStep(4, TOTAL_STEPS, "Verifica\xE7\xE3o final");
|
|
2795
|
+
if (!options.skipDiagnostics) {
|
|
2796
|
+
process.stdout.write(" Executando diagn\xF3stico... ");
|
|
2797
|
+
const report = await runDiagnostics();
|
|
2798
|
+
println6(chalk6.green("OK"));
|
|
2799
|
+
println6();
|
|
2800
|
+
const remainingIssues = report.issues.filter(
|
|
2801
|
+
(issue) => !["mcp-not-configured", "config-missing"].includes(issue.id)
|
|
2802
|
+
);
|
|
2803
|
+
if (remainingIssues.length > 0) {
|
|
2804
|
+
println6(chalk6.yellow(" Avisos encontrados:"));
|
|
2805
|
+
for (const issue of remainingIssues) {
|
|
2806
|
+
const icon = issue.severity === "critical" ? chalk6.red("\u25CF") : issue.severity === "warning" ? chalk6.yellow("\u25CF") : chalk6.blue("\u25CF");
|
|
2807
|
+
println6(` ${icon} ${issue.title}`);
|
|
2808
|
+
}
|
|
2809
|
+
println6();
|
|
2810
|
+
if (options.autoRepair) {
|
|
2811
|
+
process.stdout.write(" Tentando reparar automaticamente... ");
|
|
2812
|
+
const repairReport = repairAllIssues(report, apiKey, platformUrl);
|
|
2813
|
+
if (repairReport.repairsSucceeded > 0) {
|
|
2814
|
+
println6(chalk6.green(`${repairReport.repairsSucceeded} corrigido(s)`));
|
|
2815
|
+
} else {
|
|
2816
|
+
println6(chalk6.yellow("Nenhum reparo aplicado"));
|
|
633
2817
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
2818
|
+
} else {
|
|
2819
|
+
println6(
|
|
2820
|
+
chalk6.gray(' Dica: Execute "npx @tostudy-ai/mcp-setup diagnose" para mais detalhes.')
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
} else {
|
|
2824
|
+
println6(chalk6.green(" \u2713 Nenhum problema encontrado!"));
|
|
637
2825
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
2826
|
+
} else {
|
|
2827
|
+
println6(chalk6.gray(" Diagn\xF3stico ignorado (--skip-diagnostics)"));
|
|
2828
|
+
}
|
|
2829
|
+
println6();
|
|
2830
|
+
println6(chalk6.green(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2831
|
+
println6(
|
|
2832
|
+
chalk6.green(" \u2551") + chalk6.white.bold(" Configura\xE7\xE3o conclu\xEDda com sucesso! ") + chalk6.green("\u2551")
|
|
2833
|
+
);
|
|
2834
|
+
println6(chalk6.green(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
2835
|
+
println6();
|
|
2836
|
+
println6(chalk6.white(NEXT_STEPS_TITLE));
|
|
2837
|
+
println6();
|
|
2838
|
+
println6(chalk6.gray(" 1.") + " Reinicie o Claude Code");
|
|
2839
|
+
println6(chalk6.gray(" 2.") + " O servidor MCP iniciar\xE1 automaticamente");
|
|
2840
|
+
println6(chalk6.gray(" 3.") + COURSES_TOOL_HINT.trimStart());
|
|
2841
|
+
println6();
|
|
2842
|
+
println6(chalk6.gray(" Comandos \xFAteis:"));
|
|
2843
|
+
println6(
|
|
2844
|
+
chalk6.gray(" ") + chalk6.cyan("npx @tostudy-ai/mcp-setup diagnose") + chalk6.gray(" - Verificar problemas")
|
|
2845
|
+
);
|
|
2846
|
+
println6(
|
|
2847
|
+
chalk6.gray(" ") + chalk6.cyan("npx @tostudy-ai/mcp-setup repair") + chalk6.gray(" - Reparar automaticamente")
|
|
2848
|
+
);
|
|
2849
|
+
println6();
|
|
2850
|
+
println6(chalk6.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2851
|
+
println6();
|
|
2852
|
+
println6(chalk6.white(" Prefere estudar pelo terminal?"));
|
|
2853
|
+
println6(chalk6.gray(" ") + chalk6.cyan("npm install -g @tostudy-ai/cli"));
|
|
2854
|
+
println6(
|
|
2855
|
+
chalk6.gray(" ") + chalk6.cyan("tostudy login") + chalk6.gray(" \u2192 ") + chalk6.cyan("tostudy courses")
|
|
2856
|
+
);
|
|
2857
|
+
println6();
|
|
2858
|
+
}
|
|
2859
|
+
program.name("tostudy-mcp-setup").description("Configura o Claude Code para usar o ToStudy MCP server").version(VERSION);
|
|
2860
|
+
program.option("-k, --api-key <key>", "(deprecated) API key \u2014 use OAuth flow instead").option("-u, --url <url>", "URL da plataforma (default: https://tostudy.ai)").option("--uninstall", "Remove a configura\xE7\xE3o do ToStudy MCP").action(async (options) => {
|
|
2861
|
+
try {
|
|
2862
|
+
if (options.uninstall) {
|
|
2863
|
+
await uninstall();
|
|
2864
|
+
} else {
|
|
2865
|
+
await setup(options.apiKey, options.url);
|
|
644
2866
|
}
|
|
2867
|
+
} catch (error) {
|
|
2868
|
+
eprintln();
|
|
2869
|
+
eprintln(chalk6.red("Erro:") + " " + (error instanceof Error ? error.message : String(error)));
|
|
2870
|
+
process.exit(1);
|
|
2871
|
+
}
|
|
645
2872
|
});
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
2873
|
+
program.command("wizard").description("Assistente interativo de configura\xE7\xE3o passo a passo").option("-k, --api-key <key>", "(deprecated) API key \u2014 use OAuth flow instead").option("-u, --url <url>", "URL da plataforma (default: https://tostudy.ai)").option("--skip-diagnostics", "Pula a verifica\xE7\xE3o de diagn\xF3stico ap\xF3s setup").option("--auto-repair", "Tenta reparar problemas automaticamente").action(async (options) => {
|
|
2874
|
+
try {
|
|
2875
|
+
await wizard({
|
|
2876
|
+
apiKey: options.apiKey,
|
|
2877
|
+
url: options.url,
|
|
2878
|
+
skipDiagnostics: options.skipDiagnostics,
|
|
2879
|
+
autoRepair: options.autoRepair
|
|
2880
|
+
});
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
eprintln();
|
|
2883
|
+
eprintln(chalk6.red("Erro:") + " " + (error instanceof Error ? error.message : String(error)));
|
|
2884
|
+
process.exit(1);
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
program.command("diagnose").description("Executa diagn\xF3stico de problemas na configura\xE7\xE3o MCP").option("--json", "Sa\xEDda em formato JSON").action(async (options) => {
|
|
2888
|
+
try {
|
|
2889
|
+
const report = await runDiagnostics();
|
|
2890
|
+
if (options.json) {
|
|
2891
|
+
println6(JSON.stringify(report, null, 2));
|
|
2892
|
+
} else {
|
|
2893
|
+
printDiagnosticReport(report);
|
|
653
2894
|
}
|
|
654
|
-
|
|
655
|
-
|
|
2895
|
+
process.exit(report.passed ? 0 : 1);
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
eprintln();
|
|
2898
|
+
eprintln(chalk6.red("Erro ao executar diagn\xF3stico:"));
|
|
2899
|
+
eprintln(error instanceof Error ? error.message : String(error));
|
|
2900
|
+
process.exit(1);
|
|
2901
|
+
}
|
|
2902
|
+
});
|
|
2903
|
+
program.command("repair").description("Repara problemas de configura\xE7\xE3o automaticamente").option("-k, --api-key <key>", "API key para reparos que necessitam autentica\xE7\xE3o").option("-u, --url <url>", "URL da plataforma (default: https://tostudy.ai)").option("--json", "Sa\xEDda em formato JSON").action(async (options) => {
|
|
2904
|
+
try {
|
|
2905
|
+
const apiKey = options.apiKey || process.env.TOSTUDY_API_KEY;
|
|
2906
|
+
const platformUrl = options.url || DEFAULT_PLATFORM_URL2;
|
|
2907
|
+
const diagnostic = await runDiagnostics();
|
|
2908
|
+
const repair = repairAllIssues(diagnostic, apiKey, platformUrl);
|
|
2909
|
+
if (options.json) {
|
|
2910
|
+
println6(JSON.stringify({ diagnostic, repair }, null, 2));
|
|
2911
|
+
} else {
|
|
2912
|
+
printRepairReport(repair);
|
|
2913
|
+
}
|
|
2914
|
+
const hasUnfixedCritical = diagnostic.issues.some(
|
|
2915
|
+
(issue) => issue.severity === "critical" && !repair.results.find((r) => r.issueId === issue.id && r.success)
|
|
2916
|
+
);
|
|
2917
|
+
process.exit(hasUnfixedCritical ? 1 : 0);
|
|
2918
|
+
} catch (error) {
|
|
2919
|
+
eprintln();
|
|
2920
|
+
eprintln(chalk6.red("Erro ao executar reparo:"));
|
|
2921
|
+
eprintln(error instanceof Error ? error.message : String(error));
|
|
2922
|
+
process.exit(1);
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
var SUPPORTED_IDES = [
|
|
2926
|
+
"claude-code",
|
|
2927
|
+
"cursor",
|
|
2928
|
+
"vscode",
|
|
2929
|
+
"desktop",
|
|
2930
|
+
"windsurf",
|
|
2931
|
+
"opencode",
|
|
2932
|
+
"codex",
|
|
2933
|
+
"antigravity",
|
|
2934
|
+
"manual"
|
|
2935
|
+
];
|
|
2936
|
+
program.command("install").description("Install MCP config for a specific IDE (used by the web setup wizard)").requiredOption("--ide <ide>", `Target IDE: ${SUPPORTED_IDES.join(", ")}`).requiredOption("--key <key>", "API key from the platform").option("--url <url>", "Platform URL (default: https://tostudy.ai)", DEFAULT_PLATFORM_URL2).action(async (options) => {
|
|
2937
|
+
try {
|
|
2938
|
+
const ide = options.ide;
|
|
2939
|
+
if (!SUPPORTED_IDES.includes(ide)) {
|
|
2940
|
+
eprintln(chalk6.red(`Unknown IDE: ${ide}`));
|
|
2941
|
+
eprintln(`Supported: ${SUPPORTED_IDES.join(", ")}`);
|
|
2942
|
+
process.exit(1);
|
|
656
2943
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
2944
|
+
const handler = getIDEHandler(ide);
|
|
2945
|
+
const mcpUrl = resolveMcpServerUrl(options.url);
|
|
2946
|
+
println6();
|
|
2947
|
+
println6(chalk6.cyan(` Installing MCP config for ${handler.name}...`));
|
|
2948
|
+
process.stdout.write(chalk6.gray(" Writing config... "));
|
|
2949
|
+
await handler.writeConfig(options.key, mcpUrl);
|
|
2950
|
+
println6(chalk6.green("OK"));
|
|
2951
|
+
if (ide !== "manual") {
|
|
2952
|
+
println6(chalk6.gray(` Config: ${handler.getConfigPath()}`));
|
|
660
2953
|
}
|
|
661
|
-
|
|
2954
|
+
process.stdout.write(chalk6.gray(" Verifying connection... "));
|
|
2955
|
+
const verified = await handler.verify(options.key, options.url);
|
|
2956
|
+
if (verified) {
|
|
2957
|
+
println6(chalk6.green("OK"));
|
|
2958
|
+
} else {
|
|
2959
|
+
println6(chalk6.yellow("SKIPPED (server not reachable)"));
|
|
2960
|
+
println6(
|
|
2961
|
+
chalk6.gray(" The config was saved. Connection will work when the server is available.")
|
|
2962
|
+
);
|
|
2963
|
+
}
|
|
2964
|
+
println6();
|
|
2965
|
+
println6(chalk6.green.bold(" Done! Restart your IDE to activate the MCP server."));
|
|
2966
|
+
println6();
|
|
2967
|
+
} catch (error) {
|
|
2968
|
+
eprintln();
|
|
2969
|
+
eprintln(
|
|
2970
|
+
chalk6.red("Install failed:") + " " + (error instanceof Error ? error.message : String(error))
|
|
2971
|
+
);
|
|
2972
|
+
process.exit(1);
|
|
2973
|
+
}
|
|
2974
|
+
});
|
|
2975
|
+
function resolveMcpServerUrl(platformUrl) {
|
|
2976
|
+
if (platformUrl.includes(":3700")) {
|
|
2977
|
+
return platformUrl.replace(":3700", ":3701");
|
|
2978
|
+
}
|
|
2979
|
+
if (platformUrl.includes("localhost") && !platformUrl.includes(":")) {
|
|
2980
|
+
return "http://localhost:3701";
|
|
2981
|
+
}
|
|
2982
|
+
if (platformUrl.includes("tostudy")) {
|
|
2983
|
+
return process.env.MCP_SERVER_URL || "https://mcp.tostudy.ai";
|
|
2984
|
+
}
|
|
2985
|
+
return platformUrl;
|
|
2986
|
+
}
|
|
2987
|
+
function resolvePlatformUrl(explicitUrl) {
|
|
2988
|
+
return explicitUrl || process.env.TOSTUDY_PLATFORM_URL || DEFAULT_PLATFORM_URL2;
|
|
662
2989
|
}
|
|
2990
|
+
async function resolveCommandApiKey(explicitApiKey) {
|
|
2991
|
+
const apiKey = explicitApiKey || process.env.TOSTUDY_API_KEY;
|
|
2992
|
+
if (apiKey) {
|
|
2993
|
+
return apiKey;
|
|
2994
|
+
}
|
|
2995
|
+
return promptApiKey();
|
|
2996
|
+
}
|
|
2997
|
+
program.command("init").description("Configura instru\xE7\xF5es do curso no workspace atual").requiredOption("--course <courseId>", "Course ID to configure in this workspace").option("-k, --api-key <key>", "API key para autentica\xE7\xE3o").option("-u, --url <url>", "URL da plataforma (default: https://tostudy.ai)").action(async (options) => {
|
|
2998
|
+
try {
|
|
2999
|
+
const result = await runInit({
|
|
3000
|
+
apiKey: await resolveCommandApiKey(options.apiKey),
|
|
3001
|
+
platformUrl: resolvePlatformUrl(options.url),
|
|
3002
|
+
courseId: options.course
|
|
3003
|
+
});
|
|
3004
|
+
if (!result.ok) {
|
|
3005
|
+
process.exit(1);
|
|
3006
|
+
}
|
|
3007
|
+
} catch (error) {
|
|
3008
|
+
eprintln();
|
|
3009
|
+
eprintln(
|
|
3010
|
+
chalk6.red("Erro no init:") + " " + (error instanceof Error ? error.message : String(error))
|
|
3011
|
+
);
|
|
3012
|
+
process.exit(1);
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
program.command("sync").description("Regenera as instru\xE7\xF5es do curso com base no arquivo existente").option("--course <courseId>", "Course ID override when metadata is unavailable").option("-k, --api-key <key>", "API key para autentica\xE7\xE3o").option("-u, --url <url>", "URL da plataforma (default: https://tostudy.ai)").action(async (options) => {
|
|
3016
|
+
try {
|
|
3017
|
+
const result = await runSync({
|
|
3018
|
+
apiKey: await resolveCommandApiKey(options.apiKey),
|
|
3019
|
+
platformUrl: resolvePlatformUrl(options.url),
|
|
3020
|
+
courseId: options.course
|
|
3021
|
+
});
|
|
3022
|
+
if (!result.ok) {
|
|
3023
|
+
process.exit(1);
|
|
3024
|
+
}
|
|
3025
|
+
} catch (error) {
|
|
3026
|
+
eprintln();
|
|
3027
|
+
eprintln(
|
|
3028
|
+
chalk6.red("Erro no sync:") + " " + (error instanceof Error ? error.message : String(error))
|
|
3029
|
+
);
|
|
3030
|
+
process.exit(1);
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
663
3033
|
program.parse();
|
|
664
3034
|
//# sourceMappingURL=index.js.map
|