@tostudy-ai/cli 0.5.0 → 0.7.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.
Files changed (3) hide show
  1. package/dist/cli.js +1390 -620
  2. package/dist/cli.js.map +4 -4
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -219,6 +219,47 @@ function getConfigDir(override) {
219
219
  }
220
220
  return path2.join(os2.homedir(), ".tostudy");
221
221
  }
222
+ function getCourseOnboardingPath(configDir) {
223
+ return path2.join(getConfigDir(configDir), "course-onboarding.json");
224
+ }
225
+ function readCourseOnboardingState(configDir) {
226
+ const onboardingPath = getCourseOnboardingPath(configDir);
227
+ if (!fs2.existsSync(onboardingPath)) return {};
228
+ return JSON.parse(fs2.readFileSync(onboardingPath, "utf-8"));
229
+ }
230
+ function writeCourseOnboardingState(state, configDir) {
231
+ const dir = getConfigDir(configDir);
232
+ fs2.mkdirSync(dir, { recursive: true });
233
+ fs2.writeFileSync(getCourseOnboardingPath(configDir), JSON.stringify(state, null, 2), {
234
+ mode: 384
235
+ });
236
+ }
237
+ async function getCourseOnboardingState(courseId, configDir) {
238
+ const state = readCourseOnboardingState(configDir);
239
+ return state[courseId] ?? null;
240
+ }
241
+ async function updateCourseOnboardingState(courseId, patch, configDir) {
242
+ const state = readCourseOnboardingState(configDir);
243
+ state[courseId] = {
244
+ ...state[courseId] ?? {},
245
+ ...patch
246
+ };
247
+ writeCourseOnboardingState(state, configDir);
248
+ }
249
+ async function setCourseWorkspacePath(courseId, workspacePath, configDir) {
250
+ await updateCourseOnboardingState(courseId, { workspacePath }, configDir);
251
+ }
252
+ async function saveCourseLearnerProfile(course, learnerProfile, artifacts, configDir) {
253
+ await updateCourseOnboardingState(
254
+ course.courseId,
255
+ {
256
+ enrollmentId: course.enrollmentId,
257
+ learnerProfile,
258
+ artifacts
259
+ },
260
+ configDir
261
+ );
262
+ }
222
263
  async function saveSession(session, configDir) {
223
264
  const dir = getConfigDir(configDir);
224
265
  fs2.mkdirSync(dir, { recursive: true });
@@ -263,6 +304,8 @@ async function clearSession(configDir) {
263
304
  if (fs2.existsSync(p)) fs2.unlinkSync(p);
264
305
  const ap = path2.join(dir, "active-course.json");
265
306
  if (fs2.existsSync(ap)) fs2.unlinkSync(ap);
307
+ const onboardingPath = getCourseOnboardingPath(configDir);
308
+ if (fs2.existsSync(onboardingPath)) fs2.unlinkSync(onboardingPath);
266
309
  await deleteStoredSessionSecrets(dir);
267
310
  }
268
311
  async function getActiveCourse(configDir) {
@@ -274,7 +317,9 @@ async function getActiveCourse(configDir) {
274
317
  async function setActiveCourse(course, configDir) {
275
318
  const dir = getConfigDir(configDir);
276
319
  fs2.mkdirSync(dir, { recursive: true });
277
- fs2.writeFileSync(path2.join(dir, "active-course.json"), JSON.stringify(course, null, 2), {
320
+ const onboardingState = await getCourseOnboardingState(course.courseId, configDir);
321
+ const persistedCourse = onboardingState?.initCompletedAt && !course.lastInitCourseId ? { ...course, lastInitCourseId: course.courseId } : course;
322
+ fs2.writeFileSync(path2.join(dir, "active-course.json"), JSON.stringify(persistedCourse, null, 2), {
278
323
  mode: 384
279
324
  });
280
325
  }
@@ -332,6 +377,13 @@ async function setLastInitCourseId(courseId, configDir) {
332
377
  const existing = await getActiveCourse(configDir);
333
378
  if (!existing) return;
334
379
  await setActiveCourse({ ...existing, lastInitCourseId: courseId }, configDir);
380
+ await updateCourseOnboardingState(
381
+ courseId,
382
+ {
383
+ initCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
384
+ },
385
+ configDir
386
+ );
335
387
  }
336
388
  async function checkCourseDrift(configDir) {
337
389
  const course = await getActiveCourse(configDir);
@@ -372,6 +424,23 @@ function progressBar(percent, width = 20) {
372
424
  const empty = width - filled;
373
425
  return "\u2588".repeat(filled) + "\u2591".repeat(empty) + ` ${clamped}%`;
374
426
  }
427
+ function filterExerciseContent(content, tierIndex) {
428
+ const positions = [];
429
+ for (const pattern of TIER_HEADERS) {
430
+ const match = content.match(pattern);
431
+ if (match?.index != null) {
432
+ positions.push({ index: match.index, match: match[0] });
433
+ }
434
+ }
435
+ positions.sort((a, b2) => a.index - b2.index);
436
+ if (positions.length === 0) return content;
437
+ const intro = content.slice(0, positions[0].index).trimEnd();
438
+ const tierPos = positions[tierIndex];
439
+ if (!tierPos) return content;
440
+ const nextPos = positions[tierIndex + 1];
441
+ const tierContent = nextPos ? content.slice(tierPos.index, nextPos.index).trimEnd() : content.slice(tierPos.index).trimEnd();
442
+ return intro + "\n\n" + tierContent;
443
+ }
375
444
  function formatCourseList(courses3) {
376
445
  if (courses3.length === 0) {
377
446
  return [
@@ -383,9 +452,17 @@ function formatCourseList(courses3) {
383
452
  const lines = ["Seus cursos:", ""];
384
453
  courses3.forEach((course, idx) => {
385
454
  const bar = progressBar(course.progress);
455
+ const enrolledDate = course.enrolledAt ? new Date(course.enrolledAt).toLocaleDateString("pt-BR", {
456
+ year: "numeric",
457
+ month: "short",
458
+ day: "numeric"
459
+ }) : null;
386
460
  lines.push(` ${idx + 1}. ${course.title}`);
387
461
  lines.push(` ${bar}`);
388
462
  lines.push(` Professor: ${course.creatorName}`);
463
+ if (enrolledDate) {
464
+ lines.push(` Inscrito em: ${enrolledDate}`);
465
+ }
389
466
  lines.push("");
390
467
  });
391
468
  lines.push("\u2192 tostudy select <n\xFAmero> para ativar um curso");
@@ -413,16 +490,17 @@ function formatProgress(data) {
413
490
  return lines.join("\n");
414
491
  }
415
492
  function formatLesson(data) {
416
- const { lesson, wayfinding, progress: progress3 } = data;
493
+ const { lesson, wayfinding, progress: progress3, exercise } = data;
417
494
  const courseBar = progressBar(progress3.coursePercent);
418
495
  const moduleBar = progressBar(progress3.modulePercent);
496
+ const tierSuffix = exercise ? ` \xB7 ${exercise.tierLabel} (${exercise.index + 1}/${exercise.total})` : "";
419
497
  const lines = [
420
- `\u2501\u2501\u2501 M\xF3dulo ${wayfinding.moduleOrder}/${wayfinding.totalModules} \xB7 Li\xE7\xE3o ${wayfinding.lessonOrder}/${wayfinding.totalLessons} \u2501\u2501\u2501`,
498
+ `\u2501\u2501\u2501 M\xF3dulo ${wayfinding.moduleOrder}/${wayfinding.totalModules} \xB7 Li\xE7\xE3o ${wayfinding.lessonOrder}/${wayfinding.totalLessons}${tierSuffix} \u2501\u2501\u2501`,
421
499
  "",
422
500
  `\u{1F4D6} ${lesson.title}`,
423
501
  ` M\xF3dulo: ${lesson.moduleTitle} | Tipo: ${lesson.type}`,
424
502
  "",
425
- lesson.content,
503
+ exercise ? filterExerciseContent(lesson.content, exercise.index) : lesson.content,
426
504
  "",
427
505
  ` Curso: ${courseBar}`,
428
506
  ` M\xF3dulo: ${moduleBar}`
@@ -433,12 +511,21 @@ function formatLesson(data) {
433
511
  if (lesson.exerciseContent) {
434
512
  lines.push("", "\u2500\u2500\u2500 Exerc\xEDcio \u2500\u2500\u2500", lesson.exerciseContent);
435
513
  }
436
- lines.push(
437
- "",
438
- "\u2192 tostudy validate <resposta> para validar sua solu\xE7\xE3o",
439
- "\u2192 tostudy hint para obter uma dica",
440
- "\u2192 tostudy next para avan\xE7ar (ap\xF3s completar)"
441
- );
514
+ if (/^checkpoint/i.test(lesson.title.trim())) {
515
+ lines.push(
516
+ "",
517
+ "\u{1F4A1} Escreva suas respostas num arquivo (ex: checkpoint.md) e valide:",
518
+ " tostudy validate checkpoint.md",
519
+ "\u2192 tostudy next para avan\xE7ar"
520
+ );
521
+ } else {
522
+ lines.push(
523
+ "",
524
+ "\u2192 tostudy validate <resposta> para validar sua solu\xE7\xE3o",
525
+ "\u2192 tostudy hint para obter uma dica",
526
+ "\u2192 tostudy next para avan\xE7ar (ap\xF3s completar)"
527
+ );
528
+ }
442
529
  return lines.join("\n");
443
530
  }
444
531
  function formatValidation(data) {
@@ -510,9 +597,11 @@ function formatModuleStart(data) {
510
597
  );
511
598
  return lines.join("\n");
512
599
  }
600
+ var TIER_HEADERS;
513
601
  var init_formatter = __esm({
514
602
  "src/output/formatter.ts"() {
515
603
  "use strict";
604
+ TIER_HEADERS = [/^## Prática Guiada/im, /^## Prática Semi-?[Gg]uiada/im, /^## Desafio/im];
516
605
  }
517
606
  });
518
607
 
@@ -739,6 +828,65 @@ var init_mcp_setup = __esm({
739
828
  }
740
829
  });
741
830
 
831
+ // src/workspace/resolve.ts
832
+ import fs4 from "node:fs/promises";
833
+ import path4 from "node:path";
834
+ import os4 from "node:os";
835
+ function courseSlug(title) {
836
+ return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
837
+ }
838
+ async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
839
+ const slug = courseSlug(courseTitle);
840
+ const candidate = path4.join(basePath, slug);
841
+ try {
842
+ await fs4.access(path4.join(candidate, ".ana-config.json"));
843
+ return { found: true, workspacePath: candidate };
844
+ } catch {
845
+ return { found: false, workspacePath: null };
846
+ }
847
+ }
848
+ var DEFAULT_BASE;
849
+ var init_resolve = __esm({
850
+ "src/workspace/resolve.ts"() {
851
+ "use strict";
852
+ DEFAULT_BASE = path4.join(os4.homedir(), "study");
853
+ }
854
+ });
855
+
856
+ // src/onboarding/status.ts
857
+ import fs5 from "node:fs/promises";
858
+ import path5 from "node:path";
859
+ async function resolveStoredWorkspace(workspacePath) {
860
+ if (!workspacePath) return null;
861
+ try {
862
+ await fs5.access(path5.join(workspacePath, ".ana-config.json"));
863
+ return workspacePath;
864
+ } catch {
865
+ return null;
866
+ }
867
+ }
868
+ async function getCourseOnboardingStatus(activeCourse, configDir) {
869
+ const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
870
+ const initReady = Boolean(onboardingState?.initCompletedAt);
871
+ let workspacePath = await resolveStoredWorkspace(onboardingState?.workspacePath);
872
+ if (!workspacePath) {
873
+ const resolvedWorkspace = await resolveWorkspace(activeCourse.courseTitle);
874
+ workspacePath = resolvedWorkspace.workspacePath;
875
+ }
876
+ return {
877
+ initReady,
878
+ workspaceReady: Boolean(workspacePath),
879
+ workspacePath
880
+ };
881
+ }
882
+ var init_status = __esm({
883
+ "src/onboarding/status.ts"() {
884
+ "use strict";
885
+ init_session();
886
+ init_resolve();
887
+ }
888
+ });
889
+
742
890
  // src/commands/setup.ts
743
891
  import { Command as Command3 } from "commander";
744
892
  async function runSetup(opts, deps = defaultDeps) {
@@ -792,8 +940,34 @@ async function runSetup(opts, deps = defaultDeps) {
792
940
  await deps.runMcpSetup(session, token2);
793
941
  deps.log(" \u2713 MCP configurado\n");
794
942
  }
943
+ const activeCourse = await deps.getActiveCourse();
944
+ if (!activeCourse) {
945
+ deps.log(" Ambiente validado, mas ainda falta selecionar um curso.");
946
+ deps.log(" Pr\xF3ximos passos obrigat\xF3rios:");
947
+ deps.log(" \u2192 tostudy courses");
948
+ deps.log(" \u2192 tostudy select <n\xFAmero>");
949
+ deps.log(" \u2192 tostudy init");
950
+ deps.log(" \u2192 tostudy workspace setup\n");
951
+ return;
952
+ }
953
+ const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
954
+ if (!onboarding.initReady || !onboarding.workspaceReady) {
955
+ deps.log(
956
+ ` Ambiente validado para ${activeCourse.courseTitle}, mas o onboarding ainda n\xE3o terminou.`
957
+ );
958
+ deps.log(" Checklist obrigat\xF3rio antes do primeiro start:");
959
+ if (!onboarding.initReady) {
960
+ deps.log(" \u2192 tostudy init");
961
+ }
962
+ if (!onboarding.workspaceReady) {
963
+ deps.log(" \u2192 tostudy workspace setup");
964
+ }
965
+ deps.log(" \u2192 tostudy start ficar\xE1 dispon\xEDvel depois desses passos\n");
966
+ return;
967
+ }
795
968
  deps.log(" Setup completo!");
796
- deps.log(" \u2192 tostudy courses (ver cursos matriculados)");
969
+ deps.log(" \u2192 tostudy start (iniciar ou retomar o m\xF3dulo atual)");
970
+ deps.log(" \u2192 tostudy courses (ver cursos matriculados)");
797
971
  deps.log(" \u2192 tostudy doctor (diagn\xF3stico completo)\n");
798
972
  }
799
973
  async function runSetupMcpSubcommand() {
@@ -815,10 +989,13 @@ var init_setup = __esm({
815
989
  init_ide_detector();
816
990
  init_session();
817
991
  init_mcp_setup();
992
+ init_status();
818
993
  defaultDeps = {
819
994
  detectNode,
820
995
  detectIDEs,
821
996
  getSession,
997
+ getActiveCourse,
998
+ getCourseOnboardingStatus,
822
999
  exchangeCliSessionForMcpToken,
823
1000
  runMcpSetup,
824
1001
  log: (message = "") => {
@@ -843,20 +1020,20 @@ __export(update_checker_exports, {
843
1020
  fetchLatestVersion: () => fetchLatestVersion,
844
1021
  isNewerVersion: () => isNewerVersion
845
1022
  });
846
- import fs4 from "node:fs";
847
- import path4 from "node:path";
848
- import os4 from "node:os";
1023
+ import fs6 from "node:fs";
1024
+ import path6 from "node:path";
1025
+ import os5 from "node:os";
849
1026
  function getConfigDir2() {
850
1027
  if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
851
- return path4.join(process.env["XDG_CONFIG_HOME"], "tostudy");
1028
+ return path6.join(process.env["XDG_CONFIG_HOME"], "tostudy");
852
1029
  }
853
- return path4.join(os4.homedir(), ".tostudy");
1030
+ return path6.join(os5.homedir(), ".tostudy");
854
1031
  }
855
1032
  function readCache() {
856
1033
  try {
857
- const p = path4.join(getConfigDir2(), CACHE_FILE);
858
- if (!fs4.existsSync(p)) return null;
859
- return JSON.parse(fs4.readFileSync(p, "utf-8"));
1034
+ const p = path6.join(getConfigDir2(), CACHE_FILE);
1035
+ if (!fs6.existsSync(p)) return null;
1036
+ return JSON.parse(fs6.readFileSync(p, "utf-8"));
860
1037
  } catch {
861
1038
  return null;
862
1039
  }
@@ -864,8 +1041,8 @@ function readCache() {
864
1041
  function writeCache(cache) {
865
1042
  try {
866
1043
  const dir = getConfigDir2();
867
- fs4.mkdirSync(dir, { recursive: true });
868
- fs4.writeFileSync(path4.join(dir, CACHE_FILE), JSON.stringify(cache));
1044
+ fs6.mkdirSync(dir, { recursive: true });
1045
+ fs6.writeFileSync(path6.join(dir, CACHE_FILE), JSON.stringify(cache));
869
1046
  } catch {
870
1047
  }
871
1048
  }
@@ -1111,7 +1288,7 @@ var init_context = __esm({
1111
1288
  // ../../packages/logger/src/formatters/json.ts
1112
1289
  function formatJson(entry) {
1113
1290
  const { timestamp: timestamp2, level, message, service, environment, context, data, error: error49 } = entry;
1114
- const output2 = {
1291
+ const output3 = {
1115
1292
  timestamp: timestamp2,
1116
1293
  level,
1117
1294
  message,
@@ -1121,9 +1298,9 @@ function formatJson(entry) {
1121
1298
  ...data
1122
1299
  };
1123
1300
  if (error49) {
1124
- output2.error = error49;
1301
+ output3.error = error49;
1125
1302
  }
1126
- return JSON.stringify(output2);
1303
+ return JSON.stringify(output3);
1127
1304
  }
1128
1305
  var init_json = __esm({
1129
1306
  "../../packages/logger/src/formatters/json.ts"() {
@@ -1138,8 +1315,8 @@ var require_picocolors = __commonJS({
1138
1315
  var argv = p.argv || [];
1139
1316
  var env = p.env || {};
1140
1317
  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);
1141
- var formatter = (open, close, replace = open) => (input) => {
1142
- let string4 = "" + input, index2 = string4.indexOf(close, open.length);
1318
+ var formatter = (open, close, replace = open) => (input2) => {
1319
+ let string4 = "" + input2, index2 = string4.indexOf(close, open.length);
1143
1320
  return ~index2 ? open + replaceClose(string4, close, replace, index2) + close : open + string4 + close;
1144
1321
  };
1145
1322
  var replaceClose = (string4, close, replace, index2) => {
@@ -1230,7 +1407,7 @@ function formatPretty(entry) {
1230
1407
  const lvl = formatLevel(level);
1231
1408
  const mod = context.module ? import_picocolors.default.dim(`[${context.module}]`) + " " : "";
1232
1409
  const svc = import_picocolors.default.dim(`[${service}]`) + " ";
1233
- let output2 = `${import_picocolors.default.dim(time4)} ${lvl} ${svc}${mod}${message}`;
1410
+ let output3 = `${import_picocolors.default.dim(time4)} ${lvl} ${svc}${mod}${message}`;
1234
1411
  const traceInfo = {};
1235
1412
  if (context.traceId) traceInfo.traceId = context.traceId;
1236
1413
  if (context.requestId && context.requestId !== context.traceId) {
@@ -1240,22 +1417,22 @@ function formatPretty(entry) {
1240
1417
  if (context.duration !== void 0) traceInfo.duration = `${context.duration}ms`;
1241
1418
  const allData = { ...traceInfo, ...data };
1242
1419
  if (Object.keys(allData).length > 0) {
1243
- output2 += formatData(allData);
1420
+ output3 += formatData(allData);
1244
1421
  }
1245
1422
  if (error49) {
1246
- output2 += "\n" + import_picocolors.default.red(` ${error49.name}: ${error49.message}`);
1423
+ output3 += "\n" + import_picocolors.default.red(` ${error49.name}: ${error49.message}`);
1247
1424
  if (error49.code) {
1248
- output2 += import_picocolors.default.dim(` (${error49.code})`);
1425
+ output3 += import_picocolors.default.dim(` (${error49.code})`);
1249
1426
  }
1250
1427
  if (error49.stack) {
1251
1428
  const stackLines = error49.stack.split("\n").slice(1, 4);
1252
- output2 += "\n" + stackLines.map((l) => import_picocolors.default.dim(` ${l.trim()}`)).join("\n");
1429
+ output3 += "\n" + stackLines.map((l) => import_picocolors.default.dim(` ${l.trim()}`)).join("\n");
1253
1430
  }
1254
1431
  if (error49.cause) {
1255
- output2 += "\n" + import_picocolors.default.dim(` Caused by: ${error49.cause.name}: ${error49.cause.message}`);
1432
+ output3 += "\n" + import_picocolors.default.dim(` Caused by: ${error49.cause.name}: ${error49.cause.message}`);
1256
1433
  }
1257
1434
  }
1258
- return output2;
1435
+ return output3;
1259
1436
  }
1260
1437
  var import_picocolors, LEVEL_COLORS, LEVEL_WIDTH;
1261
1438
  var init_pretty = __esm({
@@ -1343,6 +1520,22 @@ function isSensitiveKey(key) {
1343
1520
  return patterns.some((p) => lowerKey.includes(p));
1344
1521
  });
1345
1522
  }
1523
+ function serializeError(error49) {
1524
+ const serialized = {
1525
+ name: error49.name,
1526
+ message: error49.message
1527
+ };
1528
+ if (error49.stack) {
1529
+ serialized.stack = error49.stack;
1530
+ }
1531
+ if ("code" in error49 && typeof error49.code === "string") {
1532
+ serialized.code = error49.code;
1533
+ }
1534
+ if (error49.cause instanceof Error) {
1535
+ serialized.cause = serializeError(error49.cause);
1536
+ }
1537
+ return serialized;
1538
+ }
1346
1539
  function object(obj, additionalSensitiveKeys) {
1347
1540
  if (!obj || typeof obj !== "object") return obj;
1348
1541
  const sanitized = { ...obj };
@@ -1354,6 +1547,10 @@ function object(obj, additionalSensitiveKeys) {
1354
1547
  sanitized[key] = "[REDACTED]";
1355
1548
  continue;
1356
1549
  }
1550
+ if (value instanceof Error) {
1551
+ sanitized[key] = serializeError(value);
1552
+ continue;
1553
+ }
1357
1554
  if (value && typeof value === "object" && !Array.isArray(value)) {
1358
1555
  sanitized[key] = object(
1359
1556
  value,
@@ -1363,6 +1560,7 @@ function object(obj, additionalSensitiveKeys) {
1363
1560
  }
1364
1561
  if (Array.isArray(value)) {
1365
1562
  sanitized[key] = value.map((item) => {
1563
+ if (item instanceof Error) return serializeError(item);
1366
1564
  if (item && typeof item === "object" && !Array.isArray(item)) {
1367
1565
  return object(item, additionalSensitiveKeys);
1368
1566
  }
@@ -1373,13 +1571,7 @@ function object(obj, additionalSensitiveKeys) {
1373
1571
  return sanitized;
1374
1572
  }
1375
1573
  function headers(hdrs) {
1376
- const sensitiveHeaders = [
1377
- "authorization",
1378
- "x-api-key",
1379
- "x-auth-token",
1380
- "cookie",
1381
- "set-cookie"
1382
- ];
1574
+ const sensitiveHeaders = ["authorization", "x-api-key", "x-auth-token", "cookie", "set-cookie"];
1383
1575
  const sanitized = { ...hdrs };
1384
1576
  for (const key of Object.keys(sanitized)) {
1385
1577
  if (sensitiveHeaders.some((h) => key.toLowerCase() === h)) {
@@ -1957,9 +2149,9 @@ var init_src = __esm({
1957
2149
  });
1958
2150
 
1959
2151
  // ../../packages/tostudy-core/src/courses/list-courses.ts
1960
- async function listCourses(input, deps) {
1961
- deps.logger.info("tostudy-core: listCourses", { userId: input.userId });
1962
- return deps.data.courses.list(input.userId);
2152
+ async function listCourses(input2, deps) {
2153
+ deps.logger.info("tostudy-core: listCourses", { userId: input2.userId });
2154
+ return deps.data.courses.list(input2.userId);
1963
2155
  }
1964
2156
  var init_list_courses = __esm({
1965
2157
  "../../packages/tostudy-core/src/courses/list-courses.ts"() {
@@ -1968,9 +2160,9 @@ var init_list_courses = __esm({
1968
2160
  });
1969
2161
 
1970
2162
  // ../../packages/tostudy-core/src/courses/select-course.ts
1971
- async function selectCourse(input, deps) {
1972
- deps.logger.info("tostudy-core: selectCourse", input);
1973
- return deps.data.courses.select(input.userId, input.courseId);
2163
+ async function selectCourse(input2, deps) {
2164
+ deps.logger.info("tostudy-core: selectCourse", input2);
2165
+ return deps.data.courses.select(input2.userId, input2.courseId);
1974
2166
  }
1975
2167
  var init_select_course = __esm({
1976
2168
  "../../packages/tostudy-core/src/courses/select-course.ts"() {
@@ -1979,9 +2171,9 @@ var init_select_course = __esm({
1979
2171
  });
1980
2172
 
1981
2173
  // ../../packages/tostudy-core/src/courses/get-progress.ts
1982
- async function getProgress(input, deps) {
1983
- deps.logger.info("tostudy-core: getProgress", input);
1984
- return deps.data.courses.getProgress(input.enrollmentId);
2174
+ async function getProgress(input2, deps) {
2175
+ deps.logger.info("tostudy-core: getProgress", input2);
2176
+ return deps.data.courses.getProgress(input2.enrollmentId);
1985
2177
  }
1986
2178
  var init_get_progress = __esm({
1987
2179
  "../../packages/tostudy-core/src/courses/get-progress.ts"() {
@@ -2176,11 +2368,14 @@ var init_select = __esm({
2176
2368
  enrollmentId = found.enrollmentId;
2177
2369
  }
2178
2370
  }
2371
+ const matched = courses3.find((c) => c.courseId === courseId);
2179
2372
  const detail = await selectCourse({ userId: session.userId, courseId }, deps);
2180
2373
  await setActiveCourse({
2181
2374
  courseId: detail.courseId,
2182
2375
  courseTitle: detail.courseTitle,
2183
- enrollmentId
2376
+ enrollmentId,
2377
+ courseTags: matched?.tags,
2378
+ courseLevel: matched?.level
2184
2379
  });
2185
2380
  if (opts.json) {
2186
2381
  output({ ...detail, enrollmentId }, { json: true });
@@ -2240,9 +2435,9 @@ var init_progress = __esm({
2240
2435
  });
2241
2436
 
2242
2437
  // ../../packages/tostudy-core/src/lessons/next-lesson.ts
2243
- async function nextLesson(input, deps) {
2244
- deps.logger.info("tostudy-core: nextLesson", { enrollmentId: input.enrollmentId });
2245
- return deps.data.lessons.next(input.enrollmentId, input.userConfirmation);
2438
+ async function nextLesson(input2, deps) {
2439
+ deps.logger.info("tostudy-core: nextLesson", { enrollmentId: input2.enrollmentId });
2440
+ return deps.data.lessons.next(input2.enrollmentId, input2.userConfirmation);
2246
2441
  }
2247
2442
  var init_next_lesson = __esm({
2248
2443
  "../../packages/tostudy-core/src/lessons/next-lesson.ts"() {
@@ -2251,9 +2446,9 @@ var init_next_lesson = __esm({
2251
2446
  });
2252
2447
 
2253
2448
  // ../../packages/tostudy-core/src/lessons/get-content.ts
2254
- async function getContent(input, deps) {
2255
- deps.logger.info("tostudy-core: getContent", { lessonId: input.lessonId });
2256
- return deps.data.lessons.getContent(input.lessonId, input.enrollmentId);
2449
+ async function getContent(input2, deps) {
2450
+ deps.logger.info("tostudy-core: getContent", { lessonId: input2.lessonId });
2451
+ return deps.data.lessons.getContent(input2.lessonId, input2.enrollmentId);
2257
2452
  }
2258
2453
  var init_get_content = __esm({
2259
2454
  "../../packages/tostudy-core/src/lessons/get-content.ts"() {
@@ -2262,9 +2457,9 @@ var init_get_content = __esm({
2262
2457
  });
2263
2458
 
2264
2459
  // ../../packages/tostudy-core/src/lessons/get-hint.ts
2265
- async function getHint(input, deps) {
2266
- deps.logger.info("tostudy-core: getHint", { userId: input.userId });
2267
- return deps.data.lessons.getHint(input.userId, input.enrollmentId);
2460
+ async function getHint(input2, deps) {
2461
+ deps.logger.info("tostudy-core: getHint", { userId: input2.userId });
2462
+ return deps.data.lessons.getHint(input2.userId, input2.enrollmentId);
2268
2463
  }
2269
2464
  var init_get_hint = __esm({
2270
2465
  "../../packages/tostudy-core/src/lessons/get-hint.ts"() {
@@ -2273,9 +2468,9 @@ var init_get_hint = __esm({
2273
2468
  });
2274
2469
 
2275
2470
  // ../../packages/tostudy-core/src/lessons/start-module.ts
2276
- async function startModule(input, deps) {
2277
- deps.logger.info("tostudy-core: startModule", { enrollmentId: input.enrollmentId });
2278
- return deps.data.lessons.startModule(input.enrollmentId);
2471
+ async function startModule(input2, deps) {
2472
+ deps.logger.info("tostudy-core: startModule", { enrollmentId: input2.enrollmentId });
2473
+ return deps.data.lessons.startModule(input2.enrollmentId);
2279
2474
  }
2280
2475
  var init_start_module = __esm({
2281
2476
  "../../packages/tostudy-core/src/lessons/start-module.ts"() {
@@ -2284,9 +2479,9 @@ var init_start_module = __esm({
2284
2479
  });
2285
2480
 
2286
2481
  // ../../packages/tostudy-core/src/lessons/start-next-module.ts
2287
- async function startNextModule(input, deps) {
2288
- deps.logger.info("tostudy-core: startNextModule", { enrollmentId: input.enrollmentId });
2289
- return deps.data.lessons.startNextModule(input.enrollmentId);
2482
+ async function startNextModule(input2, deps) {
2483
+ deps.logger.info("tostudy-core: startNextModule", { enrollmentId: input2.enrollmentId });
2484
+ return deps.data.lessons.startNextModule(input2.enrollmentId);
2290
2485
  }
2291
2486
  var init_start_next_module = __esm({
2292
2487
  "../../packages/tostudy-core/src/lessons/start-next-module.ts"() {
@@ -2923,8 +3118,8 @@ var init_common = __esm({
2923
3118
  function isPgEnum(obj) {
2924
3119
  return !!obj && typeof obj === "function" && isPgEnumSym in obj && obj[isPgEnumSym] === true;
2925
3120
  }
2926
- function pgEnum(enumName, input) {
2927
- return Array.isArray(input) ? pgEnumWithSchema(enumName, [...input], void 0) : pgEnumObjectWithSchema(enumName, input, void 0);
3121
+ function pgEnum(enumName, input2) {
3122
+ return Array.isArray(input2) ? pgEnumWithSchema(enumName, [...input2], void 0) : pgEnumObjectWithSchema(enumName, input2, void 0);
2928
3123
  }
2929
3124
  function pgEnumWithSchema(enumName, values2, schema) {
2930
3125
  const enumInstance = Object.assign(
@@ -3785,7 +3980,7 @@ var init_query_promise = __esm({
3785
3980
  function mapResultRow(columns, row, joinsNotNullableMap) {
3786
3981
  const nullifyMap = {};
3787
3982
  const result = columns.reduce(
3788
- (result2, { path: path12, field }, columnIndex) => {
3983
+ (result2, { path: path15, field }, columnIndex) => {
3789
3984
  let decoder;
3790
3985
  if (is(field, Column)) {
3791
3986
  decoder = field;
@@ -3797,8 +3992,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
3797
3992
  decoder = field.sql.decoder;
3798
3993
  }
3799
3994
  let node = result2;
3800
- for (const [pathChunkIndex, pathChunk] of path12.entries()) {
3801
- if (pathChunkIndex < path12.length - 1) {
3995
+ for (const [pathChunkIndex, pathChunk] of path15.entries()) {
3996
+ if (pathChunkIndex < path15.length - 1) {
3802
3997
  if (!(pathChunk in node)) {
3803
3998
  node[pathChunk] = {};
3804
3999
  }
@@ -3806,8 +4001,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
3806
4001
  } else {
3807
4002
  const rawValue = row[columnIndex];
3808
4003
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
3809
- if (joinsNotNullableMap && is(field, Column) && path12.length === 2) {
3810
- const objectName = path12[0];
4004
+ if (joinsNotNullableMap && is(field, Column) && path15.length === 2) {
4005
+ const objectName = path15[0];
3811
4006
  if (!(objectName in nullifyMap)) {
3812
4007
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
3813
4008
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -7754,13 +7949,13 @@ function Subscribe(postgres2, options) {
7754
7949
  }
7755
7950
  }
7756
7951
  function handle(a, b2) {
7757
- const path12 = b2.relation.schema + "." + b2.relation.table;
7952
+ const path15 = b2.relation.schema + "." + b2.relation.table;
7758
7953
  call("*", a, b2);
7759
- call("*:" + path12, a, b2);
7760
- b2.relation.keys.length && call("*:" + path12 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
7954
+ call("*:" + path15, a, b2);
7955
+ b2.relation.keys.length && call("*:" + path15 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
7761
7956
  call(b2.command, a, b2);
7762
- call(b2.command + ":" + path12, a, b2);
7763
- b2.relation.keys.length && call(b2.command + ":" + path12 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
7957
+ call(b2.command + ":" + path15, a, b2);
7958
+ b2.relation.keys.length && call(b2.command + ":" + path15 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
7764
7959
  }
7765
7960
  function pong() {
7766
7961
  const x2 = Buffer.alloc(34);
@@ -7873,8 +8068,8 @@ function parseEvent(x) {
7873
8068
  const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
7874
8069
  if (!xs)
7875
8070
  throw new Error("Malformed subscribe pattern: " + x);
7876
- const [, command, path12, key] = xs;
7877
- return (command || "*") + (path12 ? ":" + (path12.indexOf(".") === -1 ? "public." + path12 : path12) : "") + (key ? "=" + key : "");
8071
+ const [, command, path15, key] = xs;
8072
+ return (command || "*") + (path15 ? ":" + (path15.indexOf(".") === -1 ? "public." + path15 : path15) : "") + (key ? "=" + key : "");
7878
8073
  }
7879
8074
  var noop2;
7880
8075
  var init_subscribe = __esm({
@@ -7955,8 +8150,8 @@ var init_large = __esm({
7955
8150
  });
7956
8151
 
7957
8152
  // ../../node_modules/postgres/src/index.js
7958
- import os5 from "os";
7959
- import fs5 from "fs";
8153
+ import os6 from "os";
8154
+ import fs7 from "fs";
7960
8155
  function Postgres(a, b2) {
7961
8156
  const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
7962
8157
  let ending = false;
@@ -8012,10 +8207,10 @@ function Postgres(a, b2) {
8012
8207
  });
8013
8208
  return query;
8014
8209
  }
8015
- function file2(path12, args = [], options2 = {}) {
8210
+ function file2(path15, args = [], options2 = {}) {
8016
8211
  arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
8017
8212
  const query = new Query([], args, (query2) => {
8018
- fs5.readFile(path12, "utf8", (err, string4) => {
8213
+ fs7.readFile(path15, "utf8", (err, string4) => {
8019
8214
  if (err)
8020
8215
  return query2.reject(err);
8021
8216
  query2.strings = [string4];
@@ -8333,7 +8528,7 @@ function parseUrl(url2) {
8333
8528
  }
8334
8529
  function osUsername() {
8335
8530
  try {
8336
- return os5.userInfo().username;
8531
+ return os6.userInfo().username;
8337
8532
  } catch (_) {
8338
8533
  return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
8339
8534
  }
@@ -8685,19 +8880,19 @@ var init_view_common2 = __esm({
8685
8880
  });
8686
8881
 
8687
8882
  // ../../node_modules/drizzle-orm/casing.js
8688
- function toSnakeCase(input) {
8689
- const words = input.replace(/['\u2019]/g, "").match(/[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g) ?? [];
8883
+ function toSnakeCase(input2) {
8884
+ const words = input2.replace(/['\u2019]/g, "").match(/[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g) ?? [];
8690
8885
  return words.map((word) => word.toLowerCase()).join("_");
8691
8886
  }
8692
- function toCamelCase(input) {
8693
- const words = input.replace(/['\u2019]/g, "").match(/[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g) ?? [];
8887
+ function toCamelCase(input2) {
8888
+ const words = input2.replace(/['\u2019]/g, "").match(/[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g) ?? [];
8694
8889
  return words.reduce((acc, word, i) => {
8695
8890
  const formattedWord = i === 0 ? word.toLowerCase() : `${word[0].toUpperCase()}${word.slice(1)}`;
8696
8891
  return acc + formattedWord;
8697
8892
  }, "");
8698
8893
  }
8699
- function noopCase(input) {
8700
- return input;
8894
+ function noopCase(input2) {
8895
+ return input2;
8701
8896
  }
8702
8897
  var CasingCache;
8703
8898
  var init_casing = __esm({
@@ -12350,12 +12545,12 @@ var init_schema = __esm({
12350
12545
  materializedView = (name, columns) => {
12351
12546
  return pgMaterializedViewWithSchema(name, columns, this.schemaName);
12352
12547
  };
12353
- enum(enumName, input) {
12354
- return Array.isArray(input) ? pgEnumWithSchema(
12548
+ enum(enumName, input2) {
12549
+ return Array.isArray(input2) ? pgEnumWithSchema(
12355
12550
  enumName,
12356
- [...input],
12551
+ [...input2],
12357
12552
  this.schemaName
12358
- ) : pgEnumObjectWithSchema(enumName, input, this.schemaName);
12553
+ ) : pgEnumObjectWithSchema(enumName, input2, this.schemaName);
12359
12554
  }
12360
12555
  sequence = (name, options) => {
12361
12556
  return pgSequenceWithSchema(name, options, this.schemaName);
@@ -13006,8 +13201,8 @@ function cached(getter) {
13006
13201
  }
13007
13202
  };
13008
13203
  }
13009
- function nullish(input) {
13010
- return input === null || input === void 0;
13204
+ function nullish(input2) {
13205
+ return input2 === null || input2 === void 0;
13011
13206
  }
13012
13207
  function cleanRegex(source) {
13013
13208
  const start = source.startsWith("^") ? 1 : 0;
@@ -13073,10 +13268,10 @@ function mergeDefs(...defs) {
13073
13268
  function cloneDef(schema) {
13074
13269
  return mergeDefs(schema._zod.def);
13075
13270
  }
13076
- function getElementAtPath(obj, path12) {
13077
- if (!path12)
13271
+ function getElementAtPath(obj, path15) {
13272
+ if (!path15)
13078
13273
  return obj;
13079
- return path12.reduce((acc, key) => acc?.[key], obj);
13274
+ return path15.reduce((acc, key) => acc?.[key], obj);
13080
13275
  }
13081
13276
  function promiseAllObject(promisesObj) {
13082
13277
  const keys = Object.keys(promisesObj);
@@ -13100,8 +13295,8 @@ function randomString(length = 10) {
13100
13295
  function esc(str) {
13101
13296
  return JSON.stringify(str);
13102
13297
  }
13103
- function slugify(input) {
13104
- return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
13298
+ function slugify(input2) {
13299
+ return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
13105
13300
  }
13106
13301
  function isObject(data) {
13107
13302
  return typeof data === "object" && data !== null && !Array.isArray(data);
@@ -13388,11 +13583,11 @@ function aborted(x, startIndex = 0) {
13388
13583
  }
13389
13584
  return false;
13390
13585
  }
13391
- function prefixIssues(path12, issues) {
13586
+ function prefixIssues(path15, issues) {
13392
13587
  return issues.map((iss) => {
13393
13588
  var _a2;
13394
13589
  (_a2 = iss).path ?? (_a2.path = []);
13395
- iss.path.unshift(path12);
13590
+ iss.path.unshift(path15);
13396
13591
  return iss;
13397
13592
  });
13398
13593
  }
@@ -13412,19 +13607,19 @@ function finalizeIssue(iss, ctx, config2) {
13412
13607
  }
13413
13608
  return full;
13414
13609
  }
13415
- function getSizableOrigin(input) {
13416
- if (input instanceof Set)
13610
+ function getSizableOrigin(input2) {
13611
+ if (input2 instanceof Set)
13417
13612
  return "set";
13418
- if (input instanceof Map)
13613
+ if (input2 instanceof Map)
13419
13614
  return "map";
13420
- if (input instanceof File)
13615
+ if (input2 instanceof File)
13421
13616
  return "file";
13422
13617
  return "unknown";
13423
13618
  }
13424
- function getLengthableOrigin(input) {
13425
- if (Array.isArray(input))
13619
+ function getLengthableOrigin(input2) {
13620
+ if (Array.isArray(input2))
13426
13621
  return "array";
13427
- if (typeof input === "string")
13622
+ if (typeof input2 === "string")
13428
13623
  return "string";
13429
13624
  return "unknown";
13430
13625
  }
@@ -13450,12 +13645,12 @@ function parsedType(data) {
13450
13645
  return t;
13451
13646
  }
13452
13647
  function issue(...args) {
13453
- const [iss, input, inst] = args;
13648
+ const [iss, input2, inst] = args;
13454
13649
  if (typeof iss === "string") {
13455
13650
  return {
13456
13651
  message: iss,
13457
13652
  code: "custom",
13458
- input,
13653
+ input: input2,
13459
13654
  inst
13460
13655
  };
13461
13656
  }
@@ -13634,7 +13829,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
13634
13829
  }
13635
13830
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
13636
13831
  const result = { errors: [] };
13637
- const processError = (error50, path12 = []) => {
13832
+ const processError = (error50, path15 = []) => {
13638
13833
  var _a2, _b;
13639
13834
  for (const issue2 of error50.issues) {
13640
13835
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -13644,7 +13839,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
13644
13839
  } else if (issue2.code === "invalid_element") {
13645
13840
  processError({ issues: issue2.issues }, issue2.path);
13646
13841
  } else {
13647
- const fullpath = [...path12, ...issue2.path];
13842
+ const fullpath = [...path15, ...issue2.path];
13648
13843
  if (fullpath.length === 0) {
13649
13844
  result.errors.push(mapper(issue2));
13650
13845
  continue;
@@ -13676,8 +13871,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
13676
13871
  }
13677
13872
  function toDotPath(_path) {
13678
13873
  const segs = [];
13679
- const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
13680
- for (const seg of path12) {
13874
+ const path15 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
13875
+ for (const seg of path15) {
13681
13876
  if (typeof seg === "number")
13682
13877
  segs.push(`[${seg}]`);
13683
13878
  else if (typeof seg === "symbol")
@@ -14103,23 +14298,23 @@ var init_checks2 = __esm({
14103
14298
  bag.pattern = integer2;
14104
14299
  });
14105
14300
  inst._zod.check = (payload) => {
14106
- const input = payload.value;
14301
+ const input2 = payload.value;
14107
14302
  if (isInt) {
14108
- if (!Number.isInteger(input)) {
14303
+ if (!Number.isInteger(input2)) {
14109
14304
  payload.issues.push({
14110
14305
  expected: origin,
14111
14306
  format: def.format,
14112
14307
  code: "invalid_type",
14113
14308
  continue: false,
14114
- input,
14309
+ input: input2,
14115
14310
  inst
14116
14311
  });
14117
14312
  return;
14118
14313
  }
14119
- if (!Number.isSafeInteger(input)) {
14120
- if (input > 0) {
14314
+ if (!Number.isSafeInteger(input2)) {
14315
+ if (input2 > 0) {
14121
14316
  payload.issues.push({
14122
- input,
14317
+ input: input2,
14123
14318
  code: "too_big",
14124
14319
  maximum: Number.MAX_SAFE_INTEGER,
14125
14320
  note: "Integers must be within the safe integer range.",
@@ -14130,7 +14325,7 @@ var init_checks2 = __esm({
14130
14325
  });
14131
14326
  } else {
14132
14327
  payload.issues.push({
14133
- input,
14328
+ input: input2,
14134
14329
  code: "too_small",
14135
14330
  minimum: Number.MIN_SAFE_INTEGER,
14136
14331
  note: "Integers must be within the safe integer range.",
@@ -14143,10 +14338,10 @@ var init_checks2 = __esm({
14143
14338
  return;
14144
14339
  }
14145
14340
  }
14146
- if (input < minimum) {
14341
+ if (input2 < minimum) {
14147
14342
  payload.issues.push({
14148
14343
  origin: "number",
14149
- input,
14344
+ input: input2,
14150
14345
  code: "too_small",
14151
14346
  minimum,
14152
14347
  inclusive: true,
@@ -14154,10 +14349,10 @@ var init_checks2 = __esm({
14154
14349
  continue: !def.abort
14155
14350
  });
14156
14351
  }
14157
- if (input > maximum) {
14352
+ if (input2 > maximum) {
14158
14353
  payload.issues.push({
14159
14354
  origin: "number",
14160
- input,
14355
+ input: input2,
14161
14356
  code: "too_big",
14162
14357
  maximum,
14163
14358
  inclusive: true,
@@ -14177,11 +14372,11 @@ var init_checks2 = __esm({
14177
14372
  bag.maximum = maximum;
14178
14373
  });
14179
14374
  inst._zod.check = (payload) => {
14180
- const input = payload.value;
14181
- if (input < minimum) {
14375
+ const input2 = payload.value;
14376
+ if (input2 < minimum) {
14182
14377
  payload.issues.push({
14183
14378
  origin: "bigint",
14184
- input,
14379
+ input: input2,
14185
14380
  code: "too_small",
14186
14381
  minimum,
14187
14382
  inclusive: true,
@@ -14189,10 +14384,10 @@ var init_checks2 = __esm({
14189
14384
  continue: !def.abort
14190
14385
  });
14191
14386
  }
14192
- if (input > maximum) {
14387
+ if (input2 > maximum) {
14193
14388
  payload.issues.push({
14194
14389
  origin: "bigint",
14195
- input,
14390
+ input: input2,
14196
14391
  code: "too_big",
14197
14392
  maximum,
14198
14393
  inclusive: true,
@@ -14215,16 +14410,16 @@ var init_checks2 = __esm({
14215
14410
  inst2._zod.bag.maximum = def.maximum;
14216
14411
  });
14217
14412
  inst._zod.check = (payload) => {
14218
- const input = payload.value;
14219
- const size2 = input.size;
14413
+ const input2 = payload.value;
14414
+ const size2 = input2.size;
14220
14415
  if (size2 <= def.maximum)
14221
14416
  return;
14222
14417
  payload.issues.push({
14223
- origin: getSizableOrigin(input),
14418
+ origin: getSizableOrigin(input2),
14224
14419
  code: "too_big",
14225
14420
  maximum: def.maximum,
14226
14421
  inclusive: true,
14227
- input,
14422
+ input: input2,
14228
14423
  inst,
14229
14424
  continue: !def.abort
14230
14425
  });
@@ -14243,16 +14438,16 @@ var init_checks2 = __esm({
14243
14438
  inst2._zod.bag.minimum = def.minimum;
14244
14439
  });
14245
14440
  inst._zod.check = (payload) => {
14246
- const input = payload.value;
14247
- const size2 = input.size;
14441
+ const input2 = payload.value;
14442
+ const size2 = input2.size;
14248
14443
  if (size2 >= def.minimum)
14249
14444
  return;
14250
14445
  payload.issues.push({
14251
- origin: getSizableOrigin(input),
14446
+ origin: getSizableOrigin(input2),
14252
14447
  code: "too_small",
14253
14448
  minimum: def.minimum,
14254
14449
  inclusive: true,
14255
- input,
14450
+ input: input2,
14256
14451
  inst,
14257
14452
  continue: !def.abort
14258
14453
  });
@@ -14272,13 +14467,13 @@ var init_checks2 = __esm({
14272
14467
  bag.size = def.size;
14273
14468
  });
14274
14469
  inst._zod.check = (payload) => {
14275
- const input = payload.value;
14276
- const size2 = input.size;
14470
+ const input2 = payload.value;
14471
+ const size2 = input2.size;
14277
14472
  if (size2 === def.size)
14278
14473
  return;
14279
14474
  const tooBig = size2 > def.size;
14280
14475
  payload.issues.push({
14281
- origin: getSizableOrigin(input),
14476
+ origin: getSizableOrigin(input2),
14282
14477
  ...tooBig ? { code: "too_big", maximum: def.size } : { code: "too_small", minimum: def.size },
14283
14478
  inclusive: true,
14284
14479
  exact: true,
@@ -14301,17 +14496,17 @@ var init_checks2 = __esm({
14301
14496
  inst2._zod.bag.maximum = def.maximum;
14302
14497
  });
14303
14498
  inst._zod.check = (payload) => {
14304
- const input = payload.value;
14305
- const length = input.length;
14499
+ const input2 = payload.value;
14500
+ const length = input2.length;
14306
14501
  if (length <= def.maximum)
14307
14502
  return;
14308
- const origin = getLengthableOrigin(input);
14503
+ const origin = getLengthableOrigin(input2);
14309
14504
  payload.issues.push({
14310
14505
  origin,
14311
14506
  code: "too_big",
14312
14507
  maximum: def.maximum,
14313
14508
  inclusive: true,
14314
- input,
14509
+ input: input2,
14315
14510
  inst,
14316
14511
  continue: !def.abort
14317
14512
  });
@@ -14330,17 +14525,17 @@ var init_checks2 = __esm({
14330
14525
  inst2._zod.bag.minimum = def.minimum;
14331
14526
  });
14332
14527
  inst._zod.check = (payload) => {
14333
- const input = payload.value;
14334
- const length = input.length;
14528
+ const input2 = payload.value;
14529
+ const length = input2.length;
14335
14530
  if (length >= def.minimum)
14336
14531
  return;
14337
- const origin = getLengthableOrigin(input);
14532
+ const origin = getLengthableOrigin(input2);
14338
14533
  payload.issues.push({
14339
14534
  origin,
14340
14535
  code: "too_small",
14341
14536
  minimum: def.minimum,
14342
14537
  inclusive: true,
14343
- input,
14538
+ input: input2,
14344
14539
  inst,
14345
14540
  continue: !def.abort
14346
14541
  });
@@ -14360,11 +14555,11 @@ var init_checks2 = __esm({
14360
14555
  bag.length = def.length;
14361
14556
  });
14362
14557
  inst._zod.check = (payload) => {
14363
- const input = payload.value;
14364
- const length = input.length;
14558
+ const input2 = payload.value;
14559
+ const length = input2.length;
14365
14560
  if (length === def.length)
14366
14561
  return;
14367
- const origin = getLengthableOrigin(input);
14562
+ const origin = getLengthableOrigin(input2);
14368
14563
  const tooBig = length > def.length;
14369
14564
  payload.issues.push({
14370
14565
  origin,
@@ -14642,15 +14837,15 @@ function handleArrayResult(result, final, index2) {
14642
14837
  }
14643
14838
  final.value[index2] = result.value;
14644
14839
  }
14645
- function handlePropertyResult(result, final, key, input, isOptionalOut) {
14840
+ function handlePropertyResult(result, final, key, input2, isOptionalOut) {
14646
14841
  if (result.issues.length) {
14647
- if (isOptionalOut && !(key in input)) {
14842
+ if (isOptionalOut && !(key in input2)) {
14648
14843
  return;
14649
14844
  }
14650
14845
  final.issues.push(...prefixIssues(key, result.issues));
14651
14846
  }
14652
14847
  if (result.value === void 0) {
14653
- if (key in input) {
14848
+ if (key in input2) {
14654
14849
  final.value[key] = void 0;
14655
14850
  }
14656
14851
  } else {
@@ -14673,31 +14868,31 @@ function normalizeDef(def) {
14673
14868
  optionalKeys: new Set(okeys)
14674
14869
  };
14675
14870
  }
14676
- function handleCatchall(proms, input, payload, ctx, def, inst) {
14871
+ function handleCatchall(proms, input2, payload, ctx, def, inst) {
14677
14872
  const unrecognized = [];
14678
14873
  const keySet = def.keySet;
14679
14874
  const _catchall = def.catchall._zod;
14680
14875
  const t = _catchall.def.type;
14681
14876
  const isOptionalOut = _catchall.optout === "optional";
14682
- for (const key in input) {
14877
+ for (const key in input2) {
14683
14878
  if (keySet.has(key))
14684
14879
  continue;
14685
14880
  if (t === "never") {
14686
14881
  unrecognized.push(key);
14687
14882
  continue;
14688
14883
  }
14689
- const r = _catchall.run({ value: input[key], issues: [] }, ctx);
14884
+ const r = _catchall.run({ value: input2[key], issues: [] }, ctx);
14690
14885
  if (r instanceof Promise) {
14691
- proms.push(r.then((r2) => handlePropertyResult(r2, payload, key, input, isOptionalOut)));
14886
+ proms.push(r.then((r2) => handlePropertyResult(r2, payload, key, input2, isOptionalOut)));
14692
14887
  } else {
14693
- handlePropertyResult(r, payload, key, input, isOptionalOut);
14888
+ handlePropertyResult(r, payload, key, input2, isOptionalOut);
14694
14889
  }
14695
14890
  }
14696
14891
  if (unrecognized.length) {
14697
14892
  payload.issues.push({
14698
14893
  code: "unrecognized_keys",
14699
14894
  keys: unrecognized,
14700
- input,
14895
+ input: input2,
14701
14896
  inst
14702
14897
  });
14703
14898
  }
@@ -14840,7 +15035,7 @@ function handleTupleResult(result, final, index2) {
14840
15035
  }
14841
15036
  final.value[index2] = result.value;
14842
15037
  }
14843
- function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
15038
+ function handleMapResult(keyResult, valueResult, final, key, input2, inst, ctx) {
14844
15039
  if (keyResult.issues.length) {
14845
15040
  if (propertyKeyTypes.has(typeof key)) {
14846
15041
  final.issues.push(...prefixIssues(key, keyResult.issues));
@@ -14848,7 +15043,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
14848
15043
  final.issues.push({
14849
15044
  code: "invalid_key",
14850
15045
  origin: "map",
14851
- input,
15046
+ input: input2,
14852
15047
  inst,
14853
15048
  issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
14854
15049
  });
@@ -14861,7 +15056,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
14861
15056
  final.issues.push({
14862
15057
  origin: "map",
14863
15058
  code: "invalid_element",
14864
- input,
15059
+ input: input2,
14865
15060
  inst,
14866
15061
  key,
14867
15062
  issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
@@ -14876,8 +15071,8 @@ function handleSetResult(result, final) {
14876
15071
  }
14877
15072
  final.value.add(result.value);
14878
15073
  }
14879
- function handleOptionalResult(result, input) {
14880
- if (result.issues.length && input === void 0) {
15074
+ function handleOptionalResult(result, input2) {
15075
+ if (result.issues.length && input2 === void 0) {
14881
15076
  return { issues: [], value: void 0 };
14882
15077
  }
14883
15078
  return result;
@@ -14937,11 +15132,11 @@ function handleReadonlyResult(payload) {
14937
15132
  payload.value = Object.freeze(payload.value);
14938
15133
  return payload;
14939
15134
  }
14940
- function handleRefineResult(result, payload, input, inst) {
15135
+ function handleRefineResult(result, payload, input2, inst) {
14941
15136
  if (!result) {
14942
15137
  const _iss = {
14943
15138
  code: "custom",
14944
- input,
15139
+ input: input2,
14945
15140
  inst,
14946
15141
  // incorporates params.error into issue reporting
14947
15142
  path: [...inst._zod.def.path ?? []],
@@ -15354,15 +15549,15 @@ var init_schemas = __esm({
15354
15549
  payload.value = Number(payload.value);
15355
15550
  } catch (_) {
15356
15551
  }
15357
- const input = payload.value;
15358
- if (typeof input === "number" && !Number.isNaN(input) && Number.isFinite(input)) {
15552
+ const input2 = payload.value;
15553
+ if (typeof input2 === "number" && !Number.isNaN(input2) && Number.isFinite(input2)) {
15359
15554
  return payload;
15360
15555
  }
15361
- const received = typeof input === "number" ? Number.isNaN(input) ? "NaN" : !Number.isFinite(input) ? "Infinity" : void 0 : void 0;
15556
+ const received = typeof input2 === "number" ? Number.isNaN(input2) ? "NaN" : !Number.isFinite(input2) ? "Infinity" : void 0 : void 0;
15362
15557
  payload.issues.push({
15363
15558
  expected: "number",
15364
15559
  code: "invalid_type",
15365
- input,
15560
+ input: input2,
15366
15561
  inst,
15367
15562
  ...received ? { received } : {}
15368
15563
  });
@@ -15382,13 +15577,13 @@ var init_schemas = __esm({
15382
15577
  payload.value = Boolean(payload.value);
15383
15578
  } catch (_) {
15384
15579
  }
15385
- const input = payload.value;
15386
- if (typeof input === "boolean")
15580
+ const input2 = payload.value;
15581
+ if (typeof input2 === "boolean")
15387
15582
  return payload;
15388
15583
  payload.issues.push({
15389
15584
  expected: "boolean",
15390
15585
  code: "invalid_type",
15391
- input,
15586
+ input: input2,
15392
15587
  inst
15393
15588
  });
15394
15589
  return payload;
@@ -15421,13 +15616,13 @@ var init_schemas = __esm({
15421
15616
  $ZodSymbol = /* @__PURE__ */ $constructor("$ZodSymbol", (inst, def) => {
15422
15617
  $ZodType.init(inst, def);
15423
15618
  inst._zod.parse = (payload, _ctx) => {
15424
- const input = payload.value;
15425
- if (typeof input === "symbol")
15619
+ const input2 = payload.value;
15620
+ if (typeof input2 === "symbol")
15426
15621
  return payload;
15427
15622
  payload.issues.push({
15428
15623
  expected: "symbol",
15429
15624
  code: "invalid_type",
15430
- input,
15625
+ input: input2,
15431
15626
  inst
15432
15627
  });
15433
15628
  return payload;
@@ -15440,13 +15635,13 @@ var init_schemas = __esm({
15440
15635
  inst._zod.optin = "optional";
15441
15636
  inst._zod.optout = "optional";
15442
15637
  inst._zod.parse = (payload, _ctx) => {
15443
- const input = payload.value;
15444
- if (typeof input === "undefined")
15638
+ const input2 = payload.value;
15639
+ if (typeof input2 === "undefined")
15445
15640
  return payload;
15446
15641
  payload.issues.push({
15447
15642
  expected: "undefined",
15448
15643
  code: "invalid_type",
15449
- input,
15644
+ input: input2,
15450
15645
  inst
15451
15646
  });
15452
15647
  return payload;
@@ -15457,13 +15652,13 @@ var init_schemas = __esm({
15457
15652
  inst._zod.pattern = _null;
15458
15653
  inst._zod.values = /* @__PURE__ */ new Set([null]);
15459
15654
  inst._zod.parse = (payload, _ctx) => {
15460
- const input = payload.value;
15461
- if (input === null)
15655
+ const input2 = payload.value;
15656
+ if (input2 === null)
15462
15657
  return payload;
15463
15658
  payload.issues.push({
15464
15659
  expected: "null",
15465
15660
  code: "invalid_type",
15466
- input,
15661
+ input: input2,
15467
15662
  inst
15468
15663
  });
15469
15664
  return payload;
@@ -15492,13 +15687,13 @@ var init_schemas = __esm({
15492
15687
  $ZodVoid = /* @__PURE__ */ $constructor("$ZodVoid", (inst, def) => {
15493
15688
  $ZodType.init(inst, def);
15494
15689
  inst._zod.parse = (payload, _ctx) => {
15495
- const input = payload.value;
15496
- if (typeof input === "undefined")
15690
+ const input2 = payload.value;
15691
+ if (typeof input2 === "undefined")
15497
15692
  return payload;
15498
15693
  payload.issues.push({
15499
15694
  expected: "void",
15500
15695
  code: "invalid_type",
15501
- input,
15696
+ input: input2,
15502
15697
  inst
15503
15698
  });
15504
15699
  return payload;
@@ -15513,15 +15708,15 @@ var init_schemas = __esm({
15513
15708
  } catch (_err) {
15514
15709
  }
15515
15710
  }
15516
- const input = payload.value;
15517
- const isDate = input instanceof Date;
15518
- const isValidDate = isDate && !Number.isNaN(input.getTime());
15711
+ const input2 = payload.value;
15712
+ const isDate = input2 instanceof Date;
15713
+ const isValidDate = isDate && !Number.isNaN(input2.getTime());
15519
15714
  if (isValidDate)
15520
15715
  return payload;
15521
15716
  payload.issues.push({
15522
15717
  expected: "date",
15523
15718
  code: "invalid_type",
15524
- input,
15719
+ input: input2,
15525
15720
  ...isDate ? { received: "Invalid Date" } : {},
15526
15721
  inst
15527
15722
  });
@@ -15531,20 +15726,20 @@ var init_schemas = __esm({
15531
15726
  $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
15532
15727
  $ZodType.init(inst, def);
15533
15728
  inst._zod.parse = (payload, ctx) => {
15534
- const input = payload.value;
15535
- if (!Array.isArray(input)) {
15729
+ const input2 = payload.value;
15730
+ if (!Array.isArray(input2)) {
15536
15731
  payload.issues.push({
15537
15732
  expected: "array",
15538
15733
  code: "invalid_type",
15539
- input,
15734
+ input: input2,
15540
15735
  inst
15541
15736
  });
15542
15737
  return payload;
15543
15738
  }
15544
- payload.value = Array(input.length);
15739
+ payload.value = Array(input2.length);
15545
15740
  const proms = [];
15546
- for (let i = 0; i < input.length; i++) {
15547
- const item = input[i];
15741
+ for (let i = 0; i < input2.length; i++) {
15742
+ const item = input2[i];
15548
15743
  const result = def.element._zod.run({
15549
15744
  value: item,
15550
15745
  issues: []
@@ -15595,12 +15790,12 @@ var init_schemas = __esm({
15595
15790
  let value;
15596
15791
  inst._zod.parse = (payload, ctx) => {
15597
15792
  value ?? (value = _normalized.value);
15598
- const input = payload.value;
15599
- if (!isObject2(input)) {
15793
+ const input2 = payload.value;
15794
+ if (!isObject2(input2)) {
15600
15795
  payload.issues.push({
15601
15796
  expected: "object",
15602
15797
  code: "invalid_type",
15603
- input,
15798
+ input: input2,
15604
15799
  inst
15605
15800
  });
15606
15801
  return payload;
@@ -15611,17 +15806,17 @@ var init_schemas = __esm({
15611
15806
  for (const key of value.keys) {
15612
15807
  const el = shape[key];
15613
15808
  const isOptionalOut = el._zod.optout === "optional";
15614
- const r = el._zod.run({ value: input[key], issues: [] }, ctx);
15809
+ const r = el._zod.run({ value: input2[key], issues: [] }, ctx);
15615
15810
  if (r instanceof Promise) {
15616
- proms.push(r.then((r2) => handlePropertyResult(r2, payload, key, input, isOptionalOut)));
15811
+ proms.push(r.then((r2) => handlePropertyResult(r2, payload, key, input2, isOptionalOut)));
15617
15812
  } else {
15618
- handlePropertyResult(r, payload, key, input, isOptionalOut);
15813
+ handlePropertyResult(r, payload, key, input2, isOptionalOut);
15619
15814
  }
15620
15815
  }
15621
15816
  if (!catchall) {
15622
15817
  return proms.length ? Promise.all(proms).then(() => payload) : payload;
15623
15818
  }
15624
- return handleCatchall(proms, input, payload, ctx, _normalized.value, inst);
15819
+ return handleCatchall(proms, input2, payload, ctx, _normalized.value, inst);
15625
15820
  };
15626
15821
  });
15627
15822
  $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) => {
@@ -15702,12 +15897,12 @@ var init_schemas = __esm({
15702
15897
  let value;
15703
15898
  inst._zod.parse = (payload, ctx) => {
15704
15899
  value ?? (value = _normalized.value);
15705
- const input = payload.value;
15706
- if (!isObject2(input)) {
15900
+ const input2 = payload.value;
15901
+ if (!isObject2(input2)) {
15707
15902
  payload.issues.push({
15708
15903
  expected: "object",
15709
15904
  code: "invalid_type",
15710
- input,
15905
+ input: input2,
15711
15906
  inst
15712
15907
  });
15713
15908
  return payload;
@@ -15718,7 +15913,7 @@ var init_schemas = __esm({
15718
15913
  payload = fastpass(payload, ctx);
15719
15914
  if (!catchall)
15720
15915
  return payload;
15721
- return handleCatchall([], input, payload, ctx, value, inst);
15916
+ return handleCatchall([], input2, payload, ctx, value, inst);
15722
15917
  }
15723
15918
  return superParse(payload, ctx);
15724
15919
  };
@@ -15836,17 +16031,17 @@ var init_schemas = __esm({
15836
16031
  return map2;
15837
16032
  });
15838
16033
  inst._zod.parse = (payload, ctx) => {
15839
- const input = payload.value;
15840
- if (!isObject(input)) {
16034
+ const input2 = payload.value;
16035
+ if (!isObject(input2)) {
15841
16036
  payload.issues.push({
15842
16037
  code: "invalid_type",
15843
16038
  expected: "object",
15844
- input,
16039
+ input: input2,
15845
16040
  inst
15846
16041
  });
15847
16042
  return payload;
15848
16043
  }
15849
- const opt = disc.value.get(input?.[def.discriminator]);
16044
+ const opt = disc.value.get(input2?.[def.discriminator]);
15850
16045
  if (opt) {
15851
16046
  return opt._zod.run(payload, ctx);
15852
16047
  }
@@ -15858,7 +16053,7 @@ var init_schemas = __esm({
15858
16053
  errors: [],
15859
16054
  note: "No matching discriminator",
15860
16055
  discriminator: def.discriminator,
15861
- input,
16056
+ input: input2,
15862
16057
  path: [def.discriminator],
15863
16058
  inst
15864
16059
  });
@@ -15868,9 +16063,9 @@ var init_schemas = __esm({
15868
16063
  $ZodIntersection = /* @__PURE__ */ $constructor("$ZodIntersection", (inst, def) => {
15869
16064
  $ZodType.init(inst, def);
15870
16065
  inst._zod.parse = (payload, ctx) => {
15871
- const input = payload.value;
15872
- const left = def.left._zod.run({ value: input, issues: [] }, ctx);
15873
- const right = def.right._zod.run({ value: input, issues: [] }, ctx);
16066
+ const input2 = payload.value;
16067
+ const left = def.left._zod.run({ value: input2, issues: [] }, ctx);
16068
+ const right = def.right._zod.run({ value: input2, issues: [] }, ctx);
15874
16069
  const async = left instanceof Promise || right instanceof Promise;
15875
16070
  if (async) {
15876
16071
  return Promise.all([left, right]).then(([left2, right2]) => {
@@ -15884,10 +16079,10 @@ var init_schemas = __esm({
15884
16079
  $ZodType.init(inst, def);
15885
16080
  const items = def.items;
15886
16081
  inst._zod.parse = (payload, ctx) => {
15887
- const input = payload.value;
15888
- if (!Array.isArray(input)) {
16082
+ const input2 = payload.value;
16083
+ if (!Array.isArray(input2)) {
15889
16084
  payload.issues.push({
15890
- input,
16085
+ input: input2,
15891
16086
  inst,
15892
16087
  expected: "tuple",
15893
16088
  code: "invalid_type"
@@ -15899,12 +16094,12 @@ var init_schemas = __esm({
15899
16094
  const reversedIndex = [...items].reverse().findIndex((item) => item._zod.optin !== "optional");
15900
16095
  const optStart = reversedIndex === -1 ? 0 : items.length - reversedIndex;
15901
16096
  if (!def.rest) {
15902
- const tooBig = input.length > items.length;
15903
- const tooSmall = input.length < optStart - 1;
16097
+ const tooBig = input2.length > items.length;
16098
+ const tooSmall = input2.length < optStart - 1;
15904
16099
  if (tooBig || tooSmall) {
15905
16100
  payload.issues.push({
15906
16101
  ...tooBig ? { code: "too_big", maximum: items.length, inclusive: true } : { code: "too_small", minimum: items.length },
15907
- input,
16102
+ input: input2,
15908
16103
  inst,
15909
16104
  origin: "array"
15910
16105
  });
@@ -15914,12 +16109,12 @@ var init_schemas = __esm({
15914
16109
  let i = -1;
15915
16110
  for (const item of items) {
15916
16111
  i++;
15917
- if (i >= input.length) {
16112
+ if (i >= input2.length) {
15918
16113
  if (i >= optStart)
15919
16114
  continue;
15920
16115
  }
15921
16116
  const result = item._zod.run({
15922
- value: input[i],
16117
+ value: input2[i],
15923
16118
  issues: []
15924
16119
  }, ctx);
15925
16120
  if (result instanceof Promise) {
@@ -15929,7 +16124,7 @@ var init_schemas = __esm({
15929
16124
  }
15930
16125
  }
15931
16126
  if (def.rest) {
15932
- const rest = input.slice(items.length);
16127
+ const rest = input2.slice(items.length);
15933
16128
  for (const el of rest) {
15934
16129
  i++;
15935
16130
  const result = def.rest._zod.run({
@@ -15951,12 +16146,12 @@ var init_schemas = __esm({
15951
16146
  $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
15952
16147
  $ZodType.init(inst, def);
15953
16148
  inst._zod.parse = (payload, ctx) => {
15954
- const input = payload.value;
15955
- if (!isPlainObject(input)) {
16149
+ const input2 = payload.value;
16150
+ if (!isPlainObject(input2)) {
15956
16151
  payload.issues.push({
15957
16152
  expected: "record",
15958
16153
  code: "invalid_type",
15959
- input,
16154
+ input: input2,
15960
16155
  inst
15961
16156
  });
15962
16157
  return payload;
@@ -15969,7 +16164,7 @@ var init_schemas = __esm({
15969
16164
  for (const key of values2) {
15970
16165
  if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
15971
16166
  recordKeys.add(typeof key === "number" ? key.toString() : key);
15972
- const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
16167
+ const result = def.valueType._zod.run({ value: input2[key], issues: [] }, ctx);
15973
16168
  if (result instanceof Promise) {
15974
16169
  proms.push(result.then((result2) => {
15975
16170
  if (result2.issues.length) {
@@ -15986,7 +16181,7 @@ var init_schemas = __esm({
15986
16181
  }
15987
16182
  }
15988
16183
  let unrecognized;
15989
- for (const key in input) {
16184
+ for (const key in input2) {
15990
16185
  if (!recordKeys.has(key)) {
15991
16186
  unrecognized = unrecognized ?? [];
15992
16187
  unrecognized.push(key);
@@ -15995,14 +16190,14 @@ var init_schemas = __esm({
15995
16190
  if (unrecognized && unrecognized.length > 0) {
15996
16191
  payload.issues.push({
15997
16192
  code: "unrecognized_keys",
15998
- input,
16193
+ input: input2,
15999
16194
  inst,
16000
16195
  keys: unrecognized
16001
16196
  });
16002
16197
  }
16003
16198
  } else {
16004
16199
  payload.value = {};
16005
- for (const key of Reflect.ownKeys(input)) {
16200
+ for (const key of Reflect.ownKeys(input2)) {
16006
16201
  if (key === "__proto__")
16007
16202
  continue;
16008
16203
  let keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
@@ -16021,7 +16216,7 @@ var init_schemas = __esm({
16021
16216
  }
16022
16217
  if (keyResult.issues.length) {
16023
16218
  if (def.mode === "loose") {
16024
- payload.value[key] = input[key];
16219
+ payload.value[key] = input2[key];
16025
16220
  } else {
16026
16221
  payload.issues.push({
16027
16222
  code: "invalid_key",
@@ -16034,7 +16229,7 @@ var init_schemas = __esm({
16034
16229
  }
16035
16230
  continue;
16036
16231
  }
16037
- const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
16232
+ const result = def.valueType._zod.run({ value: input2[key], issues: [] }, ctx);
16038
16233
  if (result instanceof Promise) {
16039
16234
  proms.push(result.then((result2) => {
16040
16235
  if (result2.issues.length) {
@@ -16059,27 +16254,27 @@ var init_schemas = __esm({
16059
16254
  $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
16060
16255
  $ZodType.init(inst, def);
16061
16256
  inst._zod.parse = (payload, ctx) => {
16062
- const input = payload.value;
16063
- if (!(input instanceof Map)) {
16257
+ const input2 = payload.value;
16258
+ if (!(input2 instanceof Map)) {
16064
16259
  payload.issues.push({
16065
16260
  expected: "map",
16066
16261
  code: "invalid_type",
16067
- input,
16262
+ input: input2,
16068
16263
  inst
16069
16264
  });
16070
16265
  return payload;
16071
16266
  }
16072
16267
  const proms = [];
16073
16268
  payload.value = /* @__PURE__ */ new Map();
16074
- for (const [key, value] of input) {
16269
+ for (const [key, value] of input2) {
16075
16270
  const keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
16076
16271
  const valueResult = def.valueType._zod.run({ value, issues: [] }, ctx);
16077
16272
  if (keyResult instanceof Promise || valueResult instanceof Promise) {
16078
16273
  proms.push(Promise.all([keyResult, valueResult]).then(([keyResult2, valueResult2]) => {
16079
- handleMapResult(keyResult2, valueResult2, payload, key, input, inst, ctx);
16274
+ handleMapResult(keyResult2, valueResult2, payload, key, input2, inst, ctx);
16080
16275
  }));
16081
16276
  } else {
16082
- handleMapResult(keyResult, valueResult, payload, key, input, inst, ctx);
16277
+ handleMapResult(keyResult, valueResult, payload, key, input2, inst, ctx);
16083
16278
  }
16084
16279
  }
16085
16280
  if (proms.length)
@@ -16090,10 +16285,10 @@ var init_schemas = __esm({
16090
16285
  $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
16091
16286
  $ZodType.init(inst, def);
16092
16287
  inst._zod.parse = (payload, ctx) => {
16093
- const input = payload.value;
16094
- if (!(input instanceof Set)) {
16288
+ const input2 = payload.value;
16289
+ if (!(input2 instanceof Set)) {
16095
16290
  payload.issues.push({
16096
- input,
16291
+ input: input2,
16097
16292
  inst,
16098
16293
  expected: "set",
16099
16294
  code: "invalid_type"
@@ -16102,7 +16297,7 @@ var init_schemas = __esm({
16102
16297
  }
16103
16298
  const proms = [];
16104
16299
  payload.value = /* @__PURE__ */ new Set();
16105
- for (const item of input) {
16300
+ for (const item of input2) {
16106
16301
  const result = def.valueType._zod.run({ value: item, issues: [] }, ctx);
16107
16302
  if (result instanceof Promise) {
16108
16303
  proms.push(result.then((result2) => handleSetResult(result2, payload)));
@@ -16121,14 +16316,14 @@ var init_schemas = __esm({
16121
16316
  inst._zod.values = valuesSet;
16122
16317
  inst._zod.pattern = new RegExp(`^(${values2.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
16123
16318
  inst._zod.parse = (payload, _ctx) => {
16124
- const input = payload.value;
16125
- if (valuesSet.has(input)) {
16319
+ const input2 = payload.value;
16320
+ if (valuesSet.has(input2)) {
16126
16321
  return payload;
16127
16322
  }
16128
16323
  payload.issues.push({
16129
16324
  code: "invalid_value",
16130
16325
  values: values2,
16131
- input,
16326
+ input: input2,
16132
16327
  inst
16133
16328
  });
16134
16329
  return payload;
@@ -16143,14 +16338,14 @@ var init_schemas = __esm({
16143
16338
  inst._zod.values = values2;
16144
16339
  inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
16145
16340
  inst._zod.parse = (payload, _ctx) => {
16146
- const input = payload.value;
16147
- if (values2.has(input)) {
16341
+ const input2 = payload.value;
16342
+ if (values2.has(input2)) {
16148
16343
  return payload;
16149
16344
  }
16150
16345
  payload.issues.push({
16151
16346
  code: "invalid_value",
16152
16347
  values: def.values,
16153
- input,
16348
+ input: input2,
16154
16349
  inst
16155
16350
  });
16156
16351
  return payload;
@@ -16159,13 +16354,13 @@ var init_schemas = __esm({
16159
16354
  $ZodFile = /* @__PURE__ */ $constructor("$ZodFile", (inst, def) => {
16160
16355
  $ZodType.init(inst, def);
16161
16356
  inst._zod.parse = (payload, _ctx) => {
16162
- const input = payload.value;
16163
- if (input instanceof File)
16357
+ const input2 = payload.value;
16358
+ if (input2 instanceof File)
16164
16359
  return payload;
16165
16360
  payload.issues.push({
16166
16361
  expected: "file",
16167
16362
  code: "invalid_type",
16168
- input,
16363
+ input: input2,
16169
16364
  inst
16170
16365
  });
16171
16366
  return payload;
@@ -16179,9 +16374,9 @@ var init_schemas = __esm({
16179
16374
  }
16180
16375
  const _out = def.transform(payload.value, payload);
16181
16376
  if (ctx.async) {
16182
- const output2 = _out instanceof Promise ? _out : Promise.resolve(_out);
16183
- return output2.then((output3) => {
16184
- payload.value = output3;
16377
+ const output3 = _out instanceof Promise ? _out : Promise.resolve(_out);
16378
+ return output3.then((output4) => {
16379
+ payload.value = output4;
16185
16380
  return payload;
16186
16381
  });
16187
16382
  }
@@ -16533,12 +16728,12 @@ var init_schemas = __esm({
16533
16728
  output: inst._def.output
16534
16729
  });
16535
16730
  };
16536
- inst.output = (output2) => {
16731
+ inst.output = (output3) => {
16537
16732
  const F = inst.constructor;
16538
16733
  return new F({
16539
16734
  type: "function",
16540
16735
  input: inst._def.input,
16541
- output: output2
16736
+ output: output3
16542
16737
  });
16543
16738
  };
16544
16739
  return inst;
@@ -16568,12 +16763,12 @@ var init_schemas = __esm({
16568
16763
  return payload;
16569
16764
  };
16570
16765
  inst._zod.check = (payload) => {
16571
- const input = payload.value;
16572
- const r = def.fn(input);
16766
+ const input2 = payload.value;
16767
+ const r = def.fn(input2);
16573
16768
  if (r instanceof Promise) {
16574
- return r.then((r2) => handleRefineResult(r2, payload, input, inst));
16769
+ return r.then((r2) => handleRefineResult(r2, payload, input2, inst));
16575
16770
  }
16576
- handleRefineResult(r, payload, input, inst);
16771
+ handleRefineResult(r, payload, input2, inst);
16577
16772
  return;
16578
16773
  };
16579
16774
  });
@@ -23218,23 +23413,23 @@ function _overwrite(tx) {
23218
23413
  }
23219
23414
  // @__NO_SIDE_EFFECTS__
23220
23415
  function _normalize(form) {
23221
- return /* @__PURE__ */ _overwrite((input) => input.normalize(form));
23416
+ return /* @__PURE__ */ _overwrite((input2) => input2.normalize(form));
23222
23417
  }
23223
23418
  // @__NO_SIDE_EFFECTS__
23224
23419
  function _trim() {
23225
- return /* @__PURE__ */ _overwrite((input) => input.trim());
23420
+ return /* @__PURE__ */ _overwrite((input2) => input2.trim());
23226
23421
  }
23227
23422
  // @__NO_SIDE_EFFECTS__
23228
23423
  function _toLowerCase() {
23229
- return /* @__PURE__ */ _overwrite((input) => input.toLowerCase());
23424
+ return /* @__PURE__ */ _overwrite((input2) => input2.toLowerCase());
23230
23425
  }
23231
23426
  // @__NO_SIDE_EFFECTS__
23232
23427
  function _toUpperCase() {
23233
- return /* @__PURE__ */ _overwrite((input) => input.toUpperCase());
23428
+ return /* @__PURE__ */ _overwrite((input2) => input2.toUpperCase());
23234
23429
  }
23235
23430
  // @__NO_SIDE_EFFECTS__
23236
23431
  function _slugify() {
23237
- return /* @__PURE__ */ _overwrite((input) => slugify(input));
23432
+ return /* @__PURE__ */ _overwrite((input2) => slugify(input2));
23238
23433
  }
23239
23434
  // @__NO_SIDE_EFFECTS__
23240
23435
  function _array(Class2, element, params) {
@@ -23539,8 +23734,8 @@ function _stringbool(Classes, _params) {
23539
23734
  type: "pipe",
23540
23735
  in: stringSchema,
23541
23736
  out: booleanSchema,
23542
- transform: ((input, payload) => {
23543
- let data = input;
23737
+ transform: ((input2, payload) => {
23738
+ let data = input2;
23544
23739
  if (params.case !== "sensitive")
23545
23740
  data = data.toLowerCase();
23546
23741
  if (truthySet.has(data)) {
@@ -23559,8 +23754,8 @@ function _stringbool(Classes, _params) {
23559
23754
  return {};
23560
23755
  }
23561
23756
  }),
23562
- reverseTransform: ((input, _payload) => {
23563
- if (input === true) {
23757
+ reverseTransform: ((input2, _payload) => {
23758
+ if (input2 === true) {
23564
23759
  return truthyArray[0] || "true";
23565
23760
  } else {
23566
23761
  return falsyArray[0] || "false";
@@ -23963,9 +24158,9 @@ var init_to_json_schema = __esm({
23963
24158
  });
23964
24159
 
23965
24160
  // ../../node_modules/zod/v4/core/json-schema-processors.js
23966
- function toJSONSchema(input, params) {
23967
- if ("_idmap" in input) {
23968
- const registry2 = input;
24161
+ function toJSONSchema(input2, params) {
24162
+ if ("_idmap" in input2) {
24163
+ const registry2 = input2;
23969
24164
  const ctx2 = initializeContext({ ...params, processors: allProcessors });
23970
24165
  const defs = {};
23971
24166
  for (const entry of registry2._idmap.entries()) {
@@ -23993,9 +24188,9 @@ function toJSONSchema(input, params) {
23993
24188
  return { schemas };
23994
24189
  }
23995
24190
  const ctx = initializeContext({ ...params, processors: allProcessors });
23996
- process2(input, ctx);
23997
- extractDefs(ctx, input);
23998
- return finalize(ctx, input);
24191
+ process2(input2, ctx);
24192
+ extractDefs(ctx, input2);
24193
+ return finalize(ctx, input2);
23999
24194
  }
24000
24195
  var formatMap, stringProcessor, numberProcessor, booleanProcessor, bigintProcessor, symbolProcessor, nullProcessor, undefinedProcessor, voidProcessor, neverProcessor, anyProcessor, unknownProcessor, dateProcessor, enumProcessor, literalProcessor, nanProcessor, templateLiteralProcessor, fileProcessor, successProcessor, customProcessor, functionProcessor, transformProcessor, mapProcessor, setProcessor, arrayProcessor, objectProcessor, unionProcessor, intersectionProcessor, tupleProcessor, recordProcessor, nullableProcessor, nonoptionalProcessor, defaultProcessor, prefaultProcessor, catchProcessor, pipeProcessor, readonlyProcessor, promiseProcessor, optionalProcessor, lazyProcessor, allProcessors;
24001
24196
  var init_json_schema_processors = __esm({
@@ -26201,14 +26396,14 @@ var init_schemas2 = __esm({
26201
26396
  payload.issues.push(util_exports.issue(_issue));
26202
26397
  }
26203
26398
  };
26204
- const output2 = def.transform(payload.value, payload);
26205
- if (output2 instanceof Promise) {
26206
- return output2.then((output3) => {
26207
- payload.value = output3;
26399
+ const output3 = def.transform(payload.value, payload);
26400
+ if (output3 instanceof Promise) {
26401
+ return output3.then((output4) => {
26402
+ payload.value = output4;
26208
26403
  return payload;
26209
26404
  });
26210
26405
  }
26211
- payload.value = output2;
26406
+ payload.value = output3;
26212
26407
  return payload;
26213
26408
  };
26214
26409
  });
@@ -26371,13 +26566,13 @@ function resolveRef(ref, ctx) {
26371
26566
  if (!ref.startsWith("#")) {
26372
26567
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
26373
26568
  }
26374
- const path12 = ref.slice(1).split("/").filter(Boolean);
26375
- if (path12.length === 0) {
26569
+ const path15 = ref.slice(1).split("/").filter(Boolean);
26570
+ if (path15.length === 0) {
26376
26571
  return ctx.rootSchema;
26377
26572
  }
26378
26573
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
26379
- if (path12[0] === defsKey) {
26380
- const key = path12[1];
26574
+ if (path15[0] === defsKey) {
26575
+ const key = path15[1];
26381
26576
  if (!key || !ctx.defs[key]) {
26382
26577
  throw new Error(`Reference not found: ${ref}`);
26383
26578
  }
@@ -28177,7 +28372,8 @@ var init_enrollments = __esm({
28177
28372
  expiresAt: timestamp("expires_at"),
28178
28373
  // Story 3.5: Preview expiration (enrolledAt + 7 days)
28179
28374
  setupChecklist: jsonb("setup_checklist").$type(),
28180
- updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
28375
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
28376
+ metadata: jsonb("metadata").$type().default({})
28181
28377
  },
28182
28378
  (table) => ({
28183
28379
  userIdIdx: index("enrollments_user_id_idx").on(table.userId),
@@ -36021,6 +36217,80 @@ var init_student_course_memory = __esm({
36021
36217
  }
36022
36218
  });
36023
36219
 
36220
+ // ../../packages/database/src/schema/student-feedback.ts
36221
+ var studentFeedbackKindEnum, studentFeedbackSurfaceEnum, studentFeedbackCategoryEnum, studentFeedback;
36222
+ var init_student_feedback = __esm({
36223
+ "../../packages/database/src/schema/student-feedback.ts"() {
36224
+ "use strict";
36225
+ init_drizzle_orm();
36226
+ init_pg_core();
36227
+ init_courses3();
36228
+ init_lessons();
36229
+ init_module_exercises();
36230
+ init_study_module_sessions();
36231
+ init_users();
36232
+ studentFeedbackKindEnum = pgEnum("student_feedback_kind", [
36233
+ "free_text",
36234
+ "lesson_pulse"
36235
+ ]);
36236
+ studentFeedbackSurfaceEnum = pgEnum("student_feedback_surface", [
36237
+ "lesson",
36238
+ "exercise",
36239
+ "chatstudy"
36240
+ ]);
36241
+ studentFeedbackCategoryEnum = pgEnum("student_feedback_category", [
36242
+ "BUG",
36243
+ "UX",
36244
+ "FEAT",
36245
+ "CONTENT",
36246
+ "PERF",
36247
+ "INTEG"
36248
+ ]);
36249
+ studentFeedback = pgTable(
36250
+ "student_feedback",
36251
+ {
36252
+ id: uuid("id").defaultRandom().primaryKey(),
36253
+ studentId: uuid("student_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
36254
+ courseId: uuid("course_id").references(() => courses.id, { onDelete: "cascade" }).notNull(),
36255
+ surface: studentFeedbackSurfaceEnum("surface").notNull(),
36256
+ routeOrSlug: text("route_or_slug").notNull(),
36257
+ kind: studentFeedbackKindEnum("kind").notNull().default("free_text"),
36258
+ category: studentFeedbackCategoryEnum("category"),
36259
+ comment: text("comment"),
36260
+ rating: integer("rating"),
36261
+ lessonId: uuid("lesson_id").references(() => lessons.id, { onDelete: "set null" }),
36262
+ exerciseId: uuid("exercise_id").references(() => moduleExercises.id, { onDelete: "set null" }),
36263
+ // Current fullscreen student flow uses v2 module sessions.
36264
+ chatSessionId: uuid("chat_session_id").references(() => studyModuleSessions.id, {
36265
+ onDelete: "set null"
36266
+ }),
36267
+ capturedAt: timestamp("captured_at", { withTimezone: true }).defaultNow().notNull(),
36268
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
36269
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
36270
+ },
36271
+ (table) => ({
36272
+ courseCapturedAtIdx: index("student_feedback_course_captured_at_idx").on(
36273
+ table.courseId,
36274
+ table.capturedAt
36275
+ ),
36276
+ surfaceCapturedAtIdx: index("student_feedback_surface_captured_at_idx").on(
36277
+ table.surface,
36278
+ table.capturedAt
36279
+ ),
36280
+ categoryCapturedAtIdx: index("student_feedback_category_captured_at_idx").on(
36281
+ table.category,
36282
+ table.capturedAt
36283
+ ),
36284
+ studentCapturedAtIdx: index("student_feedback_student_captured_at_idx").on(
36285
+ table.studentId,
36286
+ table.capturedAt
36287
+ ),
36288
+ lessonPulseUniqueIdx: uniqueIndex("student_feedback_lesson_pulse_unique_idx").on(table.studentId, table.lessonId).where(sql`${table.kind} = 'lesson_pulse' AND ${table.lessonId} IS NOT NULL`)
36289
+ })
36290
+ );
36291
+ }
36292
+ });
36293
+
36024
36294
  // ../../packages/database/src/schema/student-module-progress.ts
36025
36295
  var moduleProgressStatusEnum, studentModuleProgress;
36026
36296
  var init_student_module_progress = __esm({
@@ -38198,6 +38468,10 @@ __export(schema_exports, {
38198
38468
  stringifySettingValue: () => stringifySettingValue,
38199
38469
  studentCourseMemory: () => studentCourseMemory,
38200
38470
  studentCourseMemoryRelations: () => studentCourseMemoryRelations,
38471
+ studentFeedback: () => studentFeedback,
38472
+ studentFeedbackCategoryEnum: () => studentFeedbackCategoryEnum,
38473
+ studentFeedbackKindEnum: () => studentFeedbackKindEnum,
38474
+ studentFeedbackSurfaceEnum: () => studentFeedbackSurfaceEnum,
38201
38475
  studentModuleProgress: () => studentModuleProgress,
38202
38476
  studentNotes: () => studentNotes2,
38203
38477
  studentNotesRelations: () => studentNotesRelations2,
@@ -38370,6 +38644,7 @@ var init_schema3 = __esm({
38370
38644
  init_exercise_materials();
38371
38645
  init_course_knowledge_bases();
38372
38646
  init_student_course_memory();
38647
+ init_student_feedback();
38373
38648
  init_student_module_progress();
38374
38649
  init_course_projects();
38375
38650
  init_source_extractions();
@@ -39818,6 +40093,32 @@ var init_exercise_types = __esm({
39818
40093
  }
39819
40094
  });
39820
40095
 
40096
+ // ../../packages/tostudy-core/src/workspace/lesson-exercise-data.ts
40097
+ var init_lesson_exercise_data = __esm({
40098
+ "../../packages/tostudy-core/src/workspace/lesson-exercise-data.ts"() {
40099
+ "use strict";
40100
+ }
40101
+ });
40102
+
40103
+ // ../../packages/tostudy-core/src/learning/sync-enrollment-progress.ts
40104
+ var init_sync_enrollment_progress = __esm({
40105
+ "../../packages/tostudy-core/src/learning/sync-enrollment-progress.ts"() {
40106
+ "use strict";
40107
+ init_src4();
40108
+ init_src();
40109
+ }
40110
+ });
40111
+
40112
+ // ../../packages/tostudy-core/src/learning/study-state-sync.ts
40113
+ var DEFAULT_MAX_SESSION_TIME_SECONDS;
40114
+ var init_study_state_sync = __esm({
40115
+ "../../packages/tostudy-core/src/learning/study-state-sync.ts"() {
40116
+ "use strict";
40117
+ init_src4();
40118
+ DEFAULT_MAX_SESSION_TIME_SECONDS = 2 * 60 * 60;
40119
+ }
40120
+ });
40121
+
39821
40122
  // ../../packages/tostudy-core/src/learning/start-module-full.ts
39822
40123
  var init_start_module_full = __esm({
39823
40124
  "../../packages/tostudy-core/src/learning/start-module-full.ts"() {
@@ -39825,6 +40126,9 @@ var init_start_module_full = __esm({
39825
40126
  init_src4();
39826
40127
  init_parser();
39827
40128
  init_exercise_types();
40129
+ init_lesson_exercise_data();
40130
+ init_sync_enrollment_progress();
40131
+ init_study_state_sync();
39828
40132
  }
39829
40133
  });
39830
40134
 
@@ -39834,6 +40138,9 @@ var init_next_lesson_full = __esm({
39834
40138
  "use strict";
39835
40139
  init_src4();
39836
40140
  init_parser();
40141
+ init_lesson_exercise_data();
40142
+ init_sync_enrollment_progress();
40143
+ init_study_state_sync();
39837
40144
  }
39838
40145
  });
39839
40146
 
@@ -39844,6 +40151,8 @@ var init_validate_solution_full = __esm({
39844
40151
  init_src4();
39845
40152
  init_parser();
39846
40153
  init_exercise_types();
40154
+ init_study_state_sync();
40155
+ init_sync_enrollment_progress();
39847
40156
  }
39848
40157
  });
39849
40158
 
@@ -39903,6 +40212,7 @@ var init_learning = __esm({
39903
40212
  init_get_lesson_content_full();
39904
40213
  init_get_hint_full();
39905
40214
  init_explain_concept();
40215
+ init_sync_enrollment_progress();
39906
40216
  }
39907
40217
  });
39908
40218
 
@@ -39921,7 +40231,50 @@ var init_lessons2 = __esm({
39921
40231
 
39922
40232
  // src/commands/start.ts
39923
40233
  import { Command as Command8 } from "commander";
39924
- var logger5, startCommand;
40234
+ function buildOnboardingBlockerMessage(courseTitle, onboarding) {
40235
+ const lines = [`Onboarding obrigat\xF3rio pendente para "${courseTitle}".`];
40236
+ if (!onboarding.initReady) {
40237
+ lines.push("Execute `tostudy init` primeiro para configurar o tutor deste curso.");
40238
+ }
40239
+ if (!onboarding.workspaceReady) {
40240
+ lines.push("Execute `tostudy workspace setup` para criar o workspace local antes de iniciar.");
40241
+ }
40242
+ return lines.join("\n");
40243
+ }
40244
+ async function runStart(opts, deps = defaultDeps2) {
40245
+ try {
40246
+ const session = await deps.requireSession();
40247
+ const activeCourse = await deps.requireActiveCourse();
40248
+ const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
40249
+ if (!onboarding.initReady || !onboarding.workspaceReady) {
40250
+ throw new StartBlockedError(
40251
+ buildOnboardingBlockerMessage(activeCourse.courseTitle, onboarding)
40252
+ );
40253
+ }
40254
+ const driftWarning = await deps.checkCourseDrift();
40255
+ if (driftWarning) deps.stderrWrite(driftWarning + "\n");
40256
+ const data = deps.createHttpProvider(session.apiUrl, session.token);
40257
+ const moduleData = await deps.startModule(
40258
+ { enrollmentId: activeCourse.enrollmentId },
40259
+ { data, logger: deps.logger }
40260
+ );
40261
+ await deps.setActiveCourse({ ...activeCourse, currentLessonId: moduleData.firstLesson.id });
40262
+ if (opts.json) {
40263
+ deps.output(moduleData, { json: true });
40264
+ } else {
40265
+ deps.output(deps.formatModuleStart(moduleData), { json: false });
40266
+ }
40267
+ } catch (err) {
40268
+ if (err instanceof CliApiError && err.status === 403 && err.message === "CHANNEL_MISMATCH") {
40269
+ process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
40270
+ process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
40271
+ process.exit(1);
40272
+ }
40273
+ const msg = err instanceof Error ? err.message : String(err);
40274
+ deps.error(msg);
40275
+ }
40276
+ }
40277
+ var logger5, defaultDeps2, StartBlockedError, startCommand;
39925
40278
  var init_start = __esm({
39926
40279
  "src/commands/start.ts"() {
39927
40280
  "use strict";
@@ -39929,31 +40282,27 @@ var init_start = __esm({
39929
40282
  init_lessons2();
39930
40283
  init_http2();
39931
40284
  init_session();
40285
+ init_status();
39932
40286
  init_formatter();
39933
40287
  logger5 = createLogger("cli:start");
40288
+ defaultDeps2 = {
40289
+ requireSession,
40290
+ requireActiveCourse,
40291
+ checkCourseDrift,
40292
+ getCourseOnboardingStatus,
40293
+ createHttpProvider,
40294
+ startModule,
40295
+ setActiveCourse,
40296
+ formatModuleStart,
40297
+ output,
40298
+ error,
40299
+ stderrWrite: (message) => process.stderr.write(message),
40300
+ logger: logger5
40301
+ };
40302
+ StartBlockedError = class extends Error {
40303
+ };
39934
40304
  startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
39935
- try {
39936
- const session = await requireSession();
39937
- const activeCourse = await requireActiveCourse();
39938
- const driftWarning = await checkCourseDrift();
39939
- if (driftWarning) process.stderr.write(driftWarning + "\n");
39940
- const data = createHttpProvider(session.apiUrl, session.token);
39941
- const deps = { data, logger: logger5 };
39942
- const moduleData = await startModule({ enrollmentId: activeCourse.enrollmentId }, deps);
39943
- if (opts.json) {
39944
- output(moduleData, { json: true });
39945
- } else {
39946
- output(formatModuleStart(moduleData), { json: false });
39947
- }
39948
- } catch (err) {
39949
- if (err instanceof CliApiError && err.status === 403 && err.message === "CHANNEL_MISMATCH") {
39950
- process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
39951
- process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
39952
- process.exit(1);
39953
- }
39954
- const msg = err instanceof Error ? err.message : String(err);
39955
- error(msg);
39956
- }
40305
+ await runStart(opts);
39957
40306
  });
39958
40307
  }
39959
40308
  });
@@ -39979,12 +40328,18 @@ var init_start_next = __esm({
39979
40328
  const data = createHttpProvider(session.apiUrl, session.token);
39980
40329
  const deps = { data, logger: logger6 };
39981
40330
  const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
40331
+ await setActiveCourse({ ...activeCourse, currentLessonId: moduleData.firstLesson.id });
39982
40332
  if (opts.json) {
39983
40333
  output(moduleData, { json: true });
39984
40334
  } else {
39985
40335
  output(formatModuleStart(moduleData), { json: false });
39986
40336
  }
39987
40337
  } catch (err) {
40338
+ if (err instanceof CliApiError && err.message === "COURSE_COMPLETE") {
40339
+ process.stderr.write("\u{1F389} Parab\xE9ns! Curso conclu\xEDdo!\n");
40340
+ process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
40341
+ process.exit(0);
40342
+ }
39988
40343
  const msg = err instanceof Error ? err.message : String(err);
39989
40344
  error(msg);
39990
40345
  }
@@ -40016,16 +40371,34 @@ var init_next = __esm({
40016
40371
  { enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
40017
40372
  deps
40018
40373
  );
40374
+ await setActiveCourse({ ...activeCourse, currentLessonId: lessonData.lesson.id });
40019
40375
  if (opts.json) {
40020
40376
  output(lessonData, { json: true });
40021
40377
  } else {
40022
40378
  output(formatLesson(lessonData), { json: false });
40023
40379
  }
40024
40380
  } catch (err) {
40025
- if (err instanceof CliApiError && err.status === 403 && err.message === "CHANNEL_MISMATCH") {
40026
- process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
40027
- process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
40028
- process.exit(1);
40381
+ if (err instanceof CliApiError) {
40382
+ if (err.status === 403 && err.message === "CHANNEL_MISMATCH") {
40383
+ process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
40384
+ process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
40385
+ process.exit(1);
40386
+ }
40387
+ if (err.message === "MODULE_COMPLETE") {
40388
+ process.stderr.write("\u2713 M\xF3dulo conclu\xEDdo!\n");
40389
+ process.stderr.write("\u2192 tostudy start-next para iniciar o pr\xF3ximo m\xF3dulo\n");
40390
+ process.exit(0);
40391
+ }
40392
+ if (err.message === "COURSE_COMPLETE") {
40393
+ process.stderr.write("\u{1F389} Parab\xE9ns! Curso conclu\xEDdo!\n");
40394
+ process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
40395
+ process.exit(0);
40396
+ }
40397
+ if (err.message === "NO_ACTIVE_LESSON") {
40398
+ process.stderr.write("Nenhuma li\xE7\xE3o em progresso.\n");
40399
+ process.stderr.write("\u2192 tostudy start-next para iniciar o pr\xF3ximo m\xF3dulo\n");
40400
+ process.exit(0);
40401
+ }
40029
40402
  }
40030
40403
  const msg = err instanceof Error ? err.message : String(err);
40031
40404
  error(msg);
@@ -40036,11 +40409,19 @@ var init_next = __esm({
40036
40409
 
40037
40410
  // src/commands/lesson.ts
40038
40411
  import { Command as Command11 } from "commander";
40412
+ function adjustTimeEstimate(type, baseMinutes) {
40413
+ if (type === "exercise") return Math.max(baseMinutes, 30);
40414
+ return baseMinutes;
40415
+ }
40416
+ function isCheckpoint(title) {
40417
+ return /^checkpoint/i.test(title.trim());
40418
+ }
40039
40419
  function formatLessonContent(data) {
40420
+ const time4 = adjustTimeEstimate(data.type, data.estimatedTimeMinutes);
40040
40421
  const lines = [
40041
40422
  `\u2501\u2501\u2501 Li\xE7\xE3o: ${data.title} \u2501\u2501\u2501`,
40042
40423
  "",
40043
- `Tipo: ${data.type} | Dura\xE7\xE3o estimada: ~${data.estimatedTimeMinutes} min`,
40424
+ `Tipo: ${data.type} | Dura\xE7\xE3o estimada: ~${time4} min`,
40044
40425
  "",
40045
40426
  data.content
40046
40427
  ];
@@ -40050,12 +40431,20 @@ function formatLessonContent(data) {
40050
40431
  if (data.hints && data.hints.length > 0) {
40051
40432
  lines.push("", `Dicas dispon\xEDveis: ${data.hints.length}`);
40052
40433
  }
40053
- lines.push(
40054
- "",
40055
- "\u2192 tostudy validate <arquivo> para validar sua solu\xE7\xE3o",
40056
- "\u2192 tostudy hint para obter uma dica",
40057
- "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o"
40058
- );
40434
+ if (isCheckpoint(data.title)) {
40435
+ lines.push(
40436
+ "",
40437
+ "\u{1F4A1} Escreva suas respostas num arquivo (ex: checkpoint.md) e valide:",
40438
+ " tostudy validate checkpoint.md"
40439
+ );
40440
+ } else {
40441
+ lines.push(
40442
+ "",
40443
+ "\u2192 tostudy validate <arquivo> para validar sua solu\xE7\xE3o",
40444
+ "\u2192 tostudy hint para obter uma dica",
40445
+ "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o"
40446
+ );
40447
+ }
40059
40448
  return lines.join("\n");
40060
40449
  }
40061
40450
  var logger8, lessonCommand;
@@ -40067,6 +40456,7 @@ var init_lesson = __esm({
40067
40456
  init_http2();
40068
40457
  init_session();
40069
40458
  init_formatter();
40459
+ init_resolve();
40070
40460
  logger8 = createLogger("cli:lesson");
40071
40461
  lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
40072
40462
  try {
@@ -40086,6 +40476,14 @@ var init_lesson = __esm({
40086
40476
  } else {
40087
40477
  output(formatLessonContent(content), { json: false });
40088
40478
  }
40479
+ if (content.type === "exercise") {
40480
+ const ws = await resolveWorkspace(activeCourse.courseTitle);
40481
+ if (!ws.found) {
40482
+ process.stderr.write(
40483
+ "\n\u{1F4A1} Dica: rode `tostudy workspace setup` para criar um workspace local para exerc\xEDcios.\n"
40484
+ );
40485
+ }
40486
+ }
40089
40487
  } catch (err) {
40090
40488
  const msg = err instanceof Error ? err.message : String(err);
40091
40489
  error(msg);
@@ -40132,13 +40530,13 @@ var init_hint = __esm({
40132
40530
  });
40133
40531
 
40134
40532
  // ../../packages/tostudy-core/src/exercises/validate-solution.ts
40135
- async function validateSolution(input, deps) {
40136
- deps.logger.info("tostudy-core: validateSolution", { lessonId: input.lessonId });
40533
+ async function validateSolution(input2, deps) {
40534
+ deps.logger.info("tostudy-core: validateSolution", { lessonId: input2.lessonId });
40137
40535
  return deps.data.exercises.validate(
40138
- input.lessonId,
40139
- input.solution,
40140
- input.userId,
40141
- input.enrollmentId
40536
+ input2.lessonId,
40537
+ input2.solution,
40538
+ input2.userId,
40539
+ input2.enrollmentId
40142
40540
  );
40143
40541
  }
40144
40542
  var init_validate_solution = __esm({
@@ -40158,134 +40556,26 @@ var init_exercises = __esm({
40158
40556
  }
40159
40557
  });
40160
40558
 
40161
- // src/commands/validate.ts
40162
- import fs6 from "node:fs";
40163
- import { Command as Command13 } from "commander";
40164
- var logger10, validateCommand;
40165
- var init_validate = __esm({
40166
- "src/commands/validate.ts"() {
40167
- "use strict";
40168
- init_src();
40169
- init_exercises();
40170
- init_http2();
40171
- init_session();
40172
- init_formatter();
40173
- logger10 = createLogger("cli:validate");
40174
- 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 (file2, opts) => {
40175
- try {
40176
- const session = await requireSession();
40177
- const activeCourse = await requireActiveCourse();
40178
- const driftWarning = await checkCourseDrift();
40179
- if (driftWarning) process.stderr.write(driftWarning + "\n");
40180
- const lessonId = activeCourse.currentLessonId;
40181
- if (!lessonId) {
40182
- error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
40183
- }
40184
- let solution;
40185
- if (opts.stdin) {
40186
- solution = fs6.readFileSync("/dev/stdin", "utf-8");
40187
- } else if (file2) {
40188
- if (!fs6.existsSync(file2)) {
40189
- error(`Arquivo n\xE3o encontrado: ${file2}`);
40190
- }
40191
- solution = fs6.readFileSync(file2, "utf-8");
40192
- } else {
40193
- error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
40194
- }
40195
- const data = createHttpProvider(session.apiUrl, session.token);
40196
- const deps = { data, logger: logger10 };
40197
- const result = await validateSolution(
40198
- {
40199
- lessonId,
40200
- solution,
40201
- userId: session.userId,
40202
- enrollmentId: activeCourse.enrollmentId
40203
- },
40204
- deps
40205
- );
40206
- if (opts.json) {
40207
- output(result, { json: true });
40208
- } else {
40209
- output(formatValidation(result), { json: false });
40210
- }
40211
- process.exit(result.passed ? 0 : 1);
40212
- } catch (err) {
40213
- const msg = err instanceof Error ? err.message : String(err);
40214
- error(msg);
40215
- }
40216
- });
40217
- }
40218
- });
40219
-
40220
- // src/commands/menu.ts
40221
- import { Command as Command14 } from "commander";
40222
- var menuCommand;
40223
- var init_menu = __esm({
40224
- "src/commands/menu.ts"() {
40225
- "use strict";
40226
- init_session();
40227
- init_formatter();
40228
- menuCommand = new Command14("menu").description("Show available commands and current study context").action(async () => {
40229
- const session = await getSession();
40230
- const activeCourse = session ? await getActiveCourse() : null;
40231
- const lines = [
40232
- "\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",
40233
- "\u2502 ToStudy CLI \u2014 Menu \u2502",
40234
- "\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",
40235
- ""
40236
- ];
40237
- if (!session) {
40238
- lines.push(
40239
- " Status: N\xE3o autenticado",
40240
- "",
40241
- " Para come\xE7ar:",
40242
- " tostudy login Autenticar com sua conta ToStudy",
40243
- ""
40244
- );
40245
- } else {
40246
- lines.push(` Usu\xE1rio: ${session.userName}`);
40247
- if (activeCourse) {
40248
- lines.push(` Curso ativo: ${activeCourse.courseTitle}`, "");
40249
- } else {
40250
- lines.push(
40251
- " Curso ativo: (nenhum)",
40252
- " \u2192 tostudy courses para ver seus cursos",
40253
- " \u2192 tostudy select <n\xFAmero> para ativar um curso",
40254
- ""
40255
- );
40256
- }
40257
- lines.push(
40258
- " \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",
40259
- " tostudy courses Listar seus cursos",
40260
- " tostudy select <id> Ativar um curso",
40261
- " tostudy progress Ver progresso do curso ativo",
40262
- "",
40263
- " \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",
40264
- " tostudy start Iniciar/retomar o m\xF3dulo atual",
40265
- " tostudy start-next Avan\xE7ar para o pr\xF3ximo m\xF3dulo",
40266
- " tostudy next Avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
40267
- " tostudy lesson Ver conte\xFAdo da li\xE7\xE3o atual",
40268
- "",
40269
- " \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",
40270
- " tostudy validate <arquivo> Validar sua solu\xE7\xE3o",
40271
- " tostudy hint Obter uma dica progressiva",
40272
- "",
40273
- " \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",
40274
- " Adicione --json a qualquer comando para sa\xEDda em JSON",
40275
- " Adicione --help a qualquer comando para ver op\xE7\xF5es",
40276
- ""
40277
- );
40278
- }
40279
- output(lines.join("\n"), { json: false });
40280
- });
40281
- }
40282
- });
40283
-
40284
40559
  // src/output/init-template.ts
40560
+ function detectTechFromTags(tags) {
40561
+ const found = /* @__PURE__ */ new Set();
40562
+ const keywords = Object.keys(LANGUAGE_TAGS);
40563
+ const joined = tags.map((t) => t.toLowerCase()).join(" ");
40564
+ for (const kw of keywords) {
40565
+ const pattern = new RegExp(`\\b${kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
40566
+ if (pattern.test(joined)) {
40567
+ found.add(kw);
40568
+ }
40569
+ }
40570
+ return [...found];
40571
+ }
40285
40572
  function formatCourseLanguage(tags) {
40286
40573
  if (!tags || tags.length === 0) return "Programa\xE7\xE3o";
40287
- const matched = tags.map((t) => LANGUAGE_TAGS[t.toLowerCase()]).filter(Boolean);
40288
- return matched.length > 0 ? matched.slice(0, 3).join(" / ") : "Programa\xE7\xE3o";
40574
+ const direct = tags.map((t) => LANGUAGE_TAGS[t.toLowerCase()]).filter(Boolean);
40575
+ if (direct.length > 0) return direct.slice(0, 3).join(" / ");
40576
+ const detected = detectTechFromTags(tags);
40577
+ const mapped = detected.map((t) => LANGUAGE_TAGS[t]).filter(Boolean);
40578
+ return mapped.length > 0 ? mapped.slice(0, 3).join(" / ") : "Programa\xE7\xE3o";
40289
40579
  }
40290
40580
  function formatCourseLevel(level) {
40291
40581
  const map2 = {
@@ -40372,6 +40662,109 @@ function buildInitTemplate(userName, course, progress3) {
40372
40662
  lines.push("- Instru\xE7\xF5es do ToStudy \u2192 fluxo de estudo (navega\xE7\xE3o, exerc\xEDcios, valida\xE7\xE3o)");
40373
40663
  return lines.join("\n");
40374
40664
  }
40665
+ function resolveTutorMode(level) {
40666
+ if (level === "beginner") return "guided";
40667
+ if (level === "advanced") return "direct";
40668
+ return "balanced";
40669
+ }
40670
+ function formatTutorMode(mode) {
40671
+ const map2 = {
40672
+ guided: "Guiado",
40673
+ balanced: "Equilibrado",
40674
+ direct: "Direto"
40675
+ };
40676
+ return map2[mode];
40677
+ }
40678
+ function buildTutorPersonalizationSection(course, learnerProfile) {
40679
+ const mode = resolveTutorMode(course.level);
40680
+ const lines = [];
40681
+ lines.push("## 6. Personaliza\xE7\xE3o do Tutor");
40682
+ lines.push(`- Modo inicial do tutor: ${formatTutorMode(mode)}`);
40683
+ lines.push(
40684
+ "- Na primeira intera\xE7\xE3o, apresente a escolha entre cen\xE1rio fict\xEDcio do curso e adapta\xE7\xE3o ao contexto real do aluno."
40685
+ );
40686
+ lines.push(
40687
+ `- O aluno atual j\xE1 optou por: ${learnerProfile.adaptToRealContext ? "Adaptar para o contexto real" : "Manter o cen\xE1rio fict\xEDcio"}`
40688
+ );
40689
+ lines.push(`- Segmento: ${learnerProfile.segment}`);
40690
+ lines.push(`- Empresa: ${learnerProfile.company}`);
40691
+ lines.push(`- Produtos/servi\xE7os: ${learnerProfile.productsOrServices}`);
40692
+ lines.push(`- Regi\xE3o: ${learnerProfile.region}`);
40693
+ lines.push(`- Equipe: ${learnerProfile.team}`);
40694
+ lines.push(`- Objetivo: ${learnerProfile.goal}`);
40695
+ lines.push(`- N\xEDvel declarado do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
40696
+ if (learnerProfile.adaptToRealContext) {
40697
+ lines.push(
40698
+ "- Use o contexto do aluno como padr\xE3o para explica\xE7\xF5es, exemplos, exerc\xEDcios e framing."
40699
+ );
40700
+ lines.push(
40701
+ "- Troque nomes, entreg\xE1veis e restri\xE7\xF5es do cen\xE1rio fict\xEDcio por equivalentes reais."
40702
+ );
40703
+ lines.push(
40704
+ "- Preserve objetivos pedag\xF3gicos, crit\xE9rios de valida\xE7\xE3o e progress\xE3o de dificuldade."
40705
+ );
40706
+ } else {
40707
+ lines.push(
40708
+ "- Preserve o cen\xE1rio fict\xEDcio como padr\xE3o e conecte os conceitos ao neg\xF3cio real quando isso ajudar."
40709
+ );
40710
+ lines.push(
40711
+ "- Se o aluno quiser migrar para o contexto real depois, oriente a rodar `tostudy init` novamente."
40712
+ );
40713
+ }
40714
+ if (mode === "guided") {
40715
+ lines.push(
40716
+ "- No modo guiado, explique o porqu\xEA de cada passo, proponha checkpoints curtos e confirme entendimento antes de avan\xE7ar."
40717
+ );
40718
+ } else if (mode === "direct") {
40719
+ lines.push(
40720
+ "- Mesmo no modo mais direto, continue sem dar a resposta pronta e preserve a progress\xE3o pedag\xF3gica."
40721
+ );
40722
+ } else {
40723
+ lines.push(
40724
+ "- No modo equilibrado, combine explica\xE7\xF5es curtas com autonomia entre checkpoints."
40725
+ );
40726
+ }
40727
+ return lines.join("\n");
40728
+ }
40729
+ function buildLearnerBrief(userName, course, learnerProfile) {
40730
+ const lines = [];
40731
+ lines.push("# ToStudy CLI \u2014 Brief do Aluno");
40732
+ lines.push("");
40733
+ lines.push("Este documento resume o contexto real do aluno para orientar exemplos e exercicios.");
40734
+ lines.push("");
40735
+ lines.push("## 1. Identificacao");
40736
+ lines.push(`- Aluno: ${userName}`);
40737
+ lines.push(`- Curso: "${course.title}"`);
40738
+ lines.push(`- Segmento: ${learnerProfile.segment}`);
40739
+ lines.push(`- Empresa: ${learnerProfile.company}`);
40740
+ lines.push(`- Produtos/servicos: ${learnerProfile.productsOrServices}`);
40741
+ lines.push(`- Regiao: ${learnerProfile.region}`);
40742
+ lines.push(`- Equipe: ${learnerProfile.team}`);
40743
+ lines.push("");
40744
+ lines.push("## 2. Objetivo e Contexto");
40745
+ lines.push(`- Objetivo principal: ${learnerProfile.goal}`);
40746
+ lines.push(`- Nivel do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
40747
+ lines.push(
40748
+ `- Adaptar curso ao contexto real: ${learnerProfile.adaptToRealContext ? "Sim" : "Nao"}`
40749
+ );
40750
+ lines.push("");
40751
+ lines.push("## 3. Como usar este brief");
40752
+ lines.push("- Reutilize este contexto para adaptar exemplos, cenarios e exercicios.");
40753
+ lines.push("- Preserve a proposta pedagogica do curso antes de customizar o contexto.");
40754
+ lines.push(
40755
+ "- Se algo mudar no negocio do aluno, rode `tostudy init` novamente para atualizar o brief."
40756
+ );
40757
+ return lines.join("\n");
40758
+ }
40759
+ function buildInitArtifacts(userName, course, progress3, learnerProfile) {
40760
+ return {
40761
+ tutorInstructions: [
40762
+ buildInitTemplate(userName, course, progress3),
40763
+ buildTutorPersonalizationSection(course, learnerProfile)
40764
+ ].join("\n\n"),
40765
+ learnerBrief: buildLearnerBrief(userName, course, learnerProfile)
40766
+ };
40767
+ }
40375
40768
  var LANGUAGE_TAGS;
40376
40769
  var init_init_template = __esm({
40377
40770
  "src/output/init-template.ts"() {
@@ -40405,104 +40798,489 @@ var init_init_template = __esm({
40405
40798
  }
40406
40799
  });
40407
40800
 
40408
- // src/commands/init.ts
40409
- import { Command as Command15 } from "commander";
40410
- var logger11, initCommand;
40411
- var init_init = __esm({
40412
- "src/commands/init.ts"() {
40801
+ // src/commands/validate.ts
40802
+ import fs8 from "node:fs";
40803
+ import path7 from "node:path";
40804
+ import { Command as Command13 } from "commander";
40805
+ var logger10, validateCommand;
40806
+ var init_validate = __esm({
40807
+ "src/commands/validate.ts"() {
40413
40808
  "use strict";
40414
40809
  init_src();
40415
- init_courses();
40810
+ init_exercises();
40416
40811
  init_http2();
40417
40812
  init_session();
40418
40813
  init_formatter();
40419
40814
  init_init_template();
40420
- logger11 = createLogger("cli:init");
40421
- initCommand = new Command15("init").description("Generate LLM tutor instructions for the active course").action(async () => {
40815
+ logger10 = createLogger("cli:validate");
40816
+ 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 (file2, opts) => {
40817
+ try {
40818
+ const session = await requireSession();
40819
+ const activeCourse = await requireActiveCourse();
40820
+ const driftWarning = await checkCourseDrift();
40821
+ if (driftWarning) process.stderr.write(driftWarning + "\n");
40822
+ const lessonId = activeCourse.currentLessonId;
40823
+ if (!lessonId) {
40824
+ error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
40825
+ }
40826
+ let solution;
40827
+ if (opts.stdin) {
40828
+ solution = fs8.readFileSync("/dev/stdin", "utf-8");
40829
+ } else if (file2) {
40830
+ if (!fs8.existsSync(file2)) {
40831
+ error(`Arquivo n\xE3o encontrado: ${file2}`);
40832
+ }
40833
+ solution = fs8.readFileSync(file2, "utf-8");
40834
+ } else {
40835
+ error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
40836
+ }
40837
+ if (file2 && activeCourse.courseTags?.length) {
40838
+ const ext = path7.extname(file2).toLowerCase();
40839
+ const LANG_EXTENSIONS = {
40840
+ ".html": ["html", "html5"],
40841
+ ".css": ["css"],
40842
+ ".js": ["javascript", "node", "nodejs", "react", "vue", "angular", "svelte", "nextjs"],
40843
+ ".ts": ["typescript", "react", "angular", "nextjs"],
40844
+ ".tsx": ["typescript", "react", "nextjs"],
40845
+ ".jsx": ["javascript", "react"],
40846
+ ".py": ["python"],
40847
+ ".go": ["go"],
40848
+ ".rs": ["rust"],
40849
+ ".java": ["java"],
40850
+ ".kt": ["kotlin"],
40851
+ ".swift": ["swift"],
40852
+ ".rb": ["ruby"],
40853
+ ".php": ["php"],
40854
+ ".cs": ["csharp", "c#"],
40855
+ ".cpp": ["cpp"],
40856
+ ".sql": ["sql"],
40857
+ ".vue": ["vue"]
40858
+ };
40859
+ const expectedTags = LANG_EXTENSIONS[ext];
40860
+ if (expectedTags) {
40861
+ const courseTech = detectTechFromTags(activeCourse.courseTags);
40862
+ const matches = expectedTags.some((t) => courseTech.includes(t));
40863
+ if (!matches) {
40864
+ const lang = formatCourseLanguage(activeCourse.courseTags);
40865
+ process.stderr.write(
40866
+ `\u26A0\uFE0F Arquivo ${ext} \u2014 este curso \xE9 de ${lang}. Verifique se est\xE1 enviando o arquivo correto.
40867
+
40868
+ `
40869
+ );
40870
+ }
40871
+ }
40872
+ }
40873
+ const data = createHttpProvider(session.apiUrl, session.token);
40874
+ const deps = { data, logger: logger10 };
40875
+ const result = await validateSolution(
40876
+ {
40877
+ lessonId,
40878
+ solution,
40879
+ userId: session.userId,
40880
+ enrollmentId: activeCourse.enrollmentId
40881
+ },
40882
+ deps
40883
+ );
40884
+ if (opts.json) {
40885
+ output(result, { json: true });
40886
+ } else {
40887
+ output(formatValidation(result), { json: false });
40888
+ }
40889
+ process.exit(result.passed ? 0 : 1);
40890
+ } catch (err) {
40891
+ const msg = err instanceof Error ? err.message : String(err);
40892
+ error(msg);
40893
+ }
40894
+ });
40895
+ }
40896
+ });
40897
+
40898
+ // src/commands/menu.ts
40899
+ import { Command as Command14 } from "commander";
40900
+ var menuCommand;
40901
+ var init_menu = __esm({
40902
+ "src/commands/menu.ts"() {
40903
+ "use strict";
40904
+ init_session();
40905
+ init_formatter();
40906
+ menuCommand = new Command14("menu").description("Show available commands and current study context").action(async () => {
40422
40907
  const session = await getSession();
40908
+ const activeCourse = session ? await getActiveCourse() : null;
40909
+ const lines = [
40910
+ "\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",
40911
+ "\u2502 ToStudy CLI \u2014 Menu \u2502",
40912
+ "\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",
40913
+ ""
40914
+ ];
40423
40915
  if (!session) {
40424
- output("N\xE3o autenticado. Rode `tostudy login` para come\xE7ar.", { json: false });
40425
- return;
40916
+ lines.push(
40917
+ " Status: N\xE3o autenticado",
40918
+ "",
40919
+ " Para come\xE7ar:",
40920
+ " tostudy login Autenticar com sua conta ToStudy",
40921
+ ""
40922
+ );
40923
+ } else {
40924
+ lines.push(` Usu\xE1rio: ${session.userName}`);
40925
+ if (activeCourse) {
40926
+ lines.push(` Curso ativo: ${activeCourse.courseTitle}`, "");
40927
+ } else {
40928
+ lines.push(
40929
+ " Curso ativo: (nenhum)",
40930
+ " \u2192 tostudy courses para ver seus cursos",
40931
+ " \u2192 tostudy select <n\xFAmero> para ativar um curso",
40932
+ ""
40933
+ );
40934
+ }
40935
+ lines.push(
40936
+ " \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",
40937
+ " tostudy courses Listar seus cursos",
40938
+ " tostudy select <id> Ativar um curso",
40939
+ " tostudy progress Ver progresso do curso ativo",
40940
+ "",
40941
+ " \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",
40942
+ " tostudy start Iniciar/retomar o m\xF3dulo atual",
40943
+ " tostudy start-next Avan\xE7ar para o pr\xF3ximo m\xF3dulo",
40944
+ " tostudy next Avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
40945
+ " tostudy lesson Ver conte\xFAdo da li\xE7\xE3o atual",
40946
+ "",
40947
+ " \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",
40948
+ " tostudy validate <arquivo> Validar sua solu\xE7\xE3o",
40949
+ " tostudy hint Obter uma dica progressiva",
40950
+ "",
40951
+ " \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",
40952
+ " Adicione --json a qualquer comando para sa\xEDda em JSON",
40953
+ " Adicione --help a qualquer comando para ver op\xE7\xF5es",
40954
+ ""
40955
+ );
40426
40956
  }
40427
- const data = createHttpProvider(session.apiUrl, session.token);
40428
- const deps = { data, logger: logger11 };
40429
- const activeCourse = await getActiveCourse();
40430
- if (!activeCourse) {
40431
- try {
40432
- const courses3 = await listCourses({ userId: session.userId }, deps);
40433
- const courseListStr = courses3.length > 0 ? courses3.map((c, i) => ` ${i + 1}. ${c.title} (${c.progress}%)`).join("\n") : " (nenhum curso encontrado)";
40434
- output(
40435
- `Nenhum curso ativo.
40957
+ output(lines.join("\n"), { json: false });
40958
+ });
40959
+ }
40960
+ });
40961
+
40962
+ // src/onboarding/learner-context.ts
40963
+ import readline from "node:readline/promises";
40964
+ import { stdin as input, stdout as output2 } from "node:process";
40965
+ async function askNonEmpty(question, deps, defaultValue) {
40966
+ while (true) {
40967
+ const answer = (await deps.ask(question)).trim();
40968
+ if (answer.length > 0) return answer;
40969
+ if (defaultValue) return defaultValue;
40970
+ }
40971
+ }
40972
+ async function askChoice(question, choices, deps, defaultChoice) {
40973
+ while (true) {
40974
+ const rawAnswer = (await deps.ask(question)).trim().toLowerCase();
40975
+ if (rawAnswer.length === 0 && defaultChoice) return defaultChoice;
40976
+ const answer = rawAnswer;
40977
+ if (choices.includes(answer)) return answer;
40978
+ }
40979
+ }
40980
+ function createPromptDeps() {
40981
+ const rl = readline.createInterface({ input, output: output2 });
40982
+ return {
40983
+ ask: (question) => rl.question(question),
40984
+ write: (content) => output2.write(content),
40985
+ close: () => rl.close()
40986
+ };
40987
+ }
40988
+ async function collectLearnerContextProfileWithDeps(input2, deps) {
40989
+ const existingProfile = input2.existingProfile;
40990
+ if (existingProfile) {
40991
+ deps.write(
40992
+ [
40993
+ "",
40994
+ `Brief existente encontrado (${input2.existingSource === "web" ? "web" : "CLI"}):`,
40995
+ `- Segmento: ${existingProfile.segment}`,
40996
+ `- Empresa: ${existingProfile.company}`,
40997
+ `- Produtos/servicos: ${existingProfile.productsOrServices}`,
40998
+ `- Regiao: ${existingProfile.region}`,
40999
+ `- Equipe: ${existingProfile.team}`,
41000
+ `- Objetivo: ${existingProfile.goal}`,
41001
+ `- Nivel: ${existingProfile.learnerLevel}`,
41002
+ `- Contexto real: ${existingProfile.adaptToRealContext ? "sim" : "nao"}`,
41003
+ ""
41004
+ ].join("\n")
41005
+ );
41006
+ }
41007
+ const action = existingProfile ? await askChoice(
41008
+ "Manter este brief ou editar? (keep/edit): ",
41009
+ ["keep", "edit"],
41010
+ deps,
41011
+ "keep"
41012
+ ) : "edit";
41013
+ const baseProfile = existingProfile;
41014
+ const segment = action === "keep" && baseProfile ? baseProfile.segment : await askNonEmpty(
41015
+ baseProfile ? `Segmento ou nicho do aluno [${baseProfile.segment}]: ` : "Segmento ou nicho do aluno: ",
41016
+ deps,
41017
+ baseProfile?.segment
41018
+ );
41019
+ const company = action === "keep" && baseProfile ? baseProfile.company : await askNonEmpty(
41020
+ baseProfile ? `Empresa ou tipo de negocio [${baseProfile.company}]: ` : "Empresa ou tipo de negocio: ",
41021
+ deps,
41022
+ baseProfile?.company
41023
+ );
41024
+ const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await askNonEmpty(
41025
+ baseProfile ? `Produtos ou servicos principais [${baseProfile.productsOrServices}]: ` : "Produtos ou servicos principais: ",
41026
+ deps,
41027
+ baseProfile?.productsOrServices
41028
+ );
41029
+ const region = action === "keep" && baseProfile ? baseProfile.region : await askNonEmpty(
41030
+ baseProfile ? `Regiao de atuacao [${baseProfile.region}]: ` : "Regiao de atuacao: ",
41031
+ deps,
41032
+ baseProfile?.region
41033
+ );
41034
+ const team = action === "keep" && baseProfile ? baseProfile.team : await askNonEmpty(
41035
+ baseProfile ? `Equipe envolvida neste contexto [${baseProfile.team}]: ` : "Equipe envolvida neste contexto: ",
41036
+ deps,
41037
+ baseProfile?.team
41038
+ );
41039
+ const goal = action === "keep" && baseProfile ? baseProfile.goal : await askNonEmpty(
41040
+ baseProfile ? `Objetivo principal com este curso [${baseProfile.goal}]: ` : "Objetivo principal com este curso: ",
41041
+ deps,
41042
+ baseProfile?.goal
41043
+ );
41044
+ const learnerLevel = action === "keep" && baseProfile ? baseProfile.learnerLevel : await askChoice(
41045
+ baseProfile ? `Nivel do aluno (beginner/intermediate/advanced) [${baseProfile.learnerLevel}]: ` : "Nivel do aluno (beginner/intermediate/advanced): ",
41046
+ ["beginner", "intermediate", "advanced"],
41047
+ deps,
41048
+ baseProfile?.learnerLevel
41049
+ );
41050
+ const adaptToRealContext = await askChoice(
41051
+ baseProfile ? `Quer que o tutor adapte exemplos ao seu contexto real ou prefere usar os cenarios ficticios do curso? (yes/no) [${baseProfile.adaptToRealContext ? "yes" : "no"}]: ` : "Quer que o tutor adapte exemplos ao seu contexto real ou prefere usar os cenarios ficticios do curso? (yes/no): ",
41052
+ ["yes", "no"],
41053
+ deps,
41054
+ baseProfile ? baseProfile.adaptToRealContext ? "yes" : "no" : void 0
41055
+ ) === "yes";
41056
+ return {
41057
+ segment,
41058
+ company,
41059
+ productsOrServices,
41060
+ region,
41061
+ team,
41062
+ goal,
41063
+ learnerLevel,
41064
+ adaptToRealContext
41065
+ };
41066
+ }
41067
+ async function collectLearnerContextProfile(input2) {
41068
+ const deps = createPromptDeps();
41069
+ try {
41070
+ return await collectLearnerContextProfileWithDeps(input2, deps);
41071
+ } finally {
41072
+ deps.close();
41073
+ }
41074
+ }
41075
+ var init_learner_context = __esm({
41076
+ "src/onboarding/learner-context.ts"() {
41077
+ "use strict";
41078
+ }
41079
+ });
41080
+
41081
+ // src/onboarding/api.ts
41082
+ async function apiFetch2(url2, token2, init) {
41083
+ const response = await fetch(url2, {
41084
+ ...init,
41085
+ headers: {
41086
+ "Content-Type": "application/json",
41087
+ Authorization: `Bearer ${token2}`,
41088
+ ...init?.headers
41089
+ }
41090
+ });
41091
+ const body = await response.json();
41092
+ if (!response.ok) {
41093
+ throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
41094
+ }
41095
+ return body;
41096
+ }
41097
+ async function getRemoteEnrollmentOnboarding(input2) {
41098
+ const url2 = new URL(`${input2.apiUrl}/api/cli/onboarding`);
41099
+ url2.searchParams.set("enrollmentId", input2.enrollmentId);
41100
+ const response = await apiFetch2(
41101
+ url2.toString(),
41102
+ input2.token
41103
+ );
41104
+ return response.onboarding;
41105
+ }
41106
+ async function saveRemoteEnrollmentOnboarding(input2) {
41107
+ const response = await apiFetch2(
41108
+ `${input2.apiUrl}/api/cli/onboarding`,
41109
+ input2.token,
41110
+ {
41111
+ method: "POST",
41112
+ body: JSON.stringify({
41113
+ enrollmentId: input2.enrollmentId,
41114
+ learnerBrief: input2.learnerBrief,
41115
+ learnerProfile: input2.learnerProfile
41116
+ })
41117
+ }
41118
+ );
41119
+ return response.onboarding;
41120
+ }
41121
+ var init_api2 = __esm({
41122
+ "src/onboarding/api.ts"() {
41123
+ "use strict";
41124
+ init_http2();
41125
+ }
41126
+ });
41127
+
41128
+ // src/commands/init.ts
41129
+ import { Command as Command15 } from "commander";
41130
+ async function runInit(deps = defaultDeps3) {
41131
+ const session = await deps.getSession();
41132
+ if (!session) {
41133
+ deps.output("Nao autenticado. Rode `tostudy login` para comecar.", { json: false });
41134
+ return;
41135
+ }
41136
+ const data = deps.createHttpProvider(session.apiUrl, session.token);
41137
+ const apiDeps = { data, logger: deps.logger };
41138
+ const activeCourse = await deps.getActiveCourse();
41139
+ if (!activeCourse) {
41140
+ try {
41141
+ const courses3 = await deps.listCourses({ userId: session.userId }, apiDeps);
41142
+ const courseListStr = courses3.length > 0 ? courses3.map((c, i) => ` ${i + 1}. ${c.title} (${c.progress}%)`).join("\n") : " (nenhum curso encontrado)";
41143
+ deps.output(
41144
+ `Nenhum curso ativo.
40436
41145
 
40437
41146
  Seus cursos:
40438
41147
  ${courseListStr}
40439
41148
 
40440
41149
  Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
40441
- { json: false }
40442
- );
40443
- } catch {
40444
- output("Nenhum curso ativo. Rode `tostudy courses` e `tostudy select <n\xFAmero>`.", {
40445
- json: false
40446
- });
40447
- }
40448
- return;
40449
- }
40450
- let matchedCourse = null;
40451
- try {
40452
- const courses3 = await listCourses({ userId: session.userId }, deps);
40453
- matchedCourse = courses3.find((c) => c.courseId === activeCourse.courseId) ?? null;
40454
- } catch (err) {
40455
- logger11.warn("Failed to fetch course metadata for init", { error: err });
40456
- }
40457
- if (!matchedCourse) {
40458
- matchedCourse = {
40459
- enrollmentId: activeCourse.enrollmentId,
40460
- courseId: activeCourse.courseId,
40461
- title: activeCourse.courseTitle,
40462
- progress: 0,
40463
- creatorName: "Desconhecido",
40464
- teachingApproach: "hybrid",
40465
- enrolledAt: /* @__PURE__ */ new Date()
40466
- };
40467
- }
40468
- let progressData = null;
40469
- try {
40470
- progressData = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
40471
- } catch (err) {
40472
- logger11.warn("Failed to fetch progress for init", { error: err });
40473
- }
40474
- try {
40475
- await setLastInitCourseId(activeCourse.courseId);
40476
- } catch (err) {
40477
- logger11.warn("Failed to save lastInitCourseId", { error: err });
40478
- }
40479
- const template = buildInitTemplate(session.userName, matchedCourse, progressData);
40480
- output(template, { json: false });
41150
+ { json: false }
41151
+ );
41152
+ } catch {
41153
+ deps.output("Nenhum curso ativo. Rode `tostudy courses` e `tostudy select <n\xFAmero>`.", {
41154
+ json: false
41155
+ });
41156
+ }
41157
+ return;
41158
+ }
41159
+ let matchedCourse = null;
41160
+ try {
41161
+ const courses3 = await deps.listCourses({ userId: session.userId }, apiDeps);
41162
+ matchedCourse = courses3.find((course) => course.courseId === activeCourse.courseId) ?? null;
41163
+ } catch (err) {
41164
+ deps.logger.warn("Failed to fetch course metadata for init", { error: err });
41165
+ }
41166
+ if (!matchedCourse) {
41167
+ matchedCourse = {
41168
+ enrollmentId: activeCourse.enrollmentId,
41169
+ courseId: activeCourse.courseId,
41170
+ title: activeCourse.courseTitle,
41171
+ progress: 0,
41172
+ creatorName: "Desconhecido",
41173
+ teachingApproach: "hybrid",
41174
+ enrolledAt: /* @__PURE__ */ new Date()
41175
+ };
41176
+ }
41177
+ let progressData = null;
41178
+ try {
41179
+ progressData = await deps.getProgress({ enrollmentId: activeCourse.enrollmentId }, apiDeps);
41180
+ } catch (err) {
41181
+ deps.logger.warn("Failed to fetch progress for init", { error: err });
41182
+ }
41183
+ let remoteOnboarding = null;
41184
+ try {
41185
+ remoteOnboarding = await deps.getRemoteEnrollmentOnboarding({
41186
+ apiUrl: session.apiUrl,
41187
+ token: session.token,
41188
+ enrollmentId: activeCourse.enrollmentId
41189
+ });
41190
+ } catch (err) {
41191
+ deps.logger.warn("Failed to fetch remote enrollment onboarding", { error: err });
41192
+ }
41193
+ const onboardingState = await deps.getCourseOnboardingState(activeCourse.courseId);
41194
+ const existingProfile = remoteOnboarding?.learnerProfile ?? onboardingState?.learnerProfile;
41195
+ const existingSource = remoteOnboarding?.source ?? (onboardingState?.learnerProfile ? "cli" : void 0);
41196
+ const learnerProfile = await deps.collectLearnerContextProfile({
41197
+ userName: session.userName,
41198
+ course: matchedCourse,
41199
+ activeCourse,
41200
+ ...existingProfile ? {
41201
+ existingProfile,
41202
+ existingSource
41203
+ } : {}
41204
+ });
41205
+ const artifacts = deps.buildInitArtifacts(
41206
+ session.userName,
41207
+ matchedCourse,
41208
+ progressData,
41209
+ learnerProfile
41210
+ );
41211
+ await deps.saveRemoteEnrollmentOnboarding({
41212
+ apiUrl: session.apiUrl,
41213
+ token: session.token,
41214
+ enrollmentId: activeCourse.enrollmentId,
41215
+ learnerBrief: artifacts.learnerBrief,
41216
+ learnerProfile,
41217
+ source: "cli"
41218
+ });
41219
+ await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
41220
+ try {
41221
+ await deps.setLastInitCourseId(activeCourse.courseId);
41222
+ } catch (err) {
41223
+ deps.logger.warn("Failed to save lastInitCourseId", { error: err });
41224
+ }
41225
+ deps.output(artifacts.tutorInstructions, { json: false });
41226
+ deps.output(artifacts.learnerBrief, { json: false });
41227
+ }
41228
+ var logger11, defaultDeps3, initCommand;
41229
+ var init_init = __esm({
41230
+ "src/commands/init.ts"() {
41231
+ "use strict";
41232
+ init_src();
41233
+ init_courses();
41234
+ init_http2();
41235
+ init_session();
41236
+ init_formatter();
41237
+ init_init_template();
41238
+ init_learner_context();
41239
+ init_api2();
41240
+ logger11 = createLogger("cli:init");
41241
+ defaultDeps3 = {
41242
+ getSession,
41243
+ getActiveCourse,
41244
+ listCourses,
41245
+ getProgress,
41246
+ getRemoteEnrollmentOnboarding,
41247
+ saveRemoteEnrollmentOnboarding,
41248
+ setLastInitCourseId,
41249
+ getCourseOnboardingState,
41250
+ collectLearnerContextProfile,
41251
+ saveCourseLearnerProfile,
41252
+ buildInitArtifacts,
41253
+ output,
41254
+ logger: logger11,
41255
+ createHttpProvider
41256
+ };
41257
+ initCommand = new Command15("init").description("Generate tutor instructions and learner brief for the active course").action(async () => {
41258
+ await runInit();
40481
41259
  });
40482
41260
  }
40483
41261
  });
40484
41262
 
40485
41263
  // ../../packages/tostudy-core/src/workspace/setup-workspace.ts
40486
- import fs7 from "node:fs/promises";
40487
- import path5 from "node:path";
40488
- async function setupWorkspace(input) {
40489
- const workspacePath = path5.join(input.basePath, input.courseSlug);
41264
+ import fs9 from "node:fs/promises";
41265
+ import path8 from "node:path";
41266
+ async function setupWorkspace(input2) {
41267
+ const workspacePath = path8.join(input2.basePath, input2.courseSlug);
40490
41268
  for (const dir of WORKSPACE_DIRS) {
40491
- await fs7.mkdir(path5.join(workspacePath, dir), { recursive: true });
41269
+ await fs9.mkdir(path8.join(workspacePath, dir), { recursive: true });
40492
41270
  }
40493
- const configPath = path5.join(workspacePath, ".ana-config.json");
41271
+ const configPath = path8.join(workspacePath, ".ana-config.json");
40494
41272
  const config2 = {
40495
- courseId: input.courseId,
40496
- courseSlug: input.courseSlug,
40497
- courseName: input.courseName,
41273
+ courseId: input2.courseId,
41274
+ courseSlug: input2.courseSlug,
41275
+ courseName: input2.courseName,
40498
41276
  workspacePath,
40499
- locale: input.locale,
41277
+ locale: input2.locale,
40500
41278
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
40501
41279
  lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
40502
41280
  };
40503
- await fs7.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
41281
+ await fs9.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
40504
41282
  const readme = [
40505
- `# ${input.courseName}`,
41283
+ `# ${input2.courseName}`,
40506
41284
  "",
40507
41285
  "Workspace de estudo gerado pelo ToStudy.",
40508
41286
  "",
@@ -40525,7 +41303,7 @@ async function setupWorkspace(input) {
40525
41303
  "tostudy vault sync # Sincronizar progresso",
40526
41304
  "```"
40527
41305
  ].join("\n");
40528
- await fs7.writeFile(path5.join(workspacePath, "README.md"), readme, "utf-8");
41306
+ await fs9.writeFile(path8.join(workspacePath, "README.md"), readme, "utf-8");
40529
41307
  return { workspacePath, directories: WORKSPACE_DIRS, configPath };
40530
41308
  }
40531
41309
  var WORKSPACE_DIRS;
@@ -40626,8 +41404,8 @@ var init_templates = __esm({
40626
41404
  });
40627
41405
 
40628
41406
  // ../../packages/tostudy-core/src/workspace/extract-exercise.ts
40629
- import fs8 from "node:fs/promises";
40630
- import path6 from "node:path";
41407
+ import fs10 from "node:fs/promises";
41408
+ import path9 from "node:path";
40631
41409
  function padOrder(n) {
40632
41410
  return String(n).padStart(2, "0");
40633
41411
  }
@@ -40647,20 +41425,20 @@ function getStarterCode(structuredData) {
40647
41425
  const data = sd.data;
40648
41426
  return data?.starterCode ?? null;
40649
41427
  }
40650
- async function extractExercise(input) {
40651
- const { lessonData, exerciseTier, workspacePath } = input;
41428
+ async function extractExercise(input2) {
41429
+ const { lessonData, exerciseTier, workspacePath } = input2;
40652
41430
  const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
40653
41431
  const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
40654
- const exercisePath = path6.join(workspacePath, "exercises", moduleDir, lessonDir);
40655
- await fs8.mkdir(exercisePath, { recursive: true });
41432
+ const exercisePath = path9.join(workspacePath, "exercises", moduleDir, lessonDir);
41433
+ await fs10.mkdir(exercisePath, { recursive: true });
40656
41434
  const extractedFiles = [];
40657
41435
  let hasStarterCode = false;
40658
41436
  if (lessonData.sandpackConfig?.files) {
40659
41437
  for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
40660
41438
  const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
40661
- const fullPath = path6.join(exercisePath, cleanPath);
40662
- await fs8.mkdir(path6.dirname(fullPath), { recursive: true });
40663
- await fs8.writeFile(fullPath, fileData.code, "utf-8");
41439
+ const fullPath = path9.join(exercisePath, cleanPath);
41440
+ await fs10.mkdir(path9.dirname(fullPath), { recursive: true });
41441
+ await fs10.writeFile(fullPath, fileData.code, "utf-8");
40664
41442
  extractedFiles.push(cleanPath);
40665
41443
  hasStarterCode = true;
40666
41444
  }
@@ -40668,13 +41446,13 @@ async function extractExercise(input) {
40668
41446
  const tierData = getTierData(lessonData.structuredData, exerciseTier);
40669
41447
  const tierCode = tierData?.code;
40670
41448
  if (tierCode) {
40671
- await fs8.writeFile(path6.join(exercisePath, "exercise.js"), tierCode, "utf-8");
41449
+ await fs10.writeFile(path9.join(exercisePath, "exercise.js"), tierCode, "utf-8");
40672
41450
  extractedFiles.push("exercise.js");
40673
41451
  hasStarterCode = true;
40674
41452
  } else {
40675
41453
  const starter = getStarterCode(lessonData.structuredData);
40676
41454
  if (starter) {
40677
- await fs8.writeFile(path6.join(exercisePath, "exercise.js"), starter, "utf-8");
41455
+ await fs10.writeFile(path9.join(exercisePath, "exercise.js"), starter, "utf-8");
40678
41456
  extractedFiles.push("exercise.js");
40679
41457
  hasStarterCode = true;
40680
41458
  }
@@ -40692,8 +41470,8 @@ async function extractExercise(input) {
40692
41470
  ...exerciseDeps
40693
41471
  }
40694
41472
  };
40695
- await fs8.writeFile(
40696
- path6.join(exercisePath, "package.json"),
41473
+ await fs10.writeFile(
41474
+ path9.join(exercisePath, "package.json"),
40697
41475
  JSON.stringify(pkgJson, null, 2),
40698
41476
  "utf-8"
40699
41477
  );
@@ -40705,20 +41483,20 @@ async function extractExercise(input) {
40705
41483
  );
40706
41484
  for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
40707
41485
  if (!sandpackFileNames.has(configFile)) {
40708
- await fs8.writeFile(path6.join(exercisePath, configFile), configContent, "utf-8");
41486
+ await fs10.writeFile(path9.join(exercisePath, configFile), configContent, "utf-8");
40709
41487
  extractedFiles.push(configFile);
40710
41488
  }
40711
41489
  }
40712
41490
  const setupSh = `#!/bin/sh
40713
41491
  ${scaffold.setupScript}
40714
41492
  `;
40715
- await fs8.writeFile(path6.join(exercisePath, "setup.sh"), setupSh, "utf-8");
41493
+ await fs10.writeFile(path9.join(exercisePath, "setup.sh"), setupSh, "utf-8");
40716
41494
  extractedFiles.push("setup.sh");
40717
41495
  }
40718
41496
  }
40719
41497
  const readme = generateReadme(lessonData, exerciseTier);
40720
- const readmePath = path6.join(exercisePath, "README.md");
40721
- await fs8.writeFile(readmePath, readme, "utf-8");
41498
+ const readmePath = path9.join(exercisePath, "README.md");
41499
+ await fs10.writeFile(readmePath, readme, "utf-8");
40722
41500
  extractedFiles.push("README.md");
40723
41501
  return {
40724
41502
  exercisePath,
@@ -40784,14 +41562,15 @@ var init_workspace = __esm({
40784
41562
  init_extract_exercise();
40785
41563
  init_export_artifact();
40786
41564
  init_templates();
41565
+ init_lesson_exercise_data();
40787
41566
  }
40788
41567
  });
40789
41568
 
40790
41569
  // src/commands/workspace.ts
40791
41570
  import { Command as Command16 } from "commander";
40792
- import path7 from "node:path";
40793
- import os6 from "node:os";
40794
- import fs9 from "node:fs/promises";
41571
+ import path10 from "node:path";
41572
+ import os7 from "node:os";
41573
+ import fs11 from "node:fs/promises";
40795
41574
  var logger12, workspaceCommand;
40796
41575
  var init_workspace2 = __esm({
40797
41576
  "src/commands/workspace.ts"() {
@@ -40803,7 +41582,7 @@ var init_workspace2 = __esm({
40803
41582
  workspaceCommand = new Command16("workspace").description(
40804
41583
  "Gerenciar workspace de estudo local"
40805
41584
  );
40806
- workspaceCommand.command("setup").description("Criar estrutura do workspace para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path7.join(os6.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
41585
+ workspaceCommand.command("setup").description("Criar estrutura do workspace para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path10.join(os7.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
40807
41586
  try {
40808
41587
  await requireSession();
40809
41588
  const activeCourse = await requireActiveCourse();
@@ -40814,6 +41593,7 @@ var init_workspace2 = __esm({
40814
41593
  basePath: opts.path,
40815
41594
  locale: "pt-BR"
40816
41595
  });
41596
+ await setCourseWorkspacePath(activeCourse.courseId, result.workspacePath);
40817
41597
  if (opts.json) {
40818
41598
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
40819
41599
  } else {
@@ -40835,14 +41615,14 @@ Pr\xF3ximo passo: tostudy export
40835
41615
  process.exit(1);
40836
41616
  }
40837
41617
  });
40838
- workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path7.join(os6.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
41618
+ workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path10.join(os7.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
40839
41619
  try {
40840
41620
  const activeCourse = await requireActiveCourse();
40841
- const courseSlug = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
40842
- const workspacePath = path7.join(opts.path, courseSlug);
41621
+ const courseSlug2 = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
41622
+ const workspacePath = path10.join(opts.path, courseSlug2);
40843
41623
  let configData = null;
40844
41624
  try {
40845
- const raw = await fs9.readFile(path7.join(workspacePath, ".ana-config.json"), "utf-8");
41625
+ const raw = await fs11.readFile(path10.join(workspacePath, ".ana-config.json"), "utf-8");
40846
41626
  configData = JSON.parse(raw);
40847
41627
  } catch {
40848
41628
  process.stderr.write(
@@ -40850,42 +41630,42 @@ Pr\xF3ximo passo: tostudy export
40850
41630
  );
40851
41631
  process.exit(1);
40852
41632
  }
40853
- const exercisesDir = path7.join(workspacePath, "exercises");
41633
+ const exercisesDir = path10.join(workspacePath, "exercises");
40854
41634
  let exerciseCount = 0;
40855
41635
  try {
40856
- const moduleDirs = await fs9.readdir(exercisesDir);
41636
+ const moduleDirs = await fs11.readdir(exercisesDir);
40857
41637
  for (const modDir of moduleDirs) {
40858
- const modPath = path7.join(exercisesDir, modDir);
40859
- const stat = await fs9.stat(modPath);
41638
+ const modPath = path10.join(exercisesDir, modDir);
41639
+ const stat = await fs11.stat(modPath);
40860
41640
  if (stat.isDirectory()) {
40861
- const lessonDirs = await fs9.readdir(modPath);
41641
+ const lessonDirs = await fs11.readdir(modPath);
40862
41642
  for (const lessonDir of lessonDirs) {
40863
- const lessonPath = path7.join(modPath, lessonDir);
40864
- const lstat = await fs9.stat(lessonPath);
41643
+ const lessonPath = path10.join(modPath, lessonDir);
41644
+ const lstat = await fs11.stat(lessonPath);
40865
41645
  if (lstat.isDirectory()) exerciseCount++;
40866
41646
  }
40867
41647
  }
40868
41648
  }
40869
41649
  } catch {
40870
41650
  }
40871
- const generatedDir = path7.join(workspacePath, "generated");
41651
+ const generatedDir = path10.join(workspacePath, "generated");
40872
41652
  let artifactCount = 0;
40873
41653
  try {
40874
- const files = await fs9.readdir(generatedDir);
41654
+ const files = await fs11.readdir(generatedDir);
40875
41655
  artifactCount = files.length;
40876
41656
  } catch {
40877
41657
  }
40878
- const diagramsDir = path7.join(workspacePath, "diagrams");
41658
+ const diagramsDir = path10.join(workspacePath, "diagrams");
40879
41659
  let diagramCount = 0;
40880
41660
  try {
40881
- const files = await fs9.readdir(diagramsDir);
41661
+ const files = await fs11.readdir(diagramsDir);
40882
41662
  diagramCount = files.length;
40883
41663
  } catch {
40884
41664
  }
40885
- const vaultDir = path7.join(workspacePath, "vault");
41665
+ const vaultDir = path10.join(workspacePath, "vault");
40886
41666
  let hasVault = false;
40887
41667
  try {
40888
- await fs9.access(path7.join(vaultDir, ".ana-vault.json"));
41668
+ await fs11.access(path10.join(vaultDir, ".ana-vault.json"));
40889
41669
  hasVault = true;
40890
41670
  } catch {
40891
41671
  }
@@ -40929,19 +41709,8 @@ Pr\xF3ximo passo: tostudy export
40929
41709
 
40930
41710
  // src/commands/export.ts
40931
41711
  import { Command as Command17 } from "commander";
40932
- import fs10 from "node:fs/promises";
40933
- import path8 from "node:path";
40934
- import os7 from "node:os";
40935
- async function findWorkspacePath(courseTitle, basePath) {
40936
- const slug = courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
40937
- const candidate = path8.join(basePath, slug);
40938
- try {
40939
- const config2 = await fs10.readFile(path8.join(candidate, ".ana-config.json"), "utf-8");
40940
- if (config2) return candidate;
40941
- } catch {
40942
- }
40943
- return null;
40944
- }
41712
+ import path11 from "node:path";
41713
+ import os8 from "node:os";
40945
41714
  var logger13, exportCommand;
40946
41715
  var init_export = __esm({
40947
41716
  "src/commands/export.ts"() {
@@ -40950,8 +41719,9 @@ var init_export = __esm({
40950
41719
  init_workspace();
40951
41720
  init_http2();
40952
41721
  init_session();
41722
+ init_resolve();
40953
41723
  logger13 = createLogger("cli:export");
40954
- exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path8.join(os7.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
41724
+ exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path11.join(os8.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
40955
41725
  try {
40956
41726
  const session = await requireSession();
40957
41727
  const activeCourse = await requireActiveCourse();
@@ -40961,8 +41731,8 @@ var init_export = __esm({
40961
41731
  process.stderr.write("\u274C Nenhuma li\xE7\xE3o ativa. Execute 'tostudy start' primeiro.\n");
40962
41732
  process.exit(1);
40963
41733
  }
40964
- const workspacePath = await findWorkspacePath(activeCourse.courseTitle, opts.path);
40965
- if (!workspacePath) {
41734
+ const ws = await resolveWorkspace(activeCourse.courseTitle, opts.path);
41735
+ if (!ws.found) {
40966
41736
  process.stderr.write(
40967
41737
  "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
40968
41738
  );
@@ -40974,7 +41744,7 @@ var init_export = __esm({
40974
41744
  const result = await extractExercise({
40975
41745
  lessonData,
40976
41746
  exerciseTier: tier,
40977
- workspacePath
41747
+ workspacePath: ws.workspacePath
40978
41748
  });
40979
41749
  if (opts.json) {
40980
41750
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
@@ -41007,14 +41777,14 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
41007
41777
  // src/commands/open.ts
41008
41778
  import { Command as Command18 } from "commander";
41009
41779
  import { execFile as execFile3 } from "node:child_process";
41010
- import fs11 from "node:fs/promises";
41011
- import path9 from "node:path";
41012
- import os8 from "node:os";
41013
- async function findWorkspacePath2(courseTitle, basePath) {
41780
+ import fs12 from "node:fs/promises";
41781
+ import path12 from "node:path";
41782
+ import os9 from "node:os";
41783
+ async function findWorkspacePath(courseTitle, basePath) {
41014
41784
  const slug = courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
41015
- const candidate = path9.join(basePath, slug);
41785
+ const candidate = path12.join(basePath, slug);
41016
41786
  try {
41017
- await fs11.access(path9.join(candidate, ".ana-config.json"));
41787
+ await fs12.access(path12.join(candidate, ".ana-config.json"));
41018
41788
  return candidate;
41019
41789
  } catch {
41020
41790
  return null;
@@ -41027,10 +41797,10 @@ var init_open = __esm({
41027
41797
  init_src();
41028
41798
  init_session();
41029
41799
  logger14 = createLogger("cli:open");
41030
- openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path9.join(os8.homedir(), "study")).action(async (opts) => {
41800
+ openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path12.join(os9.homedir(), "study")).action(async (opts) => {
41031
41801
  try {
41032
41802
  const activeCourse = await requireActiveCourse();
41033
- const workspacePath = await findWorkspacePath2(activeCourse.courseTitle, opts.path);
41803
+ const workspacePath = await findWorkspacePath(activeCourse.courseTitle, opts.path);
41034
41804
  if (!workspacePath) {
41035
41805
  process.stderr.write(
41036
41806
  "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
@@ -41076,24 +41846,24 @@ var init_types3 = __esm({
41076
41846
  });
41077
41847
 
41078
41848
  // ../../packages/tostudy-core/src/vault/write-vault.ts
41079
- import fs12 from "node:fs/promises";
41080
- import path10 from "node:path";
41081
- async function writeVaultFiles(files, outputPath, courseId, courseSlug) {
41849
+ import fs13 from "node:fs/promises";
41850
+ import path13 from "node:path";
41851
+ async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
41082
41852
  for (const file2 of files) {
41083
- const fullPath = path10.join(outputPath, file2.relativePath);
41084
- await fs12.mkdir(path10.dirname(fullPath), { recursive: true });
41085
- await fs12.writeFile(fullPath, file2.content, "utf-8");
41853
+ const fullPath = path13.join(outputPath, file2.relativePath);
41854
+ await fs13.mkdir(path13.dirname(fullPath), { recursive: true });
41855
+ await fs13.writeFile(fullPath, file2.content, "utf-8");
41086
41856
  }
41087
- const vaultPath = path10.join(outputPath, courseSlug);
41857
+ const vaultPath = path13.join(outputPath, courseSlug2);
41088
41858
  const marker = {
41089
41859
  courseId,
41090
- courseSlug,
41860
+ courseSlug: courseSlug2,
41091
41861
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
41092
41862
  version: VAULT_MARKER_VERSION
41093
41863
  };
41094
- await fs12.mkdir(vaultPath, { recursive: true });
41095
- await fs12.writeFile(
41096
- path10.join(vaultPath, VAULT_MARKER_FILENAME),
41864
+ await fs13.mkdir(vaultPath, { recursive: true });
41865
+ await fs13.writeFile(
41866
+ path13.join(vaultPath, VAULT_MARKER_FILENAME),
41097
41867
  JSON.stringify(marker, null, 2),
41098
41868
  "utf-8"
41099
41869
  );
@@ -41118,9 +41888,9 @@ var init_vault = __esm({
41118
41888
 
41119
41889
  // src/commands/vault.ts
41120
41890
  import { Command as Command19 } from "commander";
41121
- import path11 from "node:path";
41122
- import os9 from "node:os";
41123
- import fs13 from "node:fs/promises";
41891
+ import path14 from "node:path";
41892
+ import os10 from "node:os";
41893
+ import fs14 from "node:fs/promises";
41124
41894
  var logger15, vaultCommand;
41125
41895
  var init_vault2 = __esm({
41126
41896
  "src/commands/vault.ts"() {
@@ -41132,15 +41902,15 @@ var init_vault2 = __esm({
41132
41902
  init_session();
41133
41903
  logger15 = createLogger("cli:vault");
41134
41904
  vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
41135
- vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path11.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
41905
+ vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
41136
41906
  try {
41137
41907
  const session = await requireSession();
41138
41908
  const activeCourse = await requireActiveCourse();
41139
41909
  const driftWarning = await checkCourseDrift();
41140
41910
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41141
- const courseSlug = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
41142
- const workspacePath = path11.join(opts.path, courseSlug);
41143
- const vaultOutputPath = path11.join(workspacePath, "vault");
41911
+ const courseSlug2 = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
41912
+ const workspacePath = path14.join(opts.path, courseSlug2);
41913
+ const vaultOutputPath = path14.join(workspacePath, "vault");
41144
41914
  const res = await fetch(`${session.apiUrl}/api/cli/vault/init`, {
41145
41915
  method: "POST",
41146
41916
  headers: {
@@ -41162,7 +41932,7 @@ var init_vault2 = __esm({
41162
41932
  data.files,
41163
41933
  vaultOutputPath,
41164
41934
  activeCourse.courseId,
41165
- courseSlug
41935
+ courseSlug2
41166
41936
  );
41167
41937
  logger15.info("Vault generated", {
41168
41938
  courseId: activeCourse.courseId,
@@ -41203,16 +41973,16 @@ Para visualizar:
41203
41973
  process.exit(1);
41204
41974
  }
41205
41975
  });
41206
- vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path11.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
41976
+ vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
41207
41977
  try {
41208
41978
  const session = await requireSession();
41209
41979
  const activeCourse = await requireActiveCourse();
41210
41980
  const driftWarning = await checkCourseDrift();
41211
41981
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41212
- const courseSlug = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
41213
- const vaultPath = path11.join(opts.path, courseSlug, "vault");
41982
+ const courseSlug2 = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
41983
+ const vaultPath = path14.join(opts.path, courseSlug2, "vault");
41214
41984
  try {
41215
- await fs13.access(path11.join(vaultPath, ".ana-vault.json"));
41985
+ await fs14.access(path14.join(vaultPath, ".ana-vault.json"));
41216
41986
  } catch {
41217
41987
  process.stderr.write("\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n");
41218
41988
  process.exit(1);
@@ -41220,8 +41990,8 @@ Para visualizar:
41220
41990
  const data = createHttpProvider(session.apiUrl, session.token);
41221
41991
  const deps = { data, logger: logger15 };
41222
41992
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
41223
- const markerPath = path11.join(vaultPath, ".ana-vault.json");
41224
- const markerRaw = await fs13.readFile(markerPath, "utf-8");
41993
+ const markerPath = path14.join(vaultPath, ".ana-vault.json");
41994
+ const markerRaw = await fs14.readFile(markerPath, "utf-8");
41225
41995
  const marker = JSON.parse(markerRaw);
41226
41996
  marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
41227
41997
  marker.progress = {
@@ -41229,10 +41999,10 @@ Para visualizar:
41229
41999
  currentModule: progress3.currentModule.title,
41230
42000
  currentLesson: progress3.currentLesson.title
41231
42001
  };
41232
- await fs13.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
41233
- const courseIndexPath = path11.join(vaultPath, courseSlug, "index.md");
42002
+ await fs14.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
42003
+ const courseIndexPath = path14.join(vaultPath, courseSlug2, "index.md");
41234
42004
  try {
41235
- let indexContent = await fs13.readFile(courseIndexPath, "utf-8");
42005
+ let indexContent = await fs14.readFile(courseIndexPath, "utf-8");
41236
42006
  indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
41237
42007
  const titleEnd = indexContent.indexOf("\n");
41238
42008
  if (titleEnd !== -1) {
@@ -41243,7 +42013,7 @@ Para visualizar:
41243
42013
  `;
41244
42014
  indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
41245
42015
  }
41246
- await fs13.writeFile(courseIndexPath, indexContent, "utf-8");
42016
+ await fs14.writeFile(courseIndexPath, indexContent, "utf-8");
41247
42017
  } catch {
41248
42018
  }
41249
42019
  const syncedAt = marker.lastSyncedAt;
@@ -41290,7 +42060,7 @@ __export(cli_exports, {
41290
42060
  import { Command as Command20 } from "commander";
41291
42061
  function createProgram() {
41292
42062
  const program2 = new Command20();
41293
- program2.name("tostudy").description("ToStudy CLI \u2014 study courses from the terminal").version(CLI_VERSION).option("--json", "Output structured JSON (for LLM agents)").option("--verbose", "Enable debug output").option("--course <id>", "Override active course ID");
42063
+ program2.name("tostudy").description("ToStudy CLI \u2014 study courses from the terminal").version(CLI_VERSION).option("--verbose", "Enable debug output").option("--course <id>", "Override active course ID");
41294
42064
  program2.addCommand(setupCommand);
41295
42065
  program2.addCommand(doctorCommand);
41296
42066
  program2.addCommand(initCommand);
@@ -41335,7 +42105,7 @@ var init_cli = __esm({
41335
42105
  init_export();
41336
42106
  init_open();
41337
42107
  init_vault2();
41338
- CLI_VERSION = "0.5.0";
42108
+ CLI_VERSION = "0.6.0";
41339
42109
  }
41340
42110
  });
41341
42111