@tostudy-ai/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,1786 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+
28
+ // ../../node_modules/picocolors/picocolors.js
29
+ var require_picocolors = __commonJS({
30
+ "../../node_modules/picocolors/picocolors.js"(exports, module) {
31
+ var p = process || {};
32
+ var argv = p.argv || [];
33
+ var env = p.env || {};
34
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
35
+ var formatter = (open, close, replace = open) => (input) => {
36
+ let string = "" + input, index = string.indexOf(close, open.length);
37
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
38
+ };
39
+ var replaceClose = (string, close, replace, index) => {
40
+ let result = "", cursor = 0;
41
+ do {
42
+ result += string.substring(cursor, index) + replace;
43
+ cursor = index + close.length;
44
+ index = string.indexOf(close, cursor);
45
+ } while (~index);
46
+ return result + string.substring(cursor);
47
+ };
48
+ var createColors = (enabled = isColorSupported) => {
49
+ let f = enabled ? formatter : () => String;
50
+ return {
51
+ isColorSupported: enabled,
52
+ reset: f("\x1B[0m", "\x1B[0m"),
53
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
54
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
55
+ italic: f("\x1B[3m", "\x1B[23m"),
56
+ underline: f("\x1B[4m", "\x1B[24m"),
57
+ inverse: f("\x1B[7m", "\x1B[27m"),
58
+ hidden: f("\x1B[8m", "\x1B[28m"),
59
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
60
+ black: f("\x1B[30m", "\x1B[39m"),
61
+ red: f("\x1B[31m", "\x1B[39m"),
62
+ green: f("\x1B[32m", "\x1B[39m"),
63
+ yellow: f("\x1B[33m", "\x1B[39m"),
64
+ blue: f("\x1B[34m", "\x1B[39m"),
65
+ magenta: f("\x1B[35m", "\x1B[39m"),
66
+ cyan: f("\x1B[36m", "\x1B[39m"),
67
+ white: f("\x1B[37m", "\x1B[39m"),
68
+ gray: f("\x1B[90m", "\x1B[39m"),
69
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
70
+ bgRed: f("\x1B[41m", "\x1B[49m"),
71
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
72
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
73
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
74
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
75
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
76
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
77
+ blackBright: f("\x1B[90m", "\x1B[39m"),
78
+ redBright: f("\x1B[91m", "\x1B[39m"),
79
+ greenBright: f("\x1B[92m", "\x1B[39m"),
80
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
81
+ blueBright: f("\x1B[94m", "\x1B[39m"),
82
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
83
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
84
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
85
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
86
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
87
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
88
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
89
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
90
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
91
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
92
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
93
+ };
94
+ };
95
+ module.exports = createColors();
96
+ module.exports.createColors = createColors;
97
+ }
98
+ });
99
+
100
+ // src/cli.ts
101
+ import { Command as Command15 } from "commander";
102
+
103
+ // src/commands/login.ts
104
+ import { Command } from "commander";
105
+ import { execFile } from "node:child_process";
106
+
107
+ // src/auth/oauth-server.ts
108
+ import http from "node:http";
109
+ function startCallbackServer(port) {
110
+ return new Promise((resolve, reject) => {
111
+ const server = http.createServer((req, res) => {
112
+ const url = new URL(req.url, `http://localhost:${port}`);
113
+ if (url.pathname === "/callback") {
114
+ const code = url.searchParams.get("code");
115
+ if (code) {
116
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
117
+ res.end("<html><body><h1>Autenticado!</h1><p>Pode fechar esta aba.</p></body></html>");
118
+ server.close();
119
+ resolve({ code });
120
+ } else {
121
+ res.writeHead(400);
122
+ res.end("Missing code");
123
+ }
124
+ } else {
125
+ res.writeHead(404);
126
+ res.end("Not found");
127
+ }
128
+ });
129
+ server.listen(port, () => {
130
+ });
131
+ server.on("error", (err) => {
132
+ if (err.code === "EADDRINUSE") {
133
+ reject(new Error(`Port ${port} est\xE1 em uso. Tente novamente.`));
134
+ } else {
135
+ reject(err);
136
+ }
137
+ });
138
+ setTimeout(() => {
139
+ server.close();
140
+ reject(new Error("Login timeout \u2014 nenhuma resposta em 2 minutos"));
141
+ }, 12e4);
142
+ });
143
+ }
144
+
145
+ // src/auth/session.ts
146
+ import fs from "node:fs";
147
+ import path from "node:path";
148
+ import os from "node:os";
149
+ function getConfigDir(override) {
150
+ if (override) return override;
151
+ if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
152
+ return path.join(process.env["XDG_CONFIG_HOME"], "tostudy");
153
+ }
154
+ return path.join(os.homedir(), ".tostudy");
155
+ }
156
+ async function saveSession(session, configDir) {
157
+ const dir = getConfigDir(configDir);
158
+ fs.mkdirSync(dir, { recursive: true });
159
+ fs.writeFileSync(path.join(dir, "config.json"), JSON.stringify(session, null, 2), {
160
+ mode: 384
161
+ });
162
+ }
163
+ async function getSession(configDir) {
164
+ const dir = getConfigDir(configDir);
165
+ const p = path.join(dir, "config.json");
166
+ if (!fs.existsSync(p)) return null;
167
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
168
+ }
169
+ async function clearSession(configDir) {
170
+ const dir = getConfigDir(configDir);
171
+ const p = path.join(dir, "config.json");
172
+ if (fs.existsSync(p)) fs.unlinkSync(p);
173
+ const ap = path.join(dir, "active-course.json");
174
+ if (fs.existsSync(ap)) fs.unlinkSync(ap);
175
+ }
176
+ async function getActiveCourse(configDir) {
177
+ const dir = getConfigDir(configDir);
178
+ const p = path.join(dir, "active-course.json");
179
+ if (!fs.existsSync(p)) return null;
180
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
181
+ }
182
+ async function setActiveCourse(course, configDir) {
183
+ const dir = getConfigDir(configDir);
184
+ fs.mkdirSync(dir, { recursive: true });
185
+ fs.writeFileSync(path.join(dir, "active-course.json"), JSON.stringify(course, null, 2), {
186
+ mode: 384
187
+ });
188
+ }
189
+ async function requireSession(configDir) {
190
+ const session = await getSession(configDir);
191
+ if (!session) {
192
+ process.stderr.write("Erro: N\xE3o autenticado. Rode: tostudy login\n");
193
+ process.exit(2);
194
+ }
195
+ const expiresAt = new Date(session.expiresAt);
196
+ if (expiresAt < /* @__PURE__ */ new Date()) {
197
+ process.stderr.write("Erro: Sess\xE3o expirada. Rode: tostudy login\n");
198
+ process.exit(2);
199
+ }
200
+ return session;
201
+ }
202
+ async function requireActiveCourse(configDir) {
203
+ const course = await getActiveCourse(configDir);
204
+ if (!course) {
205
+ process.stderr.write("Erro: Nenhum curso ativo. Rode: tostudy select <curso>\n");
206
+ process.exit(3);
207
+ }
208
+ return course;
209
+ }
210
+
211
+ // src/output/formatter.ts
212
+ function output(data, opts) {
213
+ if (opts.json) {
214
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
215
+ } else if (typeof data === "string") {
216
+ process.stdout.write(data + "\n");
217
+ } else {
218
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
219
+ }
220
+ }
221
+ function error(msg, code = 1) {
222
+ process.stderr.write(`Erro: ${msg}
223
+ `);
224
+ process.exit(code);
225
+ }
226
+ function progressBar(percent, width = 20) {
227
+ const clamped = Math.max(0, Math.min(100, percent));
228
+ const filled = Math.round(clamped / 100 * width);
229
+ const empty = width - filled;
230
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty) + ` ${clamped}%`;
231
+ }
232
+ function formatCourseList(courses) {
233
+ if (courses.length === 0) {
234
+ return [
235
+ "Nenhum curso encontrado.",
236
+ "",
237
+ "\u2192 tostudy enroll <curso> para se matricular em um curso"
238
+ ].join("\n");
239
+ }
240
+ const lines = ["Seus cursos:", ""];
241
+ courses.forEach((course, idx) => {
242
+ const bar = progressBar(course.progress);
243
+ lines.push(` ${idx + 1}. ${course.title}`);
244
+ lines.push(` ${bar}`);
245
+ lines.push(` Professor: ${course.creatorName}`);
246
+ lines.push("");
247
+ });
248
+ lines.push("\u2192 tostudy select <n\xFAmero> para ativar um curso");
249
+ lines.push("\u2192 tostudy progress para ver seu progresso");
250
+ return lines.join("\n");
251
+ }
252
+ function formatProgress(data) {
253
+ const { currentModule, currentLesson } = data;
254
+ const courseBar = progressBar(data.coursePercent);
255
+ const moduleBar = progressBar(data.modulePercent);
256
+ const lines = [
257
+ "\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",
258
+ `M\xF3dulo ${currentModule.order} de ${currentModule.totalModules}: ${currentModule.title}`,
259
+ ` Curso: ${courseBar}`,
260
+ ` M\xF3dulo: ${moduleBar}`,
261
+ "",
262
+ `Li\xE7\xE3o atual (${currentLesson.order}/${currentLesson.totalLessons}): ${currentLesson.title}`,
263
+ `Li\xE7\xF5es conclu\xEDdas: ${data.completedLessons}`,
264
+ `Tempo restante estimado: ~${data.estimatedMinutesRemaining} min`,
265
+ "\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",
266
+ "",
267
+ "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
268
+ "\u2192 tostudy lesson para ver o conte\xFAdo da li\xE7\xE3o atual"
269
+ ];
270
+ return lines.join("\n");
271
+ }
272
+ function formatLesson(data) {
273
+ const { lesson, wayfinding, progress } = data;
274
+ const courseBar = progressBar(progress.coursePercent);
275
+ const moduleBar = progressBar(progress.modulePercent);
276
+ const lines = [
277
+ `\u2501\u2501\u2501 M\xF3dulo ${wayfinding.moduleOrder}/${wayfinding.totalModules} \xB7 Li\xE7\xE3o ${wayfinding.lessonOrder}/${wayfinding.totalLessons} \u2501\u2501\u2501`,
278
+ "",
279
+ `\u{1F4D6} ${lesson.title}`,
280
+ ` M\xF3dulo: ${lesson.moduleTitle} | Tipo: ${lesson.type}`,
281
+ "",
282
+ lesson.content,
283
+ "",
284
+ ` Curso: ${courseBar}`,
285
+ ` M\xF3dulo: ${moduleBar}`
286
+ ];
287
+ if (lesson.acceptanceCriteria) {
288
+ lines.push("", "Crit\xE9rios de aceita\xE7\xE3o:", lesson.acceptanceCriteria);
289
+ }
290
+ if (lesson.exerciseContent) {
291
+ lines.push("", "\u2500\u2500\u2500 Exerc\xEDcio \u2500\u2500\u2500", lesson.exerciseContent);
292
+ }
293
+ lines.push(
294
+ "",
295
+ "\u2192 tostudy validate <resposta> para validar sua solu\xE7\xE3o",
296
+ "\u2192 tostudy hint para obter uma dica",
297
+ "\u2192 tostudy next para avan\xE7ar (ap\xF3s completar)"
298
+ );
299
+ return lines.join("\n");
300
+ }
301
+ function formatValidation(data) {
302
+ const statusIcon = data.passed ? "\u2713" : "\u2717";
303
+ const statusLabel = data.passed ? "APROVADO" : "REPROVADO";
304
+ const scoreStr = data.score !== null ? ` \xB7 Pontua\xE7\xE3o: ${data.score}/100` : "";
305
+ const lines = [`${statusIcon} Valida\xE7\xE3o: ${statusLabel}${scoreStr}`, "", "Crit\xE9rios:"];
306
+ data.criteria.forEach((c) => {
307
+ const icon = c.met ? " \u2713" : " \u2717";
308
+ lines.push(`${icon} ${c.criteria}`);
309
+ if (c.comment) {
310
+ lines.push(` ${c.comment}`);
311
+ }
312
+ });
313
+ lines.push("", `Feedback: ${data.feedback}`, "");
314
+ if (data.passed) {
315
+ lines.push("\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o");
316
+ } else {
317
+ lines.push(
318
+ "\u2192 tostudy hint para obter uma dica",
319
+ "\u2192 tostudy validate para tentar novamente"
320
+ );
321
+ }
322
+ return lines.join("\n");
323
+ }
324
+ function formatHint(data) {
325
+ const levelLabel = data.levelLabel ?? `N\xEDvel ${data.level}`;
326
+ const lines = [
327
+ `\u{1F4A1} Dica (${levelLabel} \xB7 ${data.level}/${data.maxLevel}):`,
328
+ "",
329
+ data.hint,
330
+ ""
331
+ ];
332
+ if (data.level < data.maxLevel) {
333
+ lines.push(`\u2192 tostudy hint para uma dica mais detalhada (n\xEDvel ${data.level + 1})`);
334
+ } else {
335
+ lines.push("\u2192 tostudy validate para tentar sua solu\xE7\xE3o");
336
+ }
337
+ return lines.join("\n");
338
+ }
339
+ function formatModuleStart(data) {
340
+ const { module: mod, firstLesson, wayfinding } = data;
341
+ const lines = [
342
+ `\u2501\u2501\u2501 M\xF3dulo ${wayfinding.moduleOrder}/${wayfinding.totalModules} \u2501\u2501\u2501`,
343
+ "",
344
+ `\u{1F5C2} ${mod.title}`
345
+ ];
346
+ if (mod.description) {
347
+ lines.push("", mod.description);
348
+ }
349
+ if (mod.objectives && mod.objectives.length > 0) {
350
+ lines.push("", "Objetivos:");
351
+ mod.objectives.forEach((obj) => lines.push(` \u2022 ${obj}`));
352
+ }
353
+ lines.push("", "\u2500\u2500\u2500 Primeira Li\xE7\xE3o \u2500\u2500\u2500", "", `\u{1F4D6} ${firstLesson.title}`, "", firstLesson.content);
354
+ if (firstLesson.lessonIntroContent) {
355
+ lines.push("", firstLesson.lessonIntroContent);
356
+ }
357
+ if (firstLesson.exerciseContent) {
358
+ lines.push("", "\u2500\u2500\u2500 Exerc\xEDcio \u2500\u2500\u2500", firstLesson.exerciseContent);
359
+ }
360
+ if (firstLesson.acceptanceCriteria) {
361
+ lines.push("", "Crit\xE9rios de aceita\xE7\xE3o:", firstLesson.acceptanceCriteria);
362
+ }
363
+ lines.push(
364
+ "",
365
+ "\u2192 tostudy validate <resposta> para validar sua solu\xE7\xE3o",
366
+ "\u2192 tostudy hint para obter uma dica"
367
+ );
368
+ return lines.join("\n");
369
+ }
370
+
371
+ // src/commands/login.ts
372
+ var DEFAULT_API_URL = "https://tostudy.ai";
373
+ var PORT = 9876;
374
+ var loginCommand = new Command("login").description("Autentica no ToStudy via browser").option("--manual", "Login manual (sem browser)").option("--api-url <url>", "API URL override", DEFAULT_API_URL).action(async (opts) => {
375
+ const apiUrl = opts.apiUrl;
376
+ if (opts.manual) {
377
+ console.log(`
378
+ Acesse este endere\xE7o no seu browser:`);
379
+ console.log(` ${apiUrl}/api/cli/auth/authorize?port=${PORT}
380
+ `);
381
+ console.log(" Ap\xF3s autorizar, o browser ser\xE1 redirecionado e o login ser\xE1 conclu\xEDdo.");
382
+ console.log(" Certifique-se de rodar este comando sem --manual para o fluxo autom\xE1tico.\n");
383
+ return;
384
+ }
385
+ console.log("\n Abrindo browser para autentica\xE7\xE3o...\n");
386
+ const serverPromise = startCallbackServer(PORT);
387
+ const authUrl = `${apiUrl}/api/cli/auth/authorize?port=${PORT}`;
388
+ const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
389
+ execFile(openCmd, [authUrl], (err) => {
390
+ if (err) {
391
+ console.log(` N\xE3o foi poss\xEDvel abrir o browser automaticamente.`);
392
+ console.log(` Abra manualmente: ${authUrl}
393
+ `);
394
+ }
395
+ });
396
+ try {
397
+ const { code } = await serverPromise;
398
+ const res = await fetch(`${apiUrl}/api/cli/auth/exchange`, {
399
+ method: "POST",
400
+ headers: { "Content-Type": "application/json" },
401
+ body: JSON.stringify({ code })
402
+ });
403
+ if (!res.ok) {
404
+ const body = await res.json();
405
+ error(body.error ?? "Falha na autentica\xE7\xE3o");
406
+ }
407
+ const { token: token2, userId, userName, expiresAt } = await res.json();
408
+ await saveSession({ token: token2, userId, userName, expiresAt, apiUrl });
409
+ console.log(`
410
+ Logado como ${userName}
411
+ `);
412
+ } catch (err) {
413
+ const msg = err instanceof Error ? err.message : "Login falhou";
414
+ error(msg);
415
+ }
416
+ });
417
+
418
+ // src/commands/logout.ts
419
+ import { Command as Command2 } from "commander";
420
+ var logoutCommand = new Command2("logout").description("Remove token local").action(async () => {
421
+ await clearSession();
422
+ console.log("\n Deslogado com sucesso.\n");
423
+ });
424
+
425
+ // src/commands/setup.ts
426
+ import { Command as Command3 } from "commander";
427
+
428
+ // src/installer/node-detector.ts
429
+ import { execFileSync } from "node:child_process";
430
+ function detectNode() {
431
+ try {
432
+ const version = execFileSync("node", ["--version"], {
433
+ encoding: "utf-8"
434
+ }).trim();
435
+ let nodePath = null;
436
+ try {
437
+ const whichCmd = process.platform === "win32" ? "where" : "which";
438
+ nodePath = execFileSync(whichCmd, ["node"], { encoding: "utf-8" }).trim();
439
+ } catch {
440
+ }
441
+ const major = parseInt(version.replace("v", ""), 10);
442
+ return { installed: true, version, meetsMinimum: major >= 18, path: nodePath };
443
+ } catch {
444
+ return { installed: false, version: null, meetsMinimum: false, path: null };
445
+ }
446
+ }
447
+
448
+ // src/installer/ide-detector.ts
449
+ import { execFileSync as execFileSync2 } from "node:child_process";
450
+ import fs2 from "node:fs";
451
+ import path2 from "node:path";
452
+ import os2 from "node:os";
453
+ function checkExists(p) {
454
+ return fs2.existsSync(p);
455
+ }
456
+ function detectIDEs() {
457
+ const home = os2.homedir();
458
+ const ides = [
459
+ // Claude Code: detected by presence of 'claude' binary
460
+ {
461
+ name: "Claude Code",
462
+ id: "claude-code",
463
+ detected: false,
464
+ configPath: path2.join(home, ".mcp.json")
465
+ },
466
+ // Cursor: ~/.cursor directory
467
+ {
468
+ name: "Cursor",
469
+ id: "cursor",
470
+ detected: checkExists(path2.join(home, ".cursor")),
471
+ configPath: path2.join(home, ".cursor", "mcp.json")
472
+ },
473
+ // VS Code: ~/.vscode directory
474
+ {
475
+ name: "VS Code",
476
+ id: "vscode",
477
+ detected: checkExists(path2.join(home, ".vscode")),
478
+ configPath: ".vscode/mcp.json"
479
+ },
480
+ // Claude Desktop: platform-specific config dir
481
+ {
482
+ name: "Claude Desktop",
483
+ id: "desktop",
484
+ detected: checkExists(
485
+ process.platform === "darwin" ? path2.join(home, "Library", "Application Support", "Claude") : process.platform === "win32" ? path2.join(process.env["APPDATA"] ?? home, "Claude") : path2.join(home, ".config", "claude")
486
+ ),
487
+ configPath: process.platform === "darwin" ? path2.join(
488
+ home,
489
+ "Library",
490
+ "Application Support",
491
+ "Claude",
492
+ "claude_desktop_config.json"
493
+ ) : process.platform === "win32" ? path2.join(process.env["APPDATA"] ?? home, "Claude", "claude_desktop_config.json") : path2.join(home, ".config", "claude", "claude_desktop_config.json")
494
+ },
495
+ // Windsurf: ~/.codeium/windsurf
496
+ {
497
+ name: "Windsurf",
498
+ id: "windsurf",
499
+ detected: checkExists(path2.join(home, ".codeium", "windsurf")),
500
+ configPath: path2.join(home, ".codeium", "windsurf", "mcp_config.json")
501
+ },
502
+ // OpenCode: ~/.opencode
503
+ {
504
+ name: "OpenCode",
505
+ id: "opencode",
506
+ detected: checkExists(path2.join(home, ".opencode")),
507
+ configPath: path2.join(home, ".opencode", "opencode.json")
508
+ },
509
+ // Manual (always available as fallback)
510
+ {
511
+ name: "Manual",
512
+ id: "manual",
513
+ detected: false,
514
+ configPath: "mcp.json"
515
+ }
516
+ ];
517
+ const claudeIde = ides.find((ide) => ide.id === "claude-code");
518
+ if (claudeIde) {
519
+ try {
520
+ execFileSync2("which", ["claude"], { encoding: "utf-8" });
521
+ claudeIde.detected = true;
522
+ } catch {
523
+ }
524
+ }
525
+ return ides;
526
+ }
527
+
528
+ // src/commands/setup.ts
529
+ var setupCommand = new Command3("setup").description("Configura ambiente para estudar").option("--quick", "Setup r\xE1pido (sem wizard)").option("--ide <ide>", "Configura IDE espec\xEDfica").action(async (_opts) => {
530
+ console.log("\n ToStudy Setup\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
531
+ console.log(" 1. Verificando Node.js...");
532
+ const node = detectNode();
533
+ if (!node.installed) {
534
+ console.log(" \u2717 Node.js n\xE3o encontrado");
535
+ console.log(" \u2192 Instale via: https://nodejs.org/ ou `nvm install --lts`\n");
536
+ process.exit(1);
537
+ }
538
+ if (!node.meetsMinimum) {
539
+ console.log(` \u2717 Node.js ${node.version} \xE9 muito antigo (m\xEDnimo: v18)`);
540
+ console.log(" \u2192 Atualize via: `nvm install --lts`\n");
541
+ process.exit(1);
542
+ }
543
+ console.log(` \u2713 Node.js ${node.version}
544
+ `);
545
+ console.log(" 2. Verificando autentica\xE7\xE3o...");
546
+ let session = null;
547
+ try {
548
+ session = await getSession();
549
+ } catch {
550
+ }
551
+ if (!session) {
552
+ console.log(" \u2717 N\xE3o autenticado");
553
+ console.log(" \u2192 Rode: tostudy login\n");
554
+ process.exit(1);
555
+ }
556
+ console.log(` \u2713 Logado como ${session.userName}
557
+ `);
558
+ console.log(" 3. Detectando IDEs...");
559
+ let ides = [];
560
+ try {
561
+ ides = detectIDEs();
562
+ } catch {
563
+ }
564
+ const detected = ides.filter((ide) => ide.detected);
565
+ if (detected.length === 0) {
566
+ console.log(" \u25CB Nenhum IDE detectado");
567
+ console.log(" \u2192 Use tostudy diretamente no terminal\n");
568
+ } else {
569
+ for (const ide of detected) {
570
+ console.log(` \u2713 ${ide.name} detectado`);
571
+ }
572
+ console.log("");
573
+ }
574
+ console.log(" Setup completo!");
575
+ console.log(" \u2192 tostudy courses (ver cursos matriculados)");
576
+ console.log(" \u2192 tostudy doctor (diagn\xF3stico completo)\n");
577
+ });
578
+
579
+ // src/commands/doctor.ts
580
+ import { Command as Command4 } from "commander";
581
+ var doctorCommand = new Command4("doctor").description("Diagn\xF3stico do ambiente").option("--json", "Output JSON").option("--fix", "Auto-corrigir problemas (reservado para vers\xF5es futuras)").action(async (opts) => {
582
+ const checks = {};
583
+ const node = detectNode();
584
+ const envChecks = {
585
+ os: { platform: process.platform, arch: process.arch },
586
+ node: { ...node },
587
+ pnpm: null,
588
+ git: null
589
+ };
590
+ try {
591
+ const { execFileSync: execFileSync3 } = await import("node:child_process");
592
+ envChecks["pnpm"] = execFileSync3("pnpm", ["--version"], { encoding: "utf-8" }).trim();
593
+ } catch {
594
+ envChecks["pnpm"] = null;
595
+ }
596
+ try {
597
+ const { execFileSync: execFileSync3 } = await import("node:child_process");
598
+ envChecks["git"] = execFileSync3("git", ["--version"], { encoding: "utf-8" }).trim().replace("git version ", "");
599
+ } catch {
600
+ envChecks["git"] = null;
601
+ }
602
+ checks["environment"] = envChecks;
603
+ let session = null;
604
+ try {
605
+ session = await getSession();
606
+ } catch {
607
+ }
608
+ checks["auth"] = {
609
+ loggedIn: !!session,
610
+ userName: session?.userName ?? null,
611
+ expiresAt: session?.expiresAt ?? null
612
+ };
613
+ try {
614
+ const apiUrl = session?.apiUrl ?? "https://tostudy.ai";
615
+ const start = Date.now();
616
+ const res = await fetch(`${apiUrl}/api/mcp/heartbeat`, {
617
+ method: "POST",
618
+ headers: session ? { Authorization: `Bearer ${session.token}` } : {},
619
+ signal: AbortSignal.timeout(5e3)
620
+ });
621
+ checks["connectivity"] = { ok: res.ok, latencyMs: Date.now() - start };
622
+ } catch {
623
+ checks["connectivity"] = { ok: false, latencyMs: null };
624
+ }
625
+ let ides = [];
626
+ try {
627
+ ides = detectIDEs();
628
+ } catch {
629
+ }
630
+ checks["ides"] = ides.filter((ide) => ide.detected);
631
+ if (opts.json) {
632
+ output(checks, { json: true });
633
+ return;
634
+ }
635
+ const pnpmVersion = envChecks["pnpm"];
636
+ const gitVersion = envChecks["git"];
637
+ const connectivity = checks["connectivity"];
638
+ console.log("\n Ambiente");
639
+ console.log(
640
+ ` ${node.installed ? "\u2713" : "\u2717"} Node ${node.version ?? "n\xE3o encontrado"}`
641
+ );
642
+ console.log(
643
+ ` ${node.meetsMinimum ? "\u2713" : "\u2717"} Vers\xE3o ${node.meetsMinimum ? "OK (>= 18)" : "Desatualizado"}`
644
+ );
645
+ console.log(` \u2713 OS ${process.platform} (${process.arch})`);
646
+ console.log(` ${pnpmVersion ? "\u2713" : "\u25CB"} pnpm ${pnpmVersion ?? "n\xE3o encontrado"}`);
647
+ console.log(` ${gitVersion ? "\u2713" : "\u25CB"} git ${gitVersion ?? "n\xE3o encontrado"}`);
648
+ console.log("\n Autentica\xE7\xE3o");
649
+ console.log(` ${session ? "\u2713" : "\u2717"} Token ${session ? "v\xE1lido" : "n\xE3o logado"}`);
650
+ if (session) {
651
+ console.log(` \u2713 Usu\xE1rio ${session.userName}`);
652
+ }
653
+ console.log("\n Conectividade");
654
+ console.log(
655
+ ` ${connectivity.ok ? "\u2713" : "\u2717"} API ${connectivity.ok ? `OK (${connectivity.latencyMs}ms)` : "indispon\xEDvel"}`
656
+ );
657
+ console.log("\n LLM Clients");
658
+ for (const ide of ides) {
659
+ console.log(
660
+ ` ${ide.detected ? "\u2713" : "\u25CB"} ${ide.name.padEnd(14)} ${ide.detected ? "detectado" : "n\xE3o detectado"}`
661
+ );
662
+ }
663
+ console.log("");
664
+ });
665
+
666
+ // src/commands/courses.ts
667
+ import { Command as Command5 } from "commander";
668
+
669
+ // ../../packages/logger/src/types.ts
670
+ var LOG_LEVELS = {
671
+ trace: 0,
672
+ debug: 1,
673
+ info: 2,
674
+ warn: 3,
675
+ error: 4,
676
+ fatal: 5
677
+ };
678
+
679
+ // ../../packages/logger/src/context/async-context.ts
680
+ import { AsyncLocalStorage } from "node:async_hooks";
681
+ var asyncLocalStorage = new AsyncLocalStorage();
682
+ function getLogContext() {
683
+ return asyncLocalStorage.getStore();
684
+ }
685
+
686
+ // ../../packages/logger/src/formatters/json.ts
687
+ function formatJson(entry) {
688
+ const { timestamp, level, message, service, environment, context, data, error: error2 } = entry;
689
+ const output2 = {
690
+ timestamp,
691
+ level,
692
+ message,
693
+ service,
694
+ environment,
695
+ ...context,
696
+ ...data
697
+ };
698
+ if (error2) {
699
+ output2.error = error2;
700
+ }
701
+ return JSON.stringify(output2);
702
+ }
703
+
704
+ // ../../packages/logger/src/formatters/pretty.ts
705
+ var import_picocolors = __toESM(require_picocolors(), 1);
706
+ var LEVEL_COLORS = {
707
+ trace: import_picocolors.default.gray,
708
+ debug: import_picocolors.default.cyan,
709
+ info: import_picocolors.default.green,
710
+ warn: import_picocolors.default.yellow,
711
+ error: import_picocolors.default.red,
712
+ fatal: (s) => import_picocolors.default.bgRed(import_picocolors.default.white(s))
713
+ };
714
+ var LEVEL_WIDTH = 5;
715
+ function formatTime(timestamp) {
716
+ const date = new Date(timestamp);
717
+ const hours = String(date.getUTCHours()).padStart(2, "0");
718
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
719
+ const seconds = String(date.getUTCSeconds()).padStart(2, "0");
720
+ return `${hours}:${minutes}:${seconds}`;
721
+ }
722
+ function formatLevel(level) {
723
+ const colorFn = LEVEL_COLORS[level];
724
+ return colorFn(level.toUpperCase().padEnd(LEVEL_WIDTH));
725
+ }
726
+ function formatData(data) {
727
+ const entries = Object.entries(data);
728
+ if (entries.length === 0) return "";
729
+ const lines = entries.map(([key, value]) => {
730
+ const formatted = typeof value === "object" ? JSON.stringify(value) : String(value);
731
+ return ` ${import_picocolors.default.dim(key + ":")} ${formatted}`;
732
+ });
733
+ return "\n" + lines.join("\n");
734
+ }
735
+ function formatPretty(entry) {
736
+ const { timestamp, level, message, service, context, data, error: error2 } = entry;
737
+ const time = formatTime(timestamp);
738
+ const lvl = formatLevel(level);
739
+ const mod = context.module ? import_picocolors.default.dim(`[${context.module}]`) + " " : "";
740
+ const svc = import_picocolors.default.dim(`[${service}]`) + " ";
741
+ let output2 = `${import_picocolors.default.dim(time)} ${lvl} ${svc}${mod}${message}`;
742
+ const traceInfo = {};
743
+ if (context.traceId) traceInfo.traceId = context.traceId;
744
+ if (context.requestId && context.requestId !== context.traceId) {
745
+ traceInfo.requestId = context.requestId;
746
+ }
747
+ if (context.caller) traceInfo.caller = context.caller;
748
+ if (context.duration !== void 0) traceInfo.duration = `${context.duration}ms`;
749
+ const allData = { ...traceInfo, ...data };
750
+ if (Object.keys(allData).length > 0) {
751
+ output2 += formatData(allData);
752
+ }
753
+ if (error2) {
754
+ output2 += "\n" + import_picocolors.default.red(` ${error2.name}: ${error2.message}`);
755
+ if (error2.code) {
756
+ output2 += import_picocolors.default.dim(` (${error2.code})`);
757
+ }
758
+ if (error2.stack) {
759
+ const stackLines = error2.stack.split("\n").slice(1, 4);
760
+ output2 += "\n" + stackLines.map((l) => import_picocolors.default.dim(` ${l.trim()}`)).join("\n");
761
+ }
762
+ if (error2.cause) {
763
+ output2 += "\n" + import_picocolors.default.dim(` Caused by: ${error2.cause.name}: ${error2.cause.message}`);
764
+ }
765
+ }
766
+ return output2;
767
+ }
768
+
769
+ // ../../packages/logger/src/sanitize.ts
770
+ var SENSITIVE_FIELDS = [
771
+ // Authentication
772
+ "password",
773
+ "senha",
774
+ "pwd",
775
+ "pass",
776
+ "token",
777
+ "accessToken",
778
+ "access_token",
779
+ "refreshToken",
780
+ "refresh_token",
781
+ "apiKey",
782
+ "api_key",
783
+ "apikey",
784
+ "secret",
785
+ "secretKey",
786
+ "secret_key",
787
+ "authorization",
788
+ "auth",
789
+ "bearer",
790
+ // Payment
791
+ "creditCard",
792
+ "credit_card",
793
+ "cardNumber",
794
+ "card_number",
795
+ "cvv",
796
+ "cvc",
797
+ "securityCode",
798
+ "security_code",
799
+ "accountNumber",
800
+ "account_number",
801
+ // Personal identification
802
+ "cpf",
803
+ "rg",
804
+ "ssn",
805
+ "cnh",
806
+ "passport",
807
+ "email",
808
+ "phone",
809
+ // Crypto
810
+ "privateKey",
811
+ "private_key",
812
+ "privatekey",
813
+ "mnemonic",
814
+ "seed",
815
+ "seedPhrase",
816
+ "seed_phrase"
817
+ ];
818
+ function email(value) {
819
+ if (!value || typeof value !== "string") return "[INVALID_EMAIL]";
820
+ const atIndex = value.indexOf("@");
821
+ if (atIndex === -1) return "[INVALID_EMAIL]";
822
+ const [user, domain] = [value.slice(0, atIndex), value.slice(atIndex + 1)];
823
+ if (!user || !domain) return "[INVALID_EMAIL]";
824
+ const maskedUser = user.length <= 1 ? "*" : user[0] + "*".repeat(Math.min(user.length - 1, 5));
825
+ return `${maskedUser}@${domain}`;
826
+ }
827
+ function phone(value) {
828
+ if (!value || typeof value !== "string") return "[INVALID_PHONE]";
829
+ const digits = value.replace(/\D/g, "");
830
+ if (digits.length < 4) return "[INVALID_PHONE]";
831
+ return "*".repeat(digits.length - 4) + digits.slice(-4);
832
+ }
833
+ function cpf(value) {
834
+ if (!value || typeof value !== "string") return "[INVALID_CPF]";
835
+ const digits = value.replace(/\D/g, "");
836
+ if (digits.length !== 11) return "[INVALID_CPF]";
837
+ return `***.***${digits.slice(6, 9)}-**`;
838
+ }
839
+ function creditCard(value) {
840
+ if (!value || typeof value !== "string") return "[INVALID_CARD]";
841
+ const digits = value.replace(/\D/g, "");
842
+ if (digits.length < 4) return "[INVALID_CARD]";
843
+ return "*".repeat(digits.length - 4) + digits.slice(-4);
844
+ }
845
+ function ip(value) {
846
+ if (!value || typeof value !== "string") return "[INVALID_IP]";
847
+ if (value.includes(":")) {
848
+ const lastColon = value.lastIndexOf(":");
849
+ return value.slice(0, lastColon + 1) + "xxxx";
850
+ }
851
+ const lastDot = value.lastIndexOf(".");
852
+ if (lastDot === -1) return "[INVALID_IP]";
853
+ return value.slice(0, lastDot + 1) + "xxx";
854
+ }
855
+ function token(value) {
856
+ if (!value || typeof value !== "string") return "[REDACTED]";
857
+ if (value.length <= 8) return "[REDACTED]";
858
+ return `${value.slice(0, 4)}...[REDACTED]...${value.slice(-4)}`;
859
+ }
860
+ function isSensitiveKey(key) {
861
+ const lowerKey = key.toLowerCase();
862
+ return SENSITIVE_FIELDS.some((field) => {
863
+ const lowerField = field.toLowerCase();
864
+ if (lowerKey === lowerField) return true;
865
+ if (lowerKey.endsWith(lowerField)) return true;
866
+ const fieldIdx = key.toLowerCase().indexOf(lowerField);
867
+ if (fieldIdx === 0) {
868
+ const afterIdx = lowerField.length;
869
+ if (afterIdx >= key.length) return true;
870
+ const nextChar = key[afterIdx];
871
+ if (nextChar && (nextChar === nextChar.toUpperCase() || !/[a-zA-Z]/.test(nextChar))) {
872
+ return true;
873
+ }
874
+ }
875
+ const patterns = [
876
+ `_${lowerField}`,
877
+ `${lowerField}_`,
878
+ `-${lowerField}`,
879
+ `${lowerField}-`,
880
+ `_${lowerField}_`,
881
+ `-${lowerField}-`
882
+ ];
883
+ return patterns.some((p) => lowerKey.includes(p));
884
+ });
885
+ }
886
+ function object(obj, additionalSensitiveKeys) {
887
+ if (!obj || typeof obj !== "object") return obj;
888
+ const sanitized = { ...obj };
889
+ for (const [key, value] of Object.entries(sanitized)) {
890
+ const isAdditionalKey = additionalSensitiveKeys?.some(
891
+ (k) => key.toLowerCase() === k.toLowerCase()
892
+ );
893
+ if (isSensitiveKey(key) || isAdditionalKey) {
894
+ sanitized[key] = "[REDACTED]";
895
+ continue;
896
+ }
897
+ if (value && typeof value === "object" && !Array.isArray(value)) {
898
+ sanitized[key] = object(
899
+ value,
900
+ additionalSensitiveKeys
901
+ );
902
+ continue;
903
+ }
904
+ if (Array.isArray(value)) {
905
+ sanitized[key] = value.map((item) => {
906
+ if (item && typeof item === "object" && !Array.isArray(item)) {
907
+ return object(item, additionalSensitiveKeys);
908
+ }
909
+ return item;
910
+ });
911
+ }
912
+ }
913
+ return sanitized;
914
+ }
915
+ function headers(hdrs) {
916
+ const sensitiveHeaders = [
917
+ "authorization",
918
+ "x-api-key",
919
+ "x-auth-token",
920
+ "cookie",
921
+ "set-cookie"
922
+ ];
923
+ const sanitized = { ...hdrs };
924
+ for (const key of Object.keys(sanitized)) {
925
+ if (sensitiveHeaders.some((h) => key.toLowerCase() === h)) {
926
+ sanitized[key] = "[REDACTED]";
927
+ }
928
+ }
929
+ return sanitized;
930
+ }
931
+ var sanitize = {
932
+ email,
933
+ phone,
934
+ cpf,
935
+ creditCard,
936
+ ip,
937
+ token,
938
+ object,
939
+ headers,
940
+ isSensitiveKey
941
+ };
942
+
943
+ // ../../packages/logger/src/logger.base.ts
944
+ function extractErrorInfo(error2, includeStack) {
945
+ const info = {
946
+ name: error2.name,
947
+ message: error2.message
948
+ };
949
+ if (includeStack && error2.stack) {
950
+ info.stack = error2.stack;
951
+ }
952
+ if ("code" in error2 && typeof error2.code === "string") {
953
+ info.code = error2.code;
954
+ }
955
+ if (error2.cause instanceof Error) {
956
+ info.cause = extractErrorInfo(error2.cause, includeStack);
957
+ }
958
+ return info;
959
+ }
960
+ var BaseLogger = class {
961
+ level;
962
+ format;
963
+ module;
964
+ service;
965
+ environment;
966
+ additionalContext;
967
+ transports;
968
+ shouldSanitize;
969
+ sensitiveFields;
970
+ includeCaller;
971
+ constructor(options, defaults) {
972
+ this.level = options.level ?? defaults.level;
973
+ this.format = options.format ?? defaults.format;
974
+ this.module = options.module;
975
+ this.service = options.service ?? defaults.service;
976
+ this.environment = options.environment ?? defaults.env;
977
+ this.additionalContext = {};
978
+ this.transports = [];
979
+ this.shouldSanitize = options.sanitize ?? true;
980
+ this.sensitiveFields = options.sensitiveFields;
981
+ this.includeCaller = options.includeCaller ?? "errors";
982
+ }
983
+ /**
984
+ * Get caller information for error logging (Node.js only).
985
+ */
986
+ getCaller() {
987
+ return void 0;
988
+ }
989
+ /**
990
+ * Flush any buffered logs (e.g., file transport).
991
+ */
992
+ flush() {
993
+ }
994
+ shouldLog(level) {
995
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
996
+ }
997
+ buildEntry(level, message, data, error2) {
998
+ const asyncContext = this.getLogContext() ?? {};
999
+ const context = {
1000
+ ...asyncContext,
1001
+ ...this.additionalContext
1002
+ };
1003
+ if (this.module) {
1004
+ context.module = this.module;
1005
+ }
1006
+ const shouldIncludeCaller = this.includeCaller === true || this.includeCaller === "errors" && (level === "error" || level === "fatal");
1007
+ if (shouldIncludeCaller) {
1008
+ const caller = this.getCaller();
1009
+ if (caller) context.caller = caller;
1010
+ }
1011
+ return {
1012
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1013
+ level,
1014
+ message,
1015
+ service: this.service,
1016
+ environment: this.environment,
1017
+ context,
1018
+ data,
1019
+ error: error2
1020
+ };
1021
+ }
1022
+ /**
1023
+ * Normalizes any data type to Record<string, unknown> for structured logging.
1024
+ */
1025
+ normalizeData(data) {
1026
+ if (data === null || data === void 0) {
1027
+ return void 0;
1028
+ }
1029
+ if (data instanceof Error) {
1030
+ return {
1031
+ error: data.message,
1032
+ errorName: data.name,
1033
+ stack: data.stack
1034
+ };
1035
+ }
1036
+ if (typeof data === "string" || typeof data === "number" || typeof data === "boolean") {
1037
+ return { value: data };
1038
+ }
1039
+ if (Array.isArray(data)) {
1040
+ return { items: data };
1041
+ }
1042
+ if (typeof data === "object") {
1043
+ const obj = data;
1044
+ return this.shouldSanitize ? sanitize.object(obj, this.sensitiveFields) : obj;
1045
+ }
1046
+ return { value: String(data) };
1047
+ }
1048
+ log(level, message, data, errorObj) {
1049
+ if (!this.shouldLog(level)) return;
1050
+ const normalizedData = this.normalizeData(data);
1051
+ const errorInfo = errorObj ? extractErrorInfo(errorObj, !this.isProd()) : void 0;
1052
+ const entry = this.buildEntry(level, message, normalizedData, errorInfo);
1053
+ const formatted = this.format === "json" ? formatJson(entry) : formatPretty(entry);
1054
+ for (const transport of this.transports) {
1055
+ transport.log(entry, formatted);
1056
+ }
1057
+ this.flush();
1058
+ }
1059
+ trace(message, ...args) {
1060
+ this.log("trace", message, args.length === 1 ? args[0] : args.length > 1 ? args : void 0);
1061
+ }
1062
+ debug(message, ...args) {
1063
+ this.log("debug", message, args.length === 1 ? args[0] : args.length > 1 ? args : void 0);
1064
+ }
1065
+ info(message, ...args) {
1066
+ this.log("info", message, args.length === 1 ? args[0] : args.length > 1 ? args : void 0);
1067
+ }
1068
+ warn(message, ...args) {
1069
+ this.log("warn", message, args.length === 1 ? args[0] : args.length > 1 ? args : void 0);
1070
+ }
1071
+ error(message, errorOrData, data) {
1072
+ if (errorOrData instanceof Error) {
1073
+ this.log("error", message, data, errorOrData);
1074
+ } else {
1075
+ this.log("error", message, errorOrData);
1076
+ }
1077
+ }
1078
+ /**
1079
+ * Log a fatal error (system-critical).
1080
+ */
1081
+ fatal(message, errorOrData, data) {
1082
+ if (errorOrData instanceof Error) {
1083
+ this.log("fatal", message, data, errorOrData);
1084
+ } else {
1085
+ this.log("fatal", message, errorOrData);
1086
+ }
1087
+ }
1088
+ /**
1089
+ * Get the current trace ID from context.
1090
+ */
1091
+ getTraceId() {
1092
+ const ctx = this.getLogContext();
1093
+ return ctx?.traceId ?? this.additionalContext.traceId;
1094
+ }
1095
+ /**
1096
+ * Get the current span ID from context.
1097
+ */
1098
+ getSpanId() {
1099
+ const ctx = this.getLogContext();
1100
+ return ctx?.spanId ?? this.additionalContext.spanId;
1101
+ }
1102
+ /**
1103
+ * Time an async operation and log its duration.
1104
+ */
1105
+ async time(operation, fn, data) {
1106
+ const start = Date.now();
1107
+ try {
1108
+ const result = await fn();
1109
+ const duration = Date.now() - start;
1110
+ this.info(`${operation} completed`, { ...data, duration, operation });
1111
+ return result;
1112
+ } catch (error2) {
1113
+ const duration = Date.now() - start;
1114
+ this.error(`${operation} failed`, error2, { ...data, duration, operation });
1115
+ throw error2;
1116
+ }
1117
+ }
1118
+ /**
1119
+ * Time a sync operation and log its duration.
1120
+ */
1121
+ timeSync(operation, fn, data) {
1122
+ const start = Date.now();
1123
+ try {
1124
+ const result = fn();
1125
+ const duration = Date.now() - start;
1126
+ this.info(`${operation} completed`, { ...data, duration, operation });
1127
+ return result;
1128
+ } catch (error2) {
1129
+ const duration = Date.now() - start;
1130
+ this.error(`${operation} failed`, error2, { ...data, duration, operation });
1131
+ throw error2;
1132
+ }
1133
+ }
1134
+ };
1135
+
1136
+ // ../../packages/logger/src/transports/console.ts
1137
+ var ConsoleTransport = class {
1138
+ log(entry, formatted) {
1139
+ if (entry.level === "error" || entry.level === "fatal") {
1140
+ process.stderr.write(formatted + "\n");
1141
+ } else {
1142
+ process.stdout.write(formatted + "\n");
1143
+ }
1144
+ }
1145
+ };
1146
+
1147
+ // ../../packages/logger/src/transports/file.ts
1148
+ import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
1149
+ import { join } from "node:path";
1150
+ var FileTransport = class {
1151
+ dir;
1152
+ initialized = false;
1153
+ buffer = [];
1154
+ constructor(options) {
1155
+ this.dir = options.dir;
1156
+ }
1157
+ init() {
1158
+ if (this.initialized) return;
1159
+ if (!existsSync(this.dir)) {
1160
+ mkdirSync(this.dir, { recursive: true });
1161
+ }
1162
+ const gitignore = join(this.dir, ".gitignore");
1163
+ if (!existsSync(gitignore)) {
1164
+ writeFileSync(gitignore, "*.log\n");
1165
+ }
1166
+ this.initialized = true;
1167
+ }
1168
+ log(entry, formatted) {
1169
+ this.init();
1170
+ const line = formatted + "\n";
1171
+ this.buffer.push({ file: "combined.log", content: line });
1172
+ if (entry.level === "error") {
1173
+ this.buffer.push({ file: "error.log", content: line });
1174
+ }
1175
+ }
1176
+ flush() {
1177
+ for (const { file, content } of this.buffer) {
1178
+ const filepath = join(this.dir, file);
1179
+ appendFileSync(filepath, content);
1180
+ }
1181
+ this.buffer = [];
1182
+ }
1183
+ };
1184
+
1185
+ // ../../packages/logger/src/logger.ts
1186
+ function detectEnvironment() {
1187
+ const isProd = process.env.NODE_ENV === "production" || !!process.env.VERCEL || !!process.env.RAILWAY_ENVIRONMENT;
1188
+ let env = process.env.NODE_ENV || "development";
1189
+ if (process.env.VERCEL) env = process.env.VERCEL_ENV || "production";
1190
+ if (process.env.RAILWAY_ENVIRONMENT) env = process.env.RAILWAY_ENVIRONMENT;
1191
+ return { isProd, env };
1192
+ }
1193
+ function getDefaultFormat() {
1194
+ const override = process.env.LOG_FORMAT;
1195
+ if (override === "json" || override === "pretty") return override;
1196
+ const { isProd } = detectEnvironment();
1197
+ return isProd ? "json" : "pretty";
1198
+ }
1199
+ function getDefaultLevel() {
1200
+ const override = process.env.LOG_LEVEL;
1201
+ if (override && override in LOG_LEVELS) return override;
1202
+ const { isProd } = detectEnvironment();
1203
+ return isProd ? "info" : "debug";
1204
+ }
1205
+ function getDefaultService() {
1206
+ return process.env.SERVICE_NAME || process.env.VERCEL_GIT_REPO_SLUG || process.env.RAILWAY_SERVICE_NAME || "unknown-service";
1207
+ }
1208
+ function getCaller() {
1209
+ const err = new Error();
1210
+ const stack = err.stack?.split("\n");
1211
+ if (!stack) return void 0;
1212
+ for (let i = 3; i < stack.length; i++) {
1213
+ const line = stack[i];
1214
+ if (!line) continue;
1215
+ if (line.includes("/logger/src/")) continue;
1216
+ if (line.includes("@repo/logger")) continue;
1217
+ const match = line.match(/at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?/);
1218
+ if (match) {
1219
+ const [, fnName, file, lineNum] = match;
1220
+ const fileName = file?.split("/").pop() || file;
1221
+ return fnName ? `${fileName}:${lineNum}:${fnName}` : `${fileName}:${lineNum}`;
1222
+ }
1223
+ }
1224
+ return void 0;
1225
+ }
1226
+ var Logger = class _Logger extends BaseLogger {
1227
+ fileTransport;
1228
+ _isProd;
1229
+ constructor(options = {}) {
1230
+ const { isProd, env } = detectEnvironment();
1231
+ super(options, {
1232
+ level: getDefaultLevel(),
1233
+ format: getDefaultFormat(),
1234
+ service: getDefaultService(),
1235
+ env
1236
+ });
1237
+ this._isProd = isProd;
1238
+ this.transports = [new ConsoleTransport()];
1239
+ if (!isProd && !process.env.VERCEL && !process.env.RAILWAY_ENVIRONMENT) {
1240
+ this.fileTransport = new FileTransport({ dir: "logs" });
1241
+ this.transports.push(this.fileTransport);
1242
+ }
1243
+ }
1244
+ getLogContext() {
1245
+ return getLogContext();
1246
+ }
1247
+ isProd() {
1248
+ return this._isProd;
1249
+ }
1250
+ getCaller() {
1251
+ return getCaller();
1252
+ }
1253
+ flush() {
1254
+ this.fileTransport?.flush();
1255
+ }
1256
+ /**
1257
+ * Create a child logger with additional context.
1258
+ * Child shares transports with parent.
1259
+ */
1260
+ child(context) {
1261
+ const child = new _Logger({
1262
+ level: this.level,
1263
+ format: this.format,
1264
+ module: this.module,
1265
+ service: this.service,
1266
+ environment: this.environment,
1267
+ sanitize: this.shouldSanitize,
1268
+ sensitiveFields: this.sensitiveFields,
1269
+ includeCaller: this.includeCaller
1270
+ });
1271
+ child.additionalContext = { ...this.additionalContext, ...context };
1272
+ child.transports = this.transports;
1273
+ child.fileTransport = this.fileTransport;
1274
+ return child;
1275
+ }
1276
+ };
1277
+
1278
+ // ../../packages/logger/src/index.ts
1279
+ var logger = new Logger();
1280
+ function createLogger(module, options) {
1281
+ return new Logger({ ...options, module });
1282
+ }
1283
+
1284
+ // ../../packages/tostudy-core/src/courses/list-courses.ts
1285
+ async function listCourses(input, deps) {
1286
+ deps.logger.info("tostudy-core: listCourses", { userId: input.userId });
1287
+ return deps.data.courses.list(input.userId);
1288
+ }
1289
+
1290
+ // ../../packages/tostudy-core/src/courses/select-course.ts
1291
+ async function selectCourse(input, deps) {
1292
+ deps.logger.info("tostudy-core: selectCourse", input);
1293
+ return deps.data.courses.select(input.userId, input.courseId);
1294
+ }
1295
+
1296
+ // ../../packages/tostudy-core/src/courses/get-progress.ts
1297
+ async function getProgress(input, deps) {
1298
+ deps.logger.info("tostudy-core: getProgress", input);
1299
+ return deps.data.courses.getProgress(input.enrollmentId);
1300
+ }
1301
+
1302
+ // ../../packages/tostudy-core/src/adapters/http.ts
1303
+ var CliApiError = class extends Error {
1304
+ constructor(message, status) {
1305
+ super(message);
1306
+ this.status = status;
1307
+ this.name = "CliApiError";
1308
+ }
1309
+ };
1310
+ async function apiFetch(url, token2, init) {
1311
+ const response = await fetch(url, {
1312
+ ...init,
1313
+ headers: {
1314
+ "Content-Type": "application/json",
1315
+ Authorization: `Bearer ${token2}`,
1316
+ ...init?.headers
1317
+ }
1318
+ });
1319
+ const body = await response.json();
1320
+ if (!response.ok) {
1321
+ throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
1322
+ }
1323
+ return body;
1324
+ }
1325
+ function createHttpProvider(apiUrl, token2) {
1326
+ const base = `${apiUrl}/api/cli`;
1327
+ return {
1328
+ courses: {
1329
+ list: async (_userId) => {
1330
+ const res = await apiFetch(`${base}/courses`, token2);
1331
+ return res.courses;
1332
+ },
1333
+ select: async (_userId, courseId) => {
1334
+ const res = await apiFetch(`${base}/courses/select`, token2, {
1335
+ method: "POST",
1336
+ body: JSON.stringify({ courseId })
1337
+ });
1338
+ return res.course;
1339
+ },
1340
+ getProgress: async (enrollmentId) => {
1341
+ return apiFetch(`${base}/progress?enrollmentId=${encodeURIComponent(enrollmentId)}`, token2);
1342
+ }
1343
+ },
1344
+ lessons: {
1345
+ next: async (enrollmentId, userConfirmation) => {
1346
+ return apiFetch(`${base}/lessons/next`, token2, {
1347
+ method: "POST",
1348
+ body: JSON.stringify({ enrollmentId, userConfirmation })
1349
+ });
1350
+ },
1351
+ getContent: async (lessonId, enrollmentId) => {
1352
+ const params = new URLSearchParams({ lessonId });
1353
+ if (enrollmentId) params.set("enrollmentId", enrollmentId);
1354
+ return apiFetch(`${base}/lessons/content?${params.toString()}`, token2);
1355
+ },
1356
+ getHint: async (_userId, enrollmentId) => {
1357
+ return apiFetch(`${base}/lessons/hint`, token2, {
1358
+ method: "POST",
1359
+ body: JSON.stringify({ enrollmentId })
1360
+ });
1361
+ },
1362
+ startModule: async (enrollmentId) => {
1363
+ return apiFetch(`${base}/modules/start`, token2, {
1364
+ method: "POST",
1365
+ body: JSON.stringify({ enrollmentId })
1366
+ });
1367
+ },
1368
+ startNextModule: async (enrollmentId) => {
1369
+ return apiFetch(`${base}/modules/start-next`, token2, {
1370
+ method: "POST",
1371
+ body: JSON.stringify({ enrollmentId })
1372
+ });
1373
+ }
1374
+ },
1375
+ exercises: {
1376
+ validate: async (lessonId, solution, _userId, enrollmentId) => {
1377
+ return apiFetch(`${base}/exercises/validate`, token2, {
1378
+ method: "POST",
1379
+ body: JSON.stringify({ lessonId, solution, enrollmentId })
1380
+ });
1381
+ }
1382
+ }
1383
+ };
1384
+ }
1385
+
1386
+ // src/commands/courses.ts
1387
+ var logger2 = createLogger("cli:courses");
1388
+ var coursesCommand = new Command5("courses").description("List your enrolled courses with progress").option("--json", "Output structured JSON").action(async (opts) => {
1389
+ try {
1390
+ const session = await requireSession();
1391
+ const data = createHttpProvider(session.apiUrl, session.token);
1392
+ const deps = { data, logger: logger2 };
1393
+ const courses = await listCourses({ userId: session.userId }, deps);
1394
+ if (opts.json) {
1395
+ output(courses, { json: true });
1396
+ } else {
1397
+ output(formatCourseList(courses), { json: false });
1398
+ }
1399
+ } catch (err) {
1400
+ const msg = err instanceof Error ? err.message : String(err);
1401
+ error(msg);
1402
+ }
1403
+ });
1404
+
1405
+ // src/commands/select.ts
1406
+ import { Command as Command6 } from "commander";
1407
+ var logger3 = createLogger("cli:select");
1408
+ var selectCommand = new Command6("select").description("Activate a course by ID or list index number").argument("<course>", "Course ID (UUID) or index number from `tostudy courses`").option("--json", "Output structured JSON").action(async (course, opts) => {
1409
+ try {
1410
+ const session = await requireSession();
1411
+ const data = createHttpProvider(session.apiUrl, session.token);
1412
+ const deps = { data, logger: logger3 };
1413
+ const courses = await listCourses({ userId: session.userId }, deps);
1414
+ let courseId = course;
1415
+ let enrollmentId = "";
1416
+ if (/^\d+$/.test(course)) {
1417
+ const idx = parseInt(course, 10) - 1;
1418
+ if (idx < 0 || idx >= courses.length) {
1419
+ error(`\xCDndice ${course} inv\xE1lido. Voc\xEA tem ${courses.length} curso(s).`);
1420
+ }
1421
+ const found = courses[idx];
1422
+ courseId = found.courseId;
1423
+ enrollmentId = found.enrollmentId;
1424
+ } else {
1425
+ const found = courses.find((c) => c.courseId === course);
1426
+ if (found) {
1427
+ enrollmentId = found.enrollmentId;
1428
+ }
1429
+ }
1430
+ const detail = await selectCourse({ userId: session.userId, courseId }, deps);
1431
+ await setActiveCourse({
1432
+ courseId: detail.courseId,
1433
+ courseTitle: detail.courseTitle,
1434
+ enrollmentId
1435
+ });
1436
+ if (opts.json) {
1437
+ output({ ...detail, enrollmentId }, { json: true });
1438
+ } else {
1439
+ output(
1440
+ [
1441
+ `\u2713 Curso ativado: ${detail.courseTitle}`,
1442
+ ` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
1443
+ "",
1444
+ "\u2192 tostudy progress para ver seu progresso detalhado",
1445
+ "\u2192 tostudy start para come\xE7ar o m\xF3dulo atual",
1446
+ "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o"
1447
+ ].join("\n"),
1448
+ { json: false }
1449
+ );
1450
+ }
1451
+ } catch (err) {
1452
+ const msg = err instanceof Error ? err.message : String(err);
1453
+ error(msg);
1454
+ }
1455
+ });
1456
+
1457
+ // src/commands/progress.ts
1458
+ import { Command as Command7 } from "commander";
1459
+ var logger4 = createLogger("cli:progress");
1460
+ var progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
1461
+ try {
1462
+ const session = await requireSession();
1463
+ const activeCourse = await requireActiveCourse();
1464
+ const data = createHttpProvider(session.apiUrl, session.token);
1465
+ const deps = { data, logger: logger4 };
1466
+ const progress = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
1467
+ if (opts.json) {
1468
+ output(progress, { json: true });
1469
+ } else {
1470
+ output(formatProgress(progress), { json: false });
1471
+ }
1472
+ } catch (err) {
1473
+ const msg = err instanceof Error ? err.message : String(err);
1474
+ error(msg);
1475
+ }
1476
+ });
1477
+
1478
+ // src/commands/start.ts
1479
+ import { Command as Command8 } from "commander";
1480
+
1481
+ // ../../packages/tostudy-core/src/lessons/next-lesson.ts
1482
+ async function nextLesson(input, deps) {
1483
+ deps.logger.info("tostudy-core: nextLesson", { enrollmentId: input.enrollmentId });
1484
+ return deps.data.lessons.next(input.enrollmentId, input.userConfirmation);
1485
+ }
1486
+
1487
+ // ../../packages/tostudy-core/src/lessons/get-content.ts
1488
+ async function getContent(input, deps) {
1489
+ deps.logger.info("tostudy-core: getContent", { lessonId: input.lessonId });
1490
+ return deps.data.lessons.getContent(input.lessonId, input.enrollmentId);
1491
+ }
1492
+
1493
+ // ../../packages/tostudy-core/src/lessons/get-hint.ts
1494
+ async function getHint(input, deps) {
1495
+ deps.logger.info("tostudy-core: getHint", { userId: input.userId });
1496
+ return deps.data.lessons.getHint(input.userId, input.enrollmentId);
1497
+ }
1498
+
1499
+ // ../../packages/tostudy-core/src/lessons/start-module.ts
1500
+ async function startModule(input, deps) {
1501
+ deps.logger.info("tostudy-core: startModule", { enrollmentId: input.enrollmentId });
1502
+ return deps.data.lessons.startModule(input.enrollmentId);
1503
+ }
1504
+
1505
+ // ../../packages/tostudy-core/src/lessons/start-next-module.ts
1506
+ async function startNextModule(input, deps) {
1507
+ deps.logger.info("tostudy-core: startNextModule", { enrollmentId: input.enrollmentId });
1508
+ return deps.data.lessons.startNextModule(input.enrollmentId);
1509
+ }
1510
+
1511
+ // src/commands/start.ts
1512
+ var logger5 = createLogger("cli:start");
1513
+ var startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
1514
+ try {
1515
+ const session = await requireSession();
1516
+ const activeCourse = await requireActiveCourse();
1517
+ const data = createHttpProvider(session.apiUrl, session.token);
1518
+ const deps = { data, logger: logger5 };
1519
+ const moduleData = await startModule({ enrollmentId: activeCourse.enrollmentId }, deps);
1520
+ if (opts.json) {
1521
+ output(moduleData, { json: true });
1522
+ } else {
1523
+ output(formatModuleStart(moduleData), { json: false });
1524
+ }
1525
+ } catch (err) {
1526
+ const msg = err instanceof Error ? err.message : String(err);
1527
+ error(msg);
1528
+ }
1529
+ });
1530
+
1531
+ // src/commands/start-next.ts
1532
+ import { Command as Command9 } from "commander";
1533
+ var logger6 = createLogger("cli:start-next");
1534
+ var startNextCommand = new Command9("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
1535
+ try {
1536
+ const session = await requireSession();
1537
+ const activeCourse = await requireActiveCourse();
1538
+ const data = createHttpProvider(session.apiUrl, session.token);
1539
+ const deps = { data, logger: logger6 };
1540
+ const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
1541
+ if (opts.json) {
1542
+ output(moduleData, { json: true });
1543
+ } else {
1544
+ output(formatModuleStart(moduleData), { json: false });
1545
+ }
1546
+ } catch (err) {
1547
+ const msg = err instanceof Error ? err.message : String(err);
1548
+ error(msg);
1549
+ }
1550
+ });
1551
+
1552
+ // src/commands/next.ts
1553
+ import { Command as Command10 } from "commander";
1554
+ var logger7 = createLogger("cli:next");
1555
+ var nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
1556
+ try {
1557
+ const session = await requireSession();
1558
+ const activeCourse = await requireActiveCourse();
1559
+ const data = createHttpProvider(session.apiUrl, session.token);
1560
+ const deps = { data, logger: logger7 };
1561
+ const lessonData = await nextLesson(
1562
+ { enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
1563
+ deps
1564
+ );
1565
+ if (opts.json) {
1566
+ output(lessonData, { json: true });
1567
+ } else {
1568
+ output(formatLesson(lessonData), { json: false });
1569
+ }
1570
+ } catch (err) {
1571
+ const msg = err instanceof Error ? err.message : String(err);
1572
+ error(msg);
1573
+ }
1574
+ });
1575
+
1576
+ // src/commands/lesson.ts
1577
+ import { Command as Command11 } from "commander";
1578
+ var logger8 = createLogger("cli:lesson");
1579
+ function formatLessonContent(data) {
1580
+ const lines = [
1581
+ `\u2501\u2501\u2501 Li\xE7\xE3o: ${data.title} \u2501\u2501\u2501`,
1582
+ "",
1583
+ `Tipo: ${data.type} | Dura\xE7\xE3o estimada: ~${data.estimatedTimeMinutes} min`,
1584
+ "",
1585
+ data.content
1586
+ ];
1587
+ if (data.acceptanceCriteria) {
1588
+ lines.push("", "Crit\xE9rios de aceita\xE7\xE3o:", data.acceptanceCriteria);
1589
+ }
1590
+ if (data.hints && data.hints.length > 0) {
1591
+ lines.push("", `Dicas dispon\xEDveis: ${data.hints.length}`);
1592
+ }
1593
+ lines.push(
1594
+ "",
1595
+ "\u2192 tostudy validate <arquivo> para validar sua solu\xE7\xE3o",
1596
+ "\u2192 tostudy hint para obter uma dica",
1597
+ "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o"
1598
+ );
1599
+ return lines.join("\n");
1600
+ }
1601
+ var lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
1602
+ try {
1603
+ const session = await requireSession();
1604
+ const activeCourse = await requireActiveCourse();
1605
+ const data = createHttpProvider(session.apiUrl, session.token);
1606
+ const deps = { data, logger: logger8 };
1607
+ const lessonId = activeCourse.currentLessonId;
1608
+ if (!lessonId) {
1609
+ error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
1610
+ }
1611
+ const content = await getContent({ lessonId, enrollmentId: activeCourse.enrollmentId }, deps);
1612
+ if (opts.json) {
1613
+ output(content, { json: true });
1614
+ } else {
1615
+ output(formatLessonContent(content), { json: false });
1616
+ }
1617
+ } catch (err) {
1618
+ const msg = err instanceof Error ? err.message : String(err);
1619
+ error(msg);
1620
+ }
1621
+ });
1622
+
1623
+ // src/commands/hint.ts
1624
+ import { Command as Command12 } from "commander";
1625
+ var logger9 = createLogger("cli:hint");
1626
+ var hintCommand = new Command12("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
1627
+ try {
1628
+ const session = await requireSession();
1629
+ const activeCourse = await requireActiveCourse();
1630
+ const data = createHttpProvider(session.apiUrl, session.token);
1631
+ const deps = { data, logger: logger9 };
1632
+ const hint = await getHint(
1633
+ { userId: session.userId, enrollmentId: activeCourse.enrollmentId },
1634
+ deps
1635
+ );
1636
+ if (opts.json) {
1637
+ output(hint, { json: true });
1638
+ } else {
1639
+ output(formatHint(hint), { json: false });
1640
+ }
1641
+ } catch (err) {
1642
+ const msg = err instanceof Error ? err.message : String(err);
1643
+ error(msg);
1644
+ }
1645
+ });
1646
+
1647
+ // src/commands/validate.ts
1648
+ import fs3 from "node:fs";
1649
+ import { Command as Command13 } from "commander";
1650
+
1651
+ // ../../packages/tostudy-core/src/exercises/validate-solution.ts
1652
+ async function validateSolution(input, deps) {
1653
+ deps.logger.info("tostudy-core: validateSolution", { lessonId: input.lessonId });
1654
+ return deps.data.exercises.validate(
1655
+ input.lessonId,
1656
+ input.solution,
1657
+ input.userId,
1658
+ input.enrollmentId
1659
+ );
1660
+ }
1661
+
1662
+ // src/commands/validate.ts
1663
+ var logger10 = createLogger("cli:validate");
1664
+ var validateCommand = new Command13("validate").description("Validate your solution for the current exercise").argument("[file]", "Path to the solution file to read").option("--stdin", "Read solution from stdin instead of a file").option("--json", "Output structured JSON").action(async (file, opts) => {
1665
+ try {
1666
+ const session = await requireSession();
1667
+ const activeCourse = await requireActiveCourse();
1668
+ const lessonId = activeCourse.currentLessonId;
1669
+ if (!lessonId) {
1670
+ error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
1671
+ }
1672
+ let solution;
1673
+ if (opts.stdin) {
1674
+ solution = fs3.readFileSync("/dev/stdin", "utf-8");
1675
+ } else if (file) {
1676
+ if (!fs3.existsSync(file)) {
1677
+ error(`Arquivo n\xE3o encontrado: ${file}`);
1678
+ }
1679
+ solution = fs3.readFileSync(file, "utf-8");
1680
+ } else {
1681
+ error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
1682
+ }
1683
+ const data = createHttpProvider(session.apiUrl, session.token);
1684
+ const deps = { data, logger: logger10 };
1685
+ const result = await validateSolution(
1686
+ {
1687
+ lessonId,
1688
+ solution,
1689
+ userId: session.userId,
1690
+ enrollmentId: activeCourse.enrollmentId
1691
+ },
1692
+ deps
1693
+ );
1694
+ if (opts.json) {
1695
+ output(result, { json: true });
1696
+ } else {
1697
+ output(formatValidation(result), { json: false });
1698
+ }
1699
+ process.exit(result.passed ? 0 : 1);
1700
+ } catch (err) {
1701
+ const msg = err instanceof Error ? err.message : String(err);
1702
+ error(msg);
1703
+ }
1704
+ });
1705
+
1706
+ // src/commands/menu.ts
1707
+ import { Command as Command14 } from "commander";
1708
+ var menuCommand = new Command14("menu").description("Show available commands and current study context").action(async () => {
1709
+ const session = await getSession();
1710
+ const activeCourse = session ? await getActiveCourse() : null;
1711
+ const lines = [
1712
+ "\u250C\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\u2500\u2500\u2500\u2500\u2510",
1713
+ "\u2502 ToStudy CLI \u2014 Menu \u2502",
1714
+ "\u2514\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\u2500\u2500\u2500\u2500\u2518",
1715
+ ""
1716
+ ];
1717
+ if (!session) {
1718
+ lines.push(
1719
+ " Status: N\xE3o autenticado",
1720
+ "",
1721
+ " Para come\xE7ar:",
1722
+ " tostudy login Autenticar com sua conta ToStudy",
1723
+ ""
1724
+ );
1725
+ } else {
1726
+ lines.push(` Usu\xE1rio: ${session.userName}`);
1727
+ if (activeCourse) {
1728
+ lines.push(` Curso ativo: ${activeCourse.courseTitle}`, "");
1729
+ } else {
1730
+ lines.push(
1731
+ " Curso ativo: (nenhum)",
1732
+ " \u2192 tostudy courses para ver seus cursos",
1733
+ " \u2192 tostudy select <n\xFAmero> para ativar um curso",
1734
+ ""
1735
+ );
1736
+ }
1737
+ lines.push(
1738
+ " \u2500\u2500\u2500 Navega\xE7\xE3o \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",
1739
+ " tostudy courses Listar seus cursos",
1740
+ " tostudy select <id> Ativar um curso",
1741
+ " tostudy progress Ver progresso do curso ativo",
1742
+ "",
1743
+ " \u2500\u2500\u2500 Estudo \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",
1744
+ " tostudy start Iniciar/retomar o m\xF3dulo atual",
1745
+ " tostudy start-next Avan\xE7ar para o pr\xF3ximo m\xF3dulo",
1746
+ " tostudy next Avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
1747
+ " tostudy lesson Ver conte\xFAdo da li\xE7\xE3o atual",
1748
+ "",
1749
+ " \u2500\u2500\u2500 Exerc\xEDcios \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",
1750
+ " tostudy validate <arquivo> Validar sua solu\xE7\xE3o",
1751
+ " tostudy hint Obter uma dica progressiva",
1752
+ "",
1753
+ " \u2500\u2500\u2500 Dicas r\xE1pidas \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",
1754
+ " Adicione --json a qualquer comando para sa\xEDda em JSON",
1755
+ " Adicione --help a qualquer comando para ver op\xE7\xF5es",
1756
+ ""
1757
+ );
1758
+ }
1759
+ output(lines.join("\n"), { json: false });
1760
+ });
1761
+
1762
+ // src/cli.ts
1763
+ function createProgram() {
1764
+ const program2 = new Command15();
1765
+ program2.name("tostudy").description("ToStudy CLI \u2014 study courses from the terminal").version("0.1.0").option("--json", "Output structured JSON (for LLM agents)").option("--verbose", "Enable debug output").option("--course <id>", "Override active course ID");
1766
+ program2.addCommand(setupCommand);
1767
+ program2.addCommand(doctorCommand);
1768
+ program2.addCommand(loginCommand);
1769
+ program2.addCommand(logoutCommand);
1770
+ program2.addCommand(coursesCommand);
1771
+ program2.addCommand(selectCommand);
1772
+ program2.addCommand(progressCommand);
1773
+ program2.addCommand(startCommand);
1774
+ program2.addCommand(startNextCommand);
1775
+ program2.addCommand(nextCommand);
1776
+ program2.addCommand(lessonCommand);
1777
+ program2.addCommand(hintCommand);
1778
+ program2.addCommand(validateCommand);
1779
+ program2.addCommand(menuCommand);
1780
+ return program2;
1781
+ }
1782
+
1783
+ // src/cli-entry.ts
1784
+ var program = createProgram();
1785
+ program.parse();
1786
+ //# sourceMappingURL=cli.js.map