@tostudy-ai/cli 0.17.5 → 0.17.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1496,6 +1496,11 @@ var init_slug = __esm({
1496
1496
  function resolveWorkspaceTokensInText(text, paths) {
1497
1497
  return text.replace(WORKSPACE_TOKEN, () => paths.workspace).replace(VAULT_TOKEN, () => paths.vault);
1498
1498
  }
1499
+ function isPlainObject(value) {
1500
+ if (value === null || typeof value !== "object") return false;
1501
+ const proto = Object.getPrototypeOf(value);
1502
+ return proto === Object.prototype || proto === null;
1503
+ }
1499
1504
  function resolveWorkspaceTokens(value, paths) {
1500
1505
  if (typeof value === "string") {
1501
1506
  return resolveWorkspaceTokensInText(value, paths);
@@ -1503,7 +1508,7 @@ function resolveWorkspaceTokens(value, paths) {
1503
1508
  if (Array.isArray(value)) {
1504
1509
  return value.map((item) => resolveWorkspaceTokens(item, paths));
1505
1510
  }
1506
- if (value !== null && typeof value === "object") {
1511
+ if (isPlainObject(value)) {
1507
1512
  const out = {};
1508
1513
  for (const [key, item] of Object.entries(value)) {
1509
1514
  out[key] = resolveWorkspaceTokens(item, paths);
@@ -2281,12 +2286,15 @@ var runtime_registry_exports = {};
2281
2286
  __export(runtime_registry_exports, {
2282
2287
  RUNTIMES: () => RUNTIMES,
2283
2288
  detectedSlashHints: () => detectedSlashHints,
2289
+ isTrackedByGit: () => isTrackedByGit,
2284
2290
  needsRootAgentsConsent: () => needsRootAgentsConsent,
2285
- upsertRootAgentsBlock: () => upsertRootAgentsBlock
2291
+ parseTemplateVersion: () => parseTemplateVersion,
2292
+ upsertRootAgentsBlock: () => upsertRootAgentsBlock,
2293
+ writeProjectUniversalCommand: () => writeProjectUniversalCommand
2286
2294
  });
2287
2295
  import { execFileSync as execFileSync2 } from "node:child_process";
2288
2296
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
2289
- import { join as join2 } from "node:path";
2297
+ import { dirname, join as join2 } from "node:path";
2290
2298
  function hasBinary(name) {
2291
2299
  try {
2292
2300
  execFileSync2("which", [name], { encoding: "utf-8", stdio: "pipe" });
@@ -2299,6 +2307,42 @@ function writeFile2(absDir, fileName, body) {
2299
2307
  mkdirSync2(absDir, { recursive: true });
2300
2308
  writeFileSync2(join2(absDir, fileName), body);
2301
2309
  }
2310
+ function parseTemplateVersion(text) {
2311
+ const match = text.match(/tostudy-template-version:\s*(\d+)/);
2312
+ if (!match || !match[1]) return null;
2313
+ const val = Number.parseInt(match[1], 10);
2314
+ return Number.isNaN(val) ? null : val;
2315
+ }
2316
+ function isTrackedByGit(cwd, relativePath) {
2317
+ try {
2318
+ execFileSync2("git", ["ls-files", "--error-unmatch", "--", relativePath], {
2319
+ cwd,
2320
+ stdio: "pipe"
2321
+ });
2322
+ return true;
2323
+ } catch {
2324
+ return false;
2325
+ }
2326
+ }
2327
+ function writeProjectUniversalCommand(cwd, relativePath, body, onKept) {
2328
+ const absPath = join2(cwd, relativePath);
2329
+ if (!existsSync2(absPath)) {
2330
+ mkdirSync2(dirname(absPath), { recursive: true });
2331
+ writeFileSync2(absPath, body);
2332
+ return [relativePath];
2333
+ }
2334
+ const existing = readFileSync(absPath, "utf-8");
2335
+ if (existing === body) {
2336
+ return [relativePath];
2337
+ }
2338
+ const version2 = parseTemplateVersion(existing);
2339
+ if (!isTrackedByGit(cwd, relativePath) && version2 !== null && version2 < UNIVERSAL_TEMPLATE_VERSION) {
2340
+ writeFileSync2(absPath, body);
2341
+ return [relativePath];
2342
+ }
2343
+ onKept?.(relativePath, version2, UNIVERSAL_TEMPLATE_VERSION);
2344
+ return [];
2345
+ }
2302
2346
  function cursorMdc(title, content) {
2303
2347
  return `---
2304
2348
  description: ${title} \u2014 ToStudy Course Guide
@@ -2380,6 +2424,7 @@ var init_runtime_registry = __esm({
2380
2424
  "use strict";
2381
2425
  init_dist();
2382
2426
  init_slug();
2427
+ init_instruction_template_v3();
2383
2428
  logger2 = createLogger("cli:runtime-registry");
2384
2429
  detectOpencode = (home) => existsSync2(join2(home, ".opencode")) || existsSync2(join2(home, ".config", "opencode"));
2385
2430
  detectGrok = (home) => existsSync2(join2(home, ".grok")) || hasBinary("grok");
@@ -2393,9 +2438,8 @@ var init_runtime_registry = __esm({
2393
2438
  writeFile2(join2(cwd, ".claude", "commands"), `tostudy-${slug}.md`, content);
2394
2439
  return [`.claude/commands/tostudy-${slug}.md`];
2395
2440
  },
2396
- writeUniversal({ cwd, content }) {
2397
- writeFile2(join2(cwd, ".claude", "commands"), "tostudy.md", content);
2398
- return [".claude/commands/tostudy.md"];
2441
+ writeUniversal({ cwd, content, onKept }) {
2442
+ return writeProjectUniversalCommand(cwd, ".claude/commands/tostudy.md", content, onKept);
2399
2443
  },
2400
2444
  slashHint: (slug) => `No Claude Code, digite: /tostudy-${slug}`
2401
2445
  },
@@ -2412,9 +2456,13 @@ var init_runtime_registry = __esm({
2412
2456
  );
2413
2457
  return [`.cursor/rules/tostudy-${slug}.mdc`];
2414
2458
  },
2415
- writeUniversal({ cwd, content }) {
2416
- writeFile2(join2(cwd, ".cursor", "rules"), "tostudy.mdc", cursorMdc("ToStudy", content));
2417
- return [".cursor/rules/tostudy.mdc"];
2459
+ writeUniversal({ cwd, content, onKept }) {
2460
+ return writeProjectUniversalCommand(
2461
+ cwd,
2462
+ ".cursor/rules/tostudy.mdc",
2463
+ cursorMdc("ToStudy", content),
2464
+ onKept
2465
+ );
2418
2466
  }
2419
2467
  },
2420
2468
  {
@@ -2488,17 +2536,17 @@ ${content}`;
2488
2536
  );
2489
2537
  return [`.agents/skills/tostudy-${slug}/SKILL.md`];
2490
2538
  },
2491
- writeUniversal({ cwd, content }) {
2492
- writeFile2(
2493
- join2(cwd, ".agents", "skills", "tostudy"),
2494
- "SKILL.md",
2539
+ writeUniversal({ cwd, content, onKept }) {
2540
+ return writeProjectUniversalCommand(
2541
+ cwd,
2542
+ ".agents/skills/tostudy/SKILL.md",
2495
2543
  antigravitySkillMd(
2496
2544
  "tostudy",
2497
2545
  "ToStudy \u2014 AI Tutor Guide. Use when the student asks to study or runs tostudy commands.",
2498
2546
  content
2499
- )
2547
+ ),
2548
+ onKept
2500
2549
  );
2501
- return [".agents/skills/tostudy/SKILL.md"];
2502
2550
  },
2503
2551
  slashHint: (slug) => `No Antigravity, a skill tostudy-${slug} ativa automaticamente`
2504
2552
  },
@@ -2515,9 +2563,8 @@ ${content}`;
2515
2563
  if (rootAgents) written.push(rootAgents);
2516
2564
  return written;
2517
2565
  },
2518
- writeUniversal({ cwd, content }) {
2519
- writeFile2(join2(cwd, ".tostudy"), "AGENTS.md", content);
2520
- return [".tostudy/AGENTS.md"];
2566
+ writeUniversal({ cwd, content, onKept }) {
2567
+ return writeProjectUniversalCommand(cwd, ".tostudy/AGENTS.md", content, onKept);
2521
2568
  }
2522
2569
  }
2523
2570
  ];
@@ -2532,6 +2579,7 @@ var instruction_files_exports = {};
2532
2579
  __export(instruction_files_exports, {
2533
2580
  installUniversalCommand: () => installUniversalCommand,
2534
2581
  needsRootAgentsConsent: () => needsRootAgentsConsent,
2582
+ parseTemplateVersion: () => parseTemplateVersion,
2535
2583
  slugify: () => slugify,
2536
2584
  upsertRootAgentsBlock: () => upsertRootAgentsBlock,
2537
2585
  writeInstructionFiles: () => writeInstructionFiles
@@ -2548,11 +2596,11 @@ function writeInstructionFiles(cwd, ctx, content, opts = {}) {
2548
2596
  logger3.info("Generated instruction files (v3)", { slug, files: written });
2549
2597
  return written;
2550
2598
  }
2551
- function installUniversalCommand(platform2, cwd = process.cwd(), homeDir = homedir()) {
2599
+ function installUniversalCommand(platform2, cwd = process.cwd(), homeDir = homedir(), onKept) {
2552
2600
  const entry = RUNTIMES.find((r) => r.id === platform2);
2553
2601
  if (!entry?.writeUniversal) return [];
2554
2602
  const content = renderUniversalInstruction();
2555
- const written = entry.writeUniversal({ cwd, home: homeDir, content });
2603
+ const written = entry.writeUniversal({ cwd, home: homeDir, content, onKept });
2556
2604
  logger3.info("Installed universal /tostudy command", { platform: platform2, files: written });
2557
2605
  return written;
2558
2606
  }
@@ -2744,9 +2792,13 @@ var init_login = __esm({
2744
2792
  }
2745
2793
  const cwd = process.cwd();
2746
2794
  const home = homedir2();
2795
+ const keptFiles = [];
2796
+ const onKept = (file2, foundVersion, availableVersion) => {
2797
+ keptFiles.push({ file: file2, foundVersion, availableVersion });
2798
+ };
2747
2799
  for (const runtime of RUNTIMES2) {
2748
2800
  if (runtime.writeUniversal && runtime.detect(home, cwd)) {
2749
- installedFiles.push(...installUniversalCommand2(runtime.id, cwd));
2801
+ installedFiles.push(...installUniversalCommand2(runtime.id, cwd, home, onKept));
2750
2802
  }
2751
2803
  }
2752
2804
  if (installedFiles.length > 0) {
@@ -2755,6 +2807,16 @@ var init_login = __esm({
2755
2807
  console.log(` \u2713 Instalado: ${file2}`);
2756
2808
  }
2757
2809
  }
2810
+ if (keptFiles.length > 0) {
2811
+ if (installedFiles.length === 0) {
2812
+ console.log("");
2813
+ }
2814
+ for (const { file: file2, foundVersion, availableVersion } of keptFiles) {
2815
+ console.log(
2816
+ ` \u26A0 Mantido: ${file2} (template v${foundVersion ?? "?"} -> v${availableVersion} dispon\xEDvel -- apague o arquivo e rode tostudy login para atualizar)`
2817
+ );
2818
+ }
2819
+ }
2758
2820
  } catch {
2759
2821
  }
2760
2822
  try {
@@ -3536,7 +3598,7 @@ var CLI_VERSION;
3536
3598
  var init_version = __esm({
3537
3599
  "src/version.ts"() {
3538
3600
  "use strict";
3539
- CLI_VERSION = true ? "0.17.5" : "0.7.1";
3601
+ CLI_VERSION = true ? "0.17.6" : "0.7.1";
3540
3602
  }
3541
3603
  });
3542
3604
 
@@ -3702,6 +3764,26 @@ var init_cache2 = __esm({
3702
3764
 
3703
3765
  // src/commands/doctor.ts
3704
3766
  import { Command as Command4 } from "commander";
3767
+ async function verifyToken(session, fetchImpl = fetch) {
3768
+ try {
3769
+ const res = await fetchImpl(`${session.apiUrl}/api/cli/courses`, {
3770
+ method: "GET",
3771
+ headers: {
3772
+ Authorization: `Bearer ${session.token}`
3773
+ },
3774
+ signal: AbortSignal.timeout(5e3)
3775
+ });
3776
+ if (res.ok) {
3777
+ return "valid";
3778
+ }
3779
+ if (res.status === 401 || res.status === 403) {
3780
+ return "rejected";
3781
+ }
3782
+ return "unverified";
3783
+ } catch {
3784
+ return "unverified";
3785
+ }
3786
+ }
3705
3787
  var doctorCommand;
3706
3788
  var init_doctor = __esm({
3707
3789
  "src/commands/doctor.ts"() {
@@ -3744,10 +3826,22 @@ var init_doctor = __esm({
3744
3826
  session = await getSession();
3745
3827
  } catch {
3746
3828
  }
3829
+ let serverVerdict = null;
3830
+ if (session) {
3831
+ if (session.expiresAt && new Date(session.expiresAt) < /* @__PURE__ */ new Date()) {
3832
+ serverVerdict = "expired";
3833
+ } else {
3834
+ serverVerdict = await verifyToken({
3835
+ apiUrl: session.apiUrl ?? "https://tostudy.ai",
3836
+ token: session.token
3837
+ });
3838
+ }
3839
+ }
3747
3840
  checks["auth"] = {
3748
3841
  loggedIn: !!session,
3749
3842
  userName: session?.userName ?? null,
3750
- expiresAt: session?.expiresAt ?? null
3843
+ expiresAt: session?.expiresAt ?? null,
3844
+ serverVerdict
3751
3845
  };
3752
3846
  try {
3753
3847
  const apiUrl = session?.apiUrl ?? "https://tostudy.ai";
@@ -3847,7 +3941,17 @@ var init_doctor = __esm({
3847
3941
  console.log(` ${pnpmVersion ? "\u2713" : "\u25CB"} pnpm ${pnpmVersion ?? errs.notFound}`);
3848
3942
  console.log(` ${gitVersion ? "\u2713" : "\u25CB"} git ${gitVersion ?? errs.notFound}`);
3849
3943
  console.log("\n Autentica\xE7\xE3o");
3850
- console.log(` ${session ? "\u2713" : "\u2717"} Token ${session ? "v\xE1lido" : "n\xE3o logado"}`);
3944
+ if (!session) {
3945
+ console.log(" \u2717 Token n\xE3o logado");
3946
+ } else if (serverVerdict === "valid") {
3947
+ console.log(" \u2713 Token v\xE1lido");
3948
+ } else if (serverVerdict === "expired") {
3949
+ console.log(" \u25CB Token expirado (renova sozinho no pr\xF3ximo comando)");
3950
+ } else if (serverVerdict === "rejected") {
3951
+ console.log(" \u2717 Token recusado pelo servidor \u2014 rode: tostudy login");
3952
+ } else {
3953
+ console.log(" ? Token n\xE3o verificado (API indispon\xEDvel)");
3954
+ }
3851
3955
  if (session) {
3852
3956
  console.log(` \u2713 Usu\xE1rio ${session.userName}`);
3853
3957
  }
@@ -4716,6 +4820,7 @@ async function runStart(opts, deps = defaultDeps3) {
4716
4820
  if (ws)
4717
4821
  await deps.updateWorkspaceState(ws.workspacePath, {
4718
4822
  currentLessonId: moduleData.firstLesson.id,
4823
+ retryLessonId: void 0,
4719
4824
  currentModuleId: moduleData.module.id
4720
4825
  });
4721
4826
  if (opts.json) {
@@ -4808,6 +4913,7 @@ var init_start_next = __esm({
4808
4913
  if (ws)
4809
4914
  await updateWorkspaceState(ws.workspacePath, {
4810
4915
  currentLessonId: moduleData.firstLesson.id,
4916
+ retryLessonId: void 0,
4811
4917
  currentModuleId: moduleData.module.id
4812
4918
  });
4813
4919
  if (opts.json) {
@@ -4868,6 +4974,7 @@ var init_next = __esm({
4868
4974
  if (ws)
4869
4975
  await updateWorkspaceState(ws.workspacePath, {
4870
4976
  currentLessonId: lessonData.lesson.id,
4977
+ retryLessonId: void 0,
4871
4978
  ...lessonData.lesson.moduleId ? { currentModuleId: lessonData.lesson.moduleId } : {}
4872
4979
  });
4873
4980
  if (opts.json) {
@@ -5364,7 +5471,7 @@ __export(util_exports, {
5364
5471
  getSizableOrigin: () => getSizableOrigin,
5365
5472
  hexToUint8Array: () => hexToUint8Array,
5366
5473
  isObject: () => isObject,
5367
- isPlainObject: () => isPlainObject,
5474
+ isPlainObject: () => isPlainObject2,
5368
5475
  issue: () => issue,
5369
5476
  joinValues: () => joinValues,
5370
5477
  jsonStringifyReplacer: () => jsonStringifyReplacer,
@@ -5533,7 +5640,7 @@ function slugify2(input2) {
5533
5640
  function isObject(data) {
5534
5641
  return typeof data === "object" && data !== null && !Array.isArray(data);
5535
5642
  }
5536
- function isPlainObject(o) {
5643
+ function isPlainObject2(o) {
5537
5644
  if (isObject(o) === false)
5538
5645
  return false;
5539
5646
  const ctor = o.constructor;
@@ -5550,7 +5657,7 @@ function isPlainObject(o) {
5550
5657
  return true;
5551
5658
  }
5552
5659
  function shallowClone(o) {
5553
- if (isPlainObject(o))
5660
+ if (isPlainObject2(o))
5554
5661
  return { ...o };
5555
5662
  if (Array.isArray(o))
5556
5663
  return [...o];
@@ -5686,7 +5793,7 @@ function omit(schema, mask) {
5686
5793
  return clone(schema, def);
5687
5794
  }
5688
5795
  function extend(schema, shape) {
5689
- if (!isPlainObject(shape)) {
5796
+ if (!isPlainObject2(shape)) {
5690
5797
  throw new Error("Invalid input to extend: expected a plain object");
5691
5798
  }
5692
5799
  const checks = schema._zod.def.checks;
@@ -5709,7 +5816,7 @@ function extend(schema, shape) {
5709
5816
  return clone(schema, def);
5710
5817
  }
5711
5818
  function safeExtend(schema, shape) {
5712
- if (!isPlainObject(shape)) {
5819
+ if (!isPlainObject2(shape)) {
5713
5820
  throw new Error("Invalid input to safeExtend: expected a plain object");
5714
5821
  }
5715
5822
  const def = mergeDefs(schema._zod.def, {
@@ -7185,7 +7292,7 @@ function mergeValues(a, b) {
7185
7292
  if (a instanceof Date && b instanceof Date && +a === +b) {
7186
7293
  return { valid: true, data: a };
7187
7294
  }
7188
- if (isPlainObject(a) && isPlainObject(b)) {
7295
+ if (isPlainObject2(a) && isPlainObject2(b)) {
7189
7296
  const bKeys = Object.keys(b);
7190
7297
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
7191
7298
  const newObj = { ...a, ...b };
@@ -8379,7 +8486,7 @@ var init_schemas = __esm({
8379
8486
  $ZodType.init(inst, def);
8380
8487
  inst._zod.parse = (payload, ctx) => {
8381
8488
  const input2 = payload.value;
8382
- if (!isPlainObject(input2)) {
8489
+ if (!isPlainObject2(input2)) {
8383
8490
  payload.issues.push({
8384
8491
  expected: "record",
8385
8492
  code: "invalid_type",
@@ -20147,6 +20254,12 @@ var init_init_template = __esm({
20147
20254
  import fs11 from "node:fs";
20148
20255
  import path14 from "node:path";
20149
20256
  import { Command as Command14 } from "commander";
20257
+ async function clearRetryPin() {
20258
+ const ws = await findWorkspaceState();
20259
+ if (ws?.state.retryLessonId) {
20260
+ await updateWorkspaceState(ws.workspacePath, { retryLessonId: void 0 });
20261
+ }
20262
+ }
20150
20263
  var logger16, validateCommand;
20151
20264
  var init_validate = __esm({
20152
20265
  "src/commands/validate.ts"() {
@@ -20157,6 +20270,7 @@ var init_validate = __esm({
20157
20270
  init_http();
20158
20271
  init_guards();
20159
20272
  init_course_state();
20273
+ init_workspace_state();
20160
20274
  init_formatter();
20161
20275
  init_init_template();
20162
20276
  init_errors();
@@ -20170,7 +20284,8 @@ var init_validate = __esm({
20170
20284
  if (driftWarning) process.stderr.write(driftWarning + "\n");
20171
20285
  const data = createHttpProvider(session.apiUrl, session.token);
20172
20286
  const deps = { data, logger: logger16 };
20173
- let lessonId = activeCourse.currentLessonId;
20287
+ const retryLessonId = activeCourse.retryLessonId;
20288
+ let lessonId = retryLessonId ?? activeCourse.currentLessonId;
20174
20289
  if (!lessonId) {
20175
20290
  try {
20176
20291
  const prog = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
@@ -20254,8 +20369,19 @@ var init_validate = __esm({
20254
20369
  } else {
20255
20370
  output(formatValidation(result), { json: false });
20256
20371
  }
20372
+ if (retryLessonId && result.passed) {
20373
+ await clearRetryPin();
20374
+ if (!opts.json) {
20375
+ process.stderr.write(
20376
+ "\n\u21A9\uFE0F Revalida\xE7\xE3o aprovada e registrada. Voltando para a sua li\xE7\xE3o atual.\n"
20377
+ );
20378
+ }
20379
+ }
20257
20380
  process.exit(result.passed ? 0 : 1);
20258
20381
  } catch (err) {
20382
+ if (err instanceof CliApiError && err.code === "LESSON_NOT_REACHED") {
20383
+ await clearRetryPin();
20384
+ }
20259
20385
  if (isEnrollmentNotEntitledError(err)) {
20260
20386
  const friendly = getErrors().enrollmentNotEntitled;
20261
20387
  if (opts.json) jsonError("enrollment_not_entitled", { message: friendly });
@@ -20288,8 +20414,81 @@ var init_validate = __esm({
20288
20414
  }
20289
20415
  });
20290
20416
 
20291
- // src/commands/menu.ts
20417
+ // src/commands/retry.ts
20292
20418
  import { Command as Command15 } from "commander";
20419
+ var logger17, LESSON_ID_RE, retryCommand;
20420
+ var init_retry = __esm({
20421
+ "src/commands/retry.ts"() {
20422
+ "use strict";
20423
+ init_dist();
20424
+ init_lessons();
20425
+ init_http();
20426
+ init_guards();
20427
+ init_workspace_state();
20428
+ init_formatter();
20429
+ logger17 = createLogger("cli:retry");
20430
+ LESSON_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20431
+ retryCommand = new Command15("retry").description("Revalidate a lesson you already completed (your grade keeps the best attempt)").argument("[lessonId]", "Id of the completed lesson (see: tostudy grades --course <slug> --json)").option("--clear", "Cancel the pending retry; validate goes back to the current lesson").option("--json", "Output structured JSON").action(async (lessonId, opts) => {
20432
+ try {
20433
+ if (opts.clear) {
20434
+ const ws2 = await findWorkspaceState();
20435
+ if (ws2?.state.retryLessonId) {
20436
+ await updateWorkspaceState(ws2.workspacePath, { retryLessonId: void 0 });
20437
+ }
20438
+ if (opts.json) output({ retryLessonId: null }, { json: true });
20439
+ else
20440
+ output("Revalida\xE7\xE3o cancelada. `tostudy validate` volta a usar a sua li\xE7\xE3o atual.", {
20441
+ json: false
20442
+ });
20443
+ return;
20444
+ }
20445
+ if (!lessonId || !LESSON_ID_RE.test(lessonId)) {
20446
+ const message = "Informe o id da li\xE7\xE3o conclu\xEDda. Os ids aparecem em: tostudy grades --course <slug> --json";
20447
+ if (opts.json) jsonError("invalid_lesson_id", { message });
20448
+ error(message);
20449
+ }
20450
+ const session = await requireSession();
20451
+ const activeCourse = await requireActiveCourse();
20452
+ const ws = await findWorkspaceState();
20453
+ if (!ws) {
20454
+ const message = "Nenhum workspace ativo. Rode: tostudy select";
20455
+ if (opts.json) jsonError("no_workspace", { message });
20456
+ error(message);
20457
+ }
20458
+ const data = createHttpProvider(session.apiUrl, session.token);
20459
+ const content = await getContent(
20460
+ { lessonId, enrollmentId: activeCourse.enrollmentId },
20461
+ { data, logger: logger17 }
20462
+ );
20463
+ await updateWorkspaceState(ws.workspacePath, { retryLessonId: lessonId });
20464
+ if (opts.json) {
20465
+ output({ retryLessonId: lessonId, lesson: content }, { json: true });
20466
+ return;
20467
+ }
20468
+ output(
20469
+ [
20470
+ `\u21A9\uFE0F Revalida\xE7\xE3o da li\xE7\xE3o: ${content.title}`,
20471
+ "",
20472
+ content.content,
20473
+ ...content.acceptanceCriteria ? ["", "Crit\xE9rios de aceita\xE7\xE3o:", content.acceptanceCriteria] : [],
20474
+ "",
20475
+ "Esta li\xE7\xE3o continua conclu\xEDda. A nova tentativa entra na sua nota se for a melhor.",
20476
+ "\u2192 tostudy validate <arquivo> para enviar a nova resposta",
20477
+ "\u2192 tostudy retry --clear para desistir e voltar \xE0 li\xE7\xE3o atual"
20478
+ ].join("\n"),
20479
+ { json: false }
20480
+ );
20481
+ } catch (err) {
20482
+ const message = err instanceof Error ? err.message : String(err);
20483
+ if (opts.json) jsonError(message);
20484
+ error(message);
20485
+ }
20486
+ });
20487
+ }
20488
+ });
20489
+
20490
+ // src/commands/menu.ts
20491
+ import { Command as Command16 } from "commander";
20293
20492
  var menuCommand;
20294
20493
  var init_menu = __esm({
20295
20494
  "src/commands/menu.ts"() {
@@ -20297,7 +20496,7 @@ var init_menu = __esm({
20297
20496
  init_session_store();
20298
20497
  init_workspace_state();
20299
20498
  init_formatter();
20300
- menuCommand = new Command15("menu").description("Show available commands and current study context").action(async () => {
20499
+ menuCommand = new Command16("menu").description("Show available commands and current study context").action(async () => {
20301
20500
  const session = await getSession();
20302
20501
  const wsResult = session ? await findWorkspaceState() : null;
20303
20502
  const activeCourse = wsResult?.state ?? null;
@@ -20474,7 +20673,7 @@ var init_learner_context = __esm({
20474
20673
  });
20475
20674
 
20476
20675
  // src/commands/init.ts
20477
- import { Command as Command16 } from "commander";
20676
+ import { Command as Command17 } from "commander";
20478
20677
  function isCompleteProfile(flags) {
20479
20678
  return Boolean(
20480
20679
  flags.segment && flags.company && flags.products && flags.region && flags.team && flags.goal && flags.level
@@ -20645,7 +20844,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
20645
20844
  deps.output(artifacts.learnerBrief, { json: false });
20646
20845
  }
20647
20846
  }
20648
- var logger17, defaultDeps4, initCommand;
20847
+ var logger18, defaultDeps4, initCommand;
20649
20848
  var init_init = __esm({
20650
20849
  "src/commands/init.ts"() {
20651
20850
  "use strict";
@@ -20662,7 +20861,7 @@ var init_init = __esm({
20662
20861
  init_instruction_pipeline();
20663
20862
  init_root_agents_consent();
20664
20863
  init_pipeline_deps();
20665
- logger17 = createLogger("cli:init");
20864
+ logger18 = createLogger("cli:init");
20666
20865
  defaultDeps4 = {
20667
20866
  // GH #1419 item 3 — `init` used the raw session, so an expired access
20668
20867
  // token met the POST as a 401. Refresh through the guard when one exists.
@@ -20682,12 +20881,12 @@ var init_init = __esm({
20682
20881
  saveCourseLearnerProfile,
20683
20882
  buildInitArtifacts,
20684
20883
  output,
20685
- logger: logger17,
20884
+ logger: logger18,
20686
20885
  createHttpProvider,
20687
20886
  resolveAndGenerate,
20688
20887
  buildPipelineDeps
20689
20888
  };
20690
- initCommand = new Command16("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option(
20889
+ initCommand = new Command17("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option(
20691
20890
  "--root-agents",
20692
20891
  "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
20693
20892
  ).option("--json", "Output structured JSON").action(async (opts) => {
@@ -21015,11 +21214,11 @@ var init_workspace = __esm({
21015
21214
  });
21016
21215
 
21017
21216
  // src/commands/workspace.ts
21018
- import { Command as Command17 } from "commander";
21217
+ import { Command as Command18 } from "commander";
21019
21218
  import path17 from "node:path";
21020
21219
  import os8 from "node:os";
21021
21220
  import fs14 from "node:fs/promises";
21022
- var logger18, workspaceCommand;
21221
+ var logger19, workspaceCommand;
21023
21222
  var init_workspace2 = __esm({
21024
21223
  "src/commands/workspace.ts"() {
21025
21224
  "use strict";
@@ -21029,8 +21228,8 @@ var init_workspace2 = __esm({
21029
21228
  init_course_state();
21030
21229
  init_resolve();
21031
21230
  init_errors();
21032
- logger18 = createLogger("cli:workspace");
21033
- workspaceCommand = new Command17("workspace").description(
21231
+ logger19 = createLogger("cli:workspace");
21232
+ workspaceCommand = new Command18("workspace").description(
21034
21233
  getErrors().workspaceCommandDescription
21035
21234
  );
21036
21235
  workspaceCommand.command("setup").description(getErrors().workspaceSetupDescription).option("--path <dir>", "Diret\xF3rio base do workspace (omita para usar a pasta atual)").option("--json", "Output structured JSON").action(async (opts) => {
@@ -21095,7 +21294,7 @@ Pr\xF3ximo passo: tostudy export
21095
21294
  );
21096
21295
  }
21097
21296
  } catch (err) {
21098
- logger18.error("workspace setup failed", { error: err });
21297
+ logger19.error("workspace setup failed", { error: err });
21099
21298
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21100
21299
  `);
21101
21300
  process.exit(1);
@@ -21199,11 +21398,11 @@ Pr\xF3ximo passo: tostudy export
21199
21398
  });
21200
21399
 
21201
21400
  // src/commands/export.ts
21202
- import { Command as Command18 } from "commander";
21401
+ import { Command as Command19 } from "commander";
21203
21402
  import path18 from "node:path";
21204
21403
  import os9 from "node:os";
21205
21404
  import fs15 from "node:fs/promises";
21206
- var logger19, exportCommand;
21405
+ var logger20, exportCommand;
21207
21406
  var init_export = __esm({
21208
21407
  "src/commands/export.ts"() {
21209
21408
  "use strict";
@@ -21214,8 +21413,8 @@ var init_export = __esm({
21214
21413
  init_course_state();
21215
21414
  init_resolve();
21216
21415
  init_errors();
21217
- logger19 = createLogger("cli:export");
21218
- exportCommand = new Command18("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", path18.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
21416
+ logger20 = createLogger("cli:export");
21417
+ exportCommand = new Command19("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", path18.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
21219
21418
  try {
21220
21419
  const session = await requireSession();
21221
21420
  const activeCourse = await requireActiveCourse();
@@ -21242,7 +21441,7 @@ var init_export = __esm({
21242
21441
  }
21243
21442
  if (!hasConfig) {
21244
21443
  const slug = courseSlug(activeCourse.courseTitle);
21245
- logger19.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
21444
+ logger20.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
21246
21445
  await fs15.mkdir(ws.workspacePath, { recursive: true });
21247
21446
  for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
21248
21447
  await fs15.mkdir(path18.join(ws.workspacePath, dir), { recursive: true });
@@ -21307,7 +21506,7 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
21307
21506
  );
21308
21507
  }
21309
21508
  } catch (err) {
21310
- logger19.error("export failed", { error: err });
21509
+ logger20.error("export failed", { error: err });
21311
21510
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21312
21511
  `);
21313
21512
  process.exit(1);
@@ -21317,11 +21516,11 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
21317
21516
  });
21318
21517
 
21319
21518
  // src/commands/open.ts
21320
- import { Command as Command19 } from "commander";
21519
+ import { Command as Command20 } from "commander";
21321
21520
  import { execFile as execFile3 } from "node:child_process";
21322
21521
  import path19 from "node:path";
21323
21522
  import os10 from "node:os";
21324
- var logger20, openCommand;
21523
+ var logger21, openCommand;
21325
21524
  var init_open = __esm({
21326
21525
  "src/commands/open.ts"() {
21327
21526
  "use strict";
@@ -21330,8 +21529,8 @@ var init_open = __esm({
21330
21529
  init_course_state();
21331
21530
  init_resolve();
21332
21531
  init_errors();
21333
- logger20 = createLogger("cli:open");
21334
- openCommand = new Command19("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path19.join(os10.homedir(), "study")).action(async (opts) => {
21532
+ logger21 = createLogger("cli:open");
21533
+ openCommand = new Command20("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path19.join(os10.homedir(), "study")).action(async (opts) => {
21335
21534
  try {
21336
21535
  const activeCourse = await requireActiveCourse();
21337
21536
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -21348,7 +21547,7 @@ var init_open = __esm({
21348
21547
  const editor = process.env["EDITOR"] ?? "code";
21349
21548
  execFile3(editor, [ws.workspacePath], (err) => {
21350
21549
  if (err) {
21351
- logger20.error("open failed", { editor, workspacePath: ws.workspacePath });
21550
+ logger21.error("open failed", { editor, workspacePath: ws.workspacePath });
21352
21551
  process.stderr.write(`\u274C Falha ao abrir: ${err.message}
21353
21552
  `);
21354
21553
  process.exit(1);
@@ -21357,7 +21556,7 @@ var init_open = __esm({
21357
21556
  `);
21358
21557
  });
21359
21558
  } catch (err) {
21360
- logger20.error("open command failed", { error: err });
21559
+ logger21.error("open command failed", { error: err });
21361
21560
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21362
21561
  `);
21363
21562
  process.exit(1);
@@ -21426,11 +21625,11 @@ var init_vault = __esm({
21426
21625
  });
21427
21626
 
21428
21627
  // src/commands/vault.ts
21429
- import { Command as Command20 } from "commander";
21628
+ import { Command as Command21 } from "commander";
21430
21629
  import path21 from "node:path";
21431
21630
  import os11 from "node:os";
21432
21631
  import fs17 from "node:fs/promises";
21433
- var logger21, vaultCommand;
21632
+ var logger22, vaultCommand;
21434
21633
  var init_vault2 = __esm({
21435
21634
  "src/commands/vault.ts"() {
21436
21635
  "use strict";
@@ -21443,8 +21642,8 @@ var init_vault2 = __esm({
21443
21642
  init_course_state();
21444
21643
  init_resolve();
21445
21644
  init_errors();
21446
- logger21 = createLogger("cli:vault");
21447
- vaultCommand = new Command20("vault").description("Gerenciar vault Obsidian do curso");
21645
+ logger22 = createLogger("cli:vault");
21646
+ vaultCommand = new Command21("vault").description("Gerenciar vault Obsidian do curso");
21448
21647
  vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path21.join(os11.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
21449
21648
  try {
21450
21649
  const session = await requireSession();
@@ -21487,7 +21686,7 @@ var init_vault2 = __esm({
21487
21686
  cliWorkspacePaths(workspacePath, activeCourse.courseTitle)
21488
21687
  );
21489
21688
  const result = await writeVaultFiles(files, vaultOutputPath, activeCourse.courseId, slug);
21490
- logger21.info("Vault generated", {
21689
+ logger22.info("Vault generated", {
21491
21690
  courseId: activeCourse.courseId,
21492
21691
  vaultPath: result.vaultPath,
21493
21692
  filesWritten: result.filesWritten
@@ -21520,7 +21719,7 @@ Para visualizar:
21520
21719
  );
21521
21720
  }
21522
21721
  } catch (err) {
21523
- logger21.error("vault init failed", { error: err });
21722
+ logger22.error("vault init failed", { error: err });
21524
21723
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21525
21724
  `);
21526
21725
  process.exit(1);
@@ -21550,7 +21749,7 @@ Para visualizar:
21550
21749
  process.exit(1);
21551
21750
  }
21552
21751
  const data = createHttpProvider(session.apiUrl, session.token);
21553
- const deps = { data, logger: logger21 };
21752
+ const deps = { data, logger: logger22 };
21554
21753
  const progress = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
21555
21754
  const markerPath = path21.join(vaultPath, ".ana-vault.json");
21556
21755
  const markerRaw = await fs17.readFile(markerPath, "utf-8");
@@ -21604,7 +21803,7 @@ Para visualizar:
21604
21803
  );
21605
21804
  }
21606
21805
  } catch (err) {
21607
- logger21.error("vault sync failed", { error: err });
21806
+ logger22.error("vault sync failed", { error: err });
21608
21807
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21609
21808
  `);
21610
21809
  process.exit(1);
@@ -21614,7 +21813,7 @@ Para visualizar:
21614
21813
  });
21615
21814
 
21616
21815
  // src/commands/profile.ts
21617
- import { Command as Command21 } from "commander";
21816
+ import { Command as Command22 } from "commander";
21618
21817
  var profileCommand;
21619
21818
  var init_profile = __esm({
21620
21819
  "src/commands/profile.ts"() {
@@ -21622,7 +21821,7 @@ var init_profile = __esm({
21622
21821
  init_guards();
21623
21822
  init_course_state();
21624
21823
  init_user_profile();
21625
- profileCommand = new Command21("profile").description("Show your learner profile for the active course").option("--json", "Output structured JSON").action(async (opts) => {
21824
+ profileCommand = new Command22("profile").description("Show your learner profile for the active course").option("--json", "Output structured JSON").action(async (opts) => {
21626
21825
  const activeCourse = await requireActiveCourse();
21627
21826
  const onboarding = await getCourseOnboardingState(activeCourse.courseId);
21628
21827
  const profile = onboarding?.learnerProfile ?? await getUserProfile();
@@ -21683,8 +21882,8 @@ var init_profile = __esm({
21683
21882
  });
21684
21883
 
21685
21884
  // src/commands/sync.ts
21686
- import { Command as Command22 } from "commander";
21687
- var logger22, syncCommand;
21885
+ import { Command as Command23 } from "commander";
21886
+ var logger23, syncCommand;
21688
21887
  var init_sync = __esm({
21689
21888
  "src/commands/sync.ts"() {
21690
21889
  "use strict";
@@ -21695,8 +21894,8 @@ var init_sync = __esm({
21695
21894
  init_workspace_state();
21696
21895
  init_root_agents_consent();
21697
21896
  init_pipeline_deps();
21698
- logger22 = createLogger("cli:sync");
21699
- syncCommand = new Command22("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").option(
21897
+ logger23 = createLogger("cli:sync");
21898
+ syncCommand = new Command23("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").option(
21700
21899
  "--all-runtimes",
21701
21900
  "Write instruction files for every supported runtime, even undetected ones"
21702
21901
  ).option(
@@ -21760,7 +21959,7 @@ var init_sync = __esm({
21760
21959
  } catch (err) {
21761
21960
  const msg = err instanceof Error ? err.message : String(err);
21762
21961
  if (msg.includes("process.exit")) return;
21763
- logger22.warn("sync failed", { error: msg });
21962
+ logger23.warn("sync failed", { error: msg });
21764
21963
  error(msg);
21765
21964
  }
21766
21965
  });
@@ -21768,8 +21967,8 @@ var init_sync = __esm({
21768
21967
  });
21769
21968
 
21770
21969
  // src/commands/brief.ts
21771
- import { Command as Command23 } from "commander";
21772
- var logger23, briefCommand;
21970
+ import { Command as Command24 } from "commander";
21971
+ var logger24, briefCommand;
21773
21972
  var init_brief = __esm({
21774
21973
  "src/commands/brief.ts"() {
21775
21974
  "use strict";
@@ -21778,8 +21977,8 @@ var init_brief = __esm({
21778
21977
  init_cache();
21779
21978
  init_api();
21780
21979
  init_formatter();
21781
- logger23 = createLogger("cli:brief");
21782
- briefCommand = new Command23("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
21980
+ logger24 = createLogger("cli:brief");
21981
+ briefCommand = new Command24("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
21783
21982
  try {
21784
21983
  const session = await requireSession();
21785
21984
  const cached2 = await readBriefCache();
@@ -21812,7 +22011,7 @@ var init_brief = __esm({
21812
22011
  }
21813
22012
  output(lines.join("\n"), { json: false });
21814
22013
  } catch (err) {
21815
- logger23.error("Failed to show brief", { err });
22014
+ logger24.error("Failed to show brief", { err });
21816
22015
  error(`Erro ao buscar brief: ${err instanceof Error ? err.message : String(err)}`);
21817
22016
  }
21818
22017
  });
@@ -21820,8 +22019,8 @@ var init_brief = __esm({
21820
22019
  });
21821
22020
 
21822
22021
  // src/commands/brief-create.ts
21823
- import { Command as Command24 } from "commander";
21824
- var logger24, briefCreateCommand;
22022
+ import { Command as Command25 } from "commander";
22023
+ var logger25, briefCreateCommand;
21825
22024
  var init_brief_create = __esm({
21826
22025
  "src/commands/brief-create.ts"() {
21827
22026
  "use strict";
@@ -21831,8 +22030,8 @@ var init_brief_create = __esm({
21831
22030
  init_api();
21832
22031
  init_cache();
21833
22032
  init_formatter();
21834
- logger24 = createLogger("cli:brief-create");
21835
- briefCreateCommand = new Command24("brief-create").description("Create your base learner brief via interactive prompts (T1 bootstrap)").action(async () => {
22033
+ logger25 = createLogger("cli:brief-create");
22034
+ briefCreateCommand = new Command25("brief-create").description("Create your base learner brief via interactive prompts (T1 bootstrap)").action(async () => {
21836
22035
  try {
21837
22036
  const session = await requireSession();
21838
22037
  const answers = await collectBootstrapAnswers({ userName: session.userName });
@@ -21856,7 +22055,7 @@ var init_brief_create = __esm({
21856
22055
  ];
21857
22056
  output(doneLines.join("\n"), { json: false });
21858
22057
  } catch (err) {
21859
- logger24.error("Failed to create brief", { err });
22058
+ logger25.error("Failed to create brief", { err });
21860
22059
  error(`Erro ao criar brief: ${err instanceof Error ? err.message : String(err)}`);
21861
22060
  }
21862
22061
  });
@@ -21864,7 +22063,7 @@ var init_brief_create = __esm({
21864
22063
  });
21865
22064
 
21866
22065
  // src/commands/brief-open.ts
21867
- import { Command as Command25 } from "commander";
22066
+ import { Command as Command26 } from "commander";
21868
22067
  import { execFile as execFile4 } from "node:child_process";
21869
22068
  import { platform } from "node:process";
21870
22069
  function openUrl(url2) {
@@ -21893,7 +22092,7 @@ var init_brief_open = __esm({
21893
22092
  init_guards();
21894
22093
  init_formatter();
21895
22094
  BRIEF_URL = "https://tostudy.ai/student/settings/learner-brief";
21896
- briefOpenCommand = new Command25("brief-open").description("Open the learner brief editor in your web browser").action(async () => {
22095
+ briefOpenCommand = new Command26("brief-open").description("Open the learner brief editor in your web browser").action(async () => {
21897
22096
  await requireSession();
21898
22097
  output(`Abrindo ${BRIEF_URL} no navegador...`, { json: false });
21899
22098
  openUrl(BRIEF_URL);
@@ -21921,7 +22120,7 @@ async function saveModuleSummary(workspacePath, input2) {
21921
22120
  ""
21922
22121
  ].join("\n");
21923
22122
  fs18.writeFileSync(filePath, header + input2.summary, { mode: 384 });
21924
- logger25.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
22123
+ logger26.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
21925
22124
  return filePath;
21926
22125
  }
21927
22126
  async function loadSessionContext(workspacePath) {
@@ -21942,19 +22141,19 @@ async function loadSessionContext(workspacePath) {
21942
22141
  }
21943
22142
  return { moduleSummaries: summaries };
21944
22143
  }
21945
- var logger25;
22144
+ var logger26;
21946
22145
  var init_storage = __esm({
21947
22146
  "src/sessions/storage.ts"() {
21948
22147
  "use strict";
21949
22148
  init_dist();
21950
- logger25 = createLogger("cli:sessions");
22149
+ logger26 = createLogger("cli:sessions");
21951
22150
  }
21952
22151
  });
21953
22152
 
21954
22153
  // src/commands/compact.ts
21955
22154
  import fs19 from "node:fs";
21956
- import { Command as Command26 } from "commander";
21957
- var logger26, compactCommand;
22155
+ import { Command as Command27 } from "commander";
22156
+ var logger27, compactCommand;
21958
22157
  var init_compact = __esm({
21959
22158
  "src/commands/compact.ts"() {
21960
22159
  "use strict";
@@ -21963,8 +22162,8 @@ var init_compact = __esm({
21963
22162
  init_workspace_state();
21964
22163
  init_storage();
21965
22164
  init_formatter();
21966
- logger26 = createLogger("cli:compact");
21967
- compactCommand = new Command26("compact").description("Save a module study summary (LLM-generated, from stdin)").option("--module-id <id>", "Module ID").option("--module-title <title>", "Module title").option("--json", "Output structured JSON").action(async (opts) => {
22165
+ logger27 = createLogger("cli:compact");
22166
+ compactCommand = new Command27("compact").description("Save a module study summary (LLM-generated, from stdin)").option("--module-id <id>", "Module ID").option("--module-title <title>", "Module title").option("--json", "Output structured JSON").action(async (opts) => {
21968
22167
  try {
21969
22168
  const activeCourse = await requireActiveCourse();
21970
22169
  const ws = await findWorkspaceState();
@@ -21984,7 +22183,7 @@ var init_compact = __esm({
21984
22183
  moduleTitle,
21985
22184
  summary
21986
22185
  });
21987
- logger26.debug("Compact summary saved", { moduleId, filePath });
22186
+ logger27.debug("Compact summary saved", { moduleId, filePath });
21988
22187
  if (opts.json) {
21989
22188
  output({ saved: true, path: filePath, moduleId }, { json: true });
21990
22189
  } else {
@@ -21993,7 +22192,7 @@ var init_compact = __esm({
21993
22192
  } catch (err) {
21994
22193
  const msg = err instanceof Error ? err.message : String(err);
21995
22194
  if (msg.includes("process.exit")) return;
21996
- logger26.warn("compact failed", { error: msg });
22195
+ logger27.warn("compact failed", { error: msg });
21997
22196
  if (opts.json) jsonError(msg);
21998
22197
  error(msg);
21999
22198
  }
@@ -22002,8 +22201,8 @@ var init_compact = __esm({
22002
22201
  });
22003
22202
 
22004
22203
  // src/commands/context.ts
22005
- import { Command as Command27 } from "commander";
22006
- var logger27, contextCommand;
22204
+ import { Command as Command28 } from "commander";
22205
+ var logger28, contextCommand;
22007
22206
  var init_context = __esm({
22008
22207
  "src/commands/context.ts"() {
22009
22208
  "use strict";
@@ -22013,8 +22212,8 @@ var init_context = __esm({
22013
22212
  init_course_state();
22014
22213
  init_formatter();
22015
22214
  init_errors();
22016
- logger27 = createLogger("cli:context");
22017
- contextCommand = new Command27("context").description("Load session context (workspace state + module summaries) for LLM consumption").option("--json", "Output structured JSON").action(async (opts) => {
22215
+ logger28 = createLogger("cli:context");
22216
+ contextCommand = new Command28("context").description("Load session context (workspace state + module summaries) for LLM consumption").option("--json", "Output structured JSON").action(async (opts) => {
22018
22217
  try {
22019
22218
  const ws = await findWorkspaceState();
22020
22219
  if (!ws) {
@@ -22044,7 +22243,7 @@ var init_context = __esm({
22044
22243
  totalModulesCompleted: sessionCtx.moduleSummaries.length,
22045
22244
  driftWarning: driftWarning ?? null
22046
22245
  };
22047
- logger27.debug("Context loaded", {
22246
+ logger28.debug("Context loaded", {
22048
22247
  courseId: ws.state.courseId,
22049
22248
  moduleSummaries: sessionCtx.moduleSummaries.length
22050
22249
  });
@@ -22058,7 +22257,7 @@ var init_context = __esm({
22058
22257
  } catch (err) {
22059
22258
  const msg = err instanceof Error ? err.message : String(err);
22060
22259
  if (msg.includes("process.exit")) return;
22061
- logger27.warn("context failed", { error: msg });
22260
+ logger28.warn("context failed", { error: msg });
22062
22261
  if (opts.json) jsonError(msg);
22063
22262
  error(msg);
22064
22263
  }
@@ -22067,8 +22266,8 @@ var init_context = __esm({
22067
22266
  });
22068
22267
 
22069
22268
  // src/commands/memory.ts
22070
- import { Command as Command28 } from "commander";
22071
- var logger28, memoryCommand;
22269
+ import { Command as Command29 } from "commander";
22270
+ var logger29, memoryCommand;
22072
22271
  var init_memory = __esm({
22073
22272
  "src/commands/memory.ts"() {
22074
22273
  "use strict";
@@ -22078,8 +22277,8 @@ var init_memory = __esm({
22078
22277
  init_guards();
22079
22278
  init_formatter();
22080
22279
  init_errors();
22081
- logger28 = createLogger("cli:memory");
22082
- memoryCommand = new Command28("memory").description("Load accumulated student memory (learning profile + recent lessons) for the tutor").option("--json", "Output structured JSON").action(async (opts) => {
22280
+ logger29 = createLogger("cli:memory");
22281
+ memoryCommand = new Command29("memory").description("Load accumulated student memory (learning profile + recent lessons) for the tutor").option("--json", "Output structured JSON").action(async (opts) => {
22083
22282
  try {
22084
22283
  const ws = await findWorkspaceState();
22085
22284
  if (!ws) {
@@ -22102,7 +22301,7 @@ var init_memory = __esm({
22102
22301
  } catch (err) {
22103
22302
  const msg = err instanceof Error ? err.message : String(err);
22104
22303
  if (msg.includes("process.exit")) return;
22105
- logger28.warn("memory failed", { error: msg });
22304
+ logger29.warn("memory failed", { error: msg });
22106
22305
  if (opts.json) jsonError(msg);
22107
22306
  else error(msg);
22108
22307
  }
@@ -22111,8 +22310,8 @@ var init_memory = __esm({
22111
22310
  });
22112
22311
 
22113
22312
  // src/commands/insight.ts
22114
- import { Command as Command29 } from "commander";
22115
- var logger29, VALID_TYPES, insightCommand;
22313
+ import { Command as Command30 } from "commander";
22314
+ var logger30, VALID_TYPES, insightCommand;
22116
22315
  var init_insight = __esm({
22117
22316
  "src/commands/insight.ts"() {
22118
22317
  "use strict";
@@ -22122,9 +22321,9 @@ var init_insight = __esm({
22122
22321
  init_guards();
22123
22322
  init_formatter();
22124
22323
  init_errors();
22125
- logger29 = createLogger("cli:insight");
22324
+ logger30 = createLogger("cli:insight");
22126
22325
  VALID_TYPES = ["difficulty", "breakthrough", "question"];
22127
- insightCommand = new Command29("insight").description(
22326
+ insightCommand = new Command30("insight").description(
22128
22327
  "Persist a student cognitive insight (difficulty | breakthrough | question) into course memory"
22129
22328
  ).argument("<type>", "difficulty | breakthrough | question").argument("<content>", 'Short description, e.g. "confunde async/await com promises"').option("--module-id <id>", "Module the insight relates to").option("--json", "Output structured JSON").action(async (type, content, opts) => {
22130
22329
  try {
@@ -22164,7 +22363,7 @@ var init_insight = __esm({
22164
22363
  } catch (err) {
22165
22364
  const msg = err instanceof Error ? err.message : String(err);
22166
22365
  if (msg.includes("process.exit")) return;
22167
- logger29.warn("insight failed", { error: msg });
22366
+ logger30.warn("insight failed", { error: msg });
22168
22367
  if (opts.json) jsonError(msg);
22169
22368
  else error(msg);
22170
22369
  }
@@ -22173,11 +22372,11 @@ var init_insight = __esm({
22173
22372
  });
22174
22373
 
22175
22374
  // src/commands/level.ts
22176
- import { Command as Command30 } from "commander";
22375
+ import { Command as Command31 } from "commander";
22177
22376
  function isExerciseLevel2(value) {
22178
22377
  return ALL_LEVELS.includes(value);
22179
22378
  }
22180
- var logger30, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
22379
+ var logger31, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
22181
22380
  var init_level = __esm({
22182
22381
  "src/commands/level.ts"() {
22183
22382
  "use strict";
@@ -22186,7 +22385,7 @@ var init_level = __esm({
22186
22385
  init_exercises();
22187
22386
  init_guards();
22188
22387
  init_formatter();
22189
- logger30 = createLogger("cli:level");
22388
+ logger31 = createLogger("cli:level");
22190
22389
  ALL_LEVELS = ["L0", "L1", "L2", "L3", "L4"];
22191
22390
  LEVEL_LABELS2 = {
22192
22391
  L0: "Pr\xE9-check (perguntas conceituais)",
@@ -22195,12 +22394,12 @@ var init_level = __esm({
22195
22394
  L3: "Guiado (passo a passo + checkpoints)",
22196
22395
  L4: "Desafio livre (folha em branco)"
22197
22396
  };
22198
- levelCommand = new Command30("level").description("Show or set the exercise scaffolding level for the active course").argument("[level]", "Target level: L0, L1, L2, L3, or L4").option("--json", "Output structured JSON").action(async (rawLevel, opts) => {
22397
+ levelCommand = new Command31("level").description("Show or set the exercise scaffolding level for the active course").argument("[level]", "Target level: L0, L1, L2, L3, or L4").option("--json", "Output structured JSON").action(async (rawLevel, opts) => {
22199
22398
  try {
22200
22399
  const session = await requireSession();
22201
22400
  const activeCourse = await requireActiveCourse();
22202
22401
  const data = createHttpProvider(session.apiUrl, session.token);
22203
- const deps = { data, logger: logger30 };
22402
+ const deps = { data, logger: logger31 };
22204
22403
  if (!rawLevel) {
22205
22404
  const levels = await getEnrollmentLevels({ enrollmentId: activeCourse.enrollmentId }, deps);
22206
22405
  if (opts.json) {
@@ -22258,8 +22457,8 @@ var init_level = __esm({
22258
22457
  });
22259
22458
 
22260
22459
  // src/commands/theory.ts
22261
- import { Command as Command31 } from "commander";
22262
- var logger31, EXERCISE_LEVELS2, theoryCommand;
22460
+ import { Command as Command32 } from "commander";
22461
+ var logger32, EXERCISE_LEVELS2, theoryCommand;
22263
22462
  var init_theory = __esm({
22264
22463
  "src/commands/theory.ts"() {
22265
22464
  "use strict";
@@ -22267,9 +22466,9 @@ var init_theory = __esm({
22267
22466
  init_guards();
22268
22467
  init_formatter();
22269
22468
  init_errors();
22270
- logger31 = createLogger("cli:theory");
22469
+ logger32 = createLogger("cli:theory");
22271
22470
  EXERCISE_LEVELS2 = ["L0", "L1", "L2", "L3", "L4"];
22272
- theoryCommand = new Command31("theory").description("Request more theory on the current lesson (escape hatch)").option("--focus <text>", "Optional focus area in PT-BR (max 500 chars)").option("--level <L0|L1|L2|L3|L4>", "Override current exercise level (otherwise auto-detect)").option("--lesson <uuid>", "Override lesson ID (defaults to current workspace lesson)").option("--json", "Output structured JSON").action(async (opts) => {
22471
+ theoryCommand = new Command32("theory").description("Request more theory on the current lesson (escape hatch)").option("--focus <text>", "Optional focus area in PT-BR (max 500 chars)").option("--level <L0|L1|L2|L3|L4>", "Override current exercise level (otherwise auto-detect)").option("--lesson <uuid>", "Override lesson ID (defaults to current workspace lesson)").option("--json", "Output structured JSON").action(async (opts) => {
22273
22472
  try {
22274
22473
  const session = await requireSession();
22275
22474
  const active = await requireActiveCourse();
@@ -22306,7 +22505,7 @@ var init_theory = __esm({
22306
22505
  currentLevel = levels.currentLevel;
22307
22506
  }
22308
22507
  const requestUrl = `${session.apiUrl}/api/cli/exercises/more-theory`;
22309
- logger31.debug("Requesting more theory", { lessonId, currentLevel });
22508
+ logger32.debug("Requesting more theory", { lessonId, currentLevel });
22310
22509
  const res = await fetch(requestUrl, {
22311
22510
  method: "POST",
22312
22511
  headers: {
@@ -22640,7 +22839,7 @@ var init_resolve_enrollment = __esm({
22640
22839
  });
22641
22840
 
22642
22841
  // src/commands/export-grades.ts
22643
- import { Command as Command32 } from "commander";
22842
+ import { Command as Command33 } from "commander";
22644
22843
  function isFormat(value) {
22645
22844
  return SUPPORTED_FORMATS.includes(value);
22646
22845
  }
@@ -22727,7 +22926,7 @@ async function runExportGrades(opts, deps = defaultDeps5) {
22727
22926
  if (!rendered.endsWith("\n")) rendered += "\n";
22728
22927
  deps.stdoutWrite(rendered);
22729
22928
  }
22730
- var logger32, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
22929
+ var logger33, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
22731
22930
  var init_export_grades = __esm({
22732
22931
  "src/commands/export-grades.ts"() {
22733
22932
  "use strict";
@@ -22735,7 +22934,7 @@ var init_export_grades = __esm({
22735
22934
  init_guards();
22736
22935
  init_grades();
22737
22936
  init_resolve_enrollment();
22738
- logger32 = createLogger("cli:export-grades");
22937
+ logger33 = createLogger("cli:export-grades");
22739
22938
  SUPPORTED_FORMATS = ["json", "csv", "md"];
22740
22939
  defaultDeps5 = {
22741
22940
  requireSession,
@@ -22748,10 +22947,10 @@ var init_export_grades = __esm({
22748
22947
  stderrWrite: (message) => process.stderr.write(message),
22749
22948
  // process.exit is typed as `never` — wrap so the cast is local to one line.
22750
22949
  exit: (code) => process.exit(code),
22751
- logger: logger32,
22950
+ logger: logger33,
22752
22951
  resolveBySlug: resolveEnrollmentBySlug
22753
22952
  };
22754
- exportGradesCommand = new Command32("export-grades").description("Exporta o hist\xF3rico de valida\xE7\xF5es (notas) do curso ativo").option(
22953
+ exportGradesCommand = new Command33("export-grades").description("Exporta o hist\xF3rico de valida\xE7\xF5es (notas) do curso ativo").option(
22755
22954
  "--course <slug>",
22756
22955
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, exporta o curso ativo."
22757
22956
  ).option("--format <json|csv|md>", "Formato de sa\xEDda (default: json)", "json").addHelpText(
@@ -22776,7 +22975,7 @@ C\xF3digos de sa\xEDda:
22776
22975
  });
22777
22976
 
22778
22977
  // src/commands/grades.ts
22779
- import { Command as Command33 } from "commander";
22978
+ import { Command as Command34 } from "commander";
22780
22979
  async function fetchAttempts(session, enrollmentId, deps) {
22781
22980
  const url2 = `${session.apiUrl}/api/cli/validations/by-enrollment?enrollmentId=${encodeURIComponent(
22782
22981
  enrollmentId
@@ -22811,15 +23010,13 @@ function summarizeAttempts(courseTitle, attempts) {
22811
23010
  };
22812
23011
  }
22813
23012
  let passedCount = 0;
22814
- let scoreSum = 0;
22815
- let scoreCount = 0;
22816
23013
  let latestTs = -Infinity;
22817
23014
  let latestAt = attempts[0].attemptedAt;
23015
+ const bestByLesson = /* @__PURE__ */ new Map();
22818
23016
  for (const a of attempts) {
22819
23017
  if (a.passed) passedCount++;
22820
23018
  if (a.score !== null) {
22821
- scoreSum += a.score;
22822
- scoreCount++;
23019
+ bestByLesson.set(a.lessonId, Math.max(a.score, bestByLesson.get(a.lessonId) ?? -Infinity));
22823
23020
  }
22824
23021
  const t = (a.attemptedAt instanceof Date ? a.attemptedAt : new Date(a.attemptedAt)).getTime();
22825
23022
  if (t > latestTs) {
@@ -22827,13 +23024,14 @@ function summarizeAttempts(courseTitle, attempts) {
22827
23024
  latestAt = a.attemptedAt;
22828
23025
  }
22829
23026
  }
23027
+ const bestScores = [...bestByLesson.values()];
22830
23028
  return {
22831
23029
  courseTitle,
22832
23030
  lastAttemptAt: latestAt,
22833
23031
  attemptsCount: attempts.length,
22834
23032
  passedCount,
22835
23033
  failedCount: attempts.length - passedCount,
22836
- averageScore: scoreCount === 0 ? null : scoreSum / scoreCount
23034
+ averageScore: bestScores.length === 0 ? null : bestScores.reduce((a, b) => a + b, 0) / bestScores.length
22837
23035
  };
22838
23036
  }
22839
23037
  async function runGrades(opts, deps = defaultDeps6) {
@@ -22917,7 +23115,7 @@ async function runGrades(opts, deps = defaultDeps6) {
22917
23115
  );
22918
23116
  deps.stdoutWrite(formatGradesAllEnrollmentsTable(summaries) + "\n");
22919
23117
  }
22920
- var logger33, defaultDeps6, gradesCommand;
23118
+ var logger34, defaultDeps6, gradesCommand;
22921
23119
  var init_grades2 = __esm({
22922
23120
  "src/commands/grades.ts"() {
22923
23121
  "use strict";
@@ -22927,7 +23125,7 @@ var init_grades2 = __esm({
22927
23125
  init_guards();
22928
23126
  init_grades();
22929
23127
  init_resolve_enrollment();
22930
- logger33 = createLogger("cli:grades");
23128
+ logger34 = createLogger("cli:grades");
22931
23129
  defaultDeps6 = {
22932
23130
  requireSession,
22933
23131
  // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
@@ -22937,12 +23135,12 @@ var init_grades2 = __esm({
22937
23135
  stdoutWrite: (message) => process.stdout.write(message),
22938
23136
  stderrWrite: (message) => process.stderr.write(message),
22939
23137
  exit: (code) => process.exit(code),
22940
- logger: logger33,
23138
+ logger: logger34,
22941
23139
  resolveBySlug: resolveEnrollmentBySlug,
22942
23140
  buildDataProvider: createHttpProvider,
22943
23141
  listCoursesFn: listCourses
22944
23142
  };
22945
- gradesCommand = new Command33("grades").description("Mostra um resumo das notas (todas as matr\xEDculas ou um curso espec\xEDfico)").option(
23143
+ gradesCommand = new Command34("grades").description("Mostra um resumo das notas (todas as matr\xEDculas ou um curso espec\xEDfico)").option(
22946
23144
  "--course <slug>",
22947
23145
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, lista todos os cursos."
22948
23146
  ).addHelpText(
@@ -22966,7 +23164,7 @@ C\xF3digos de sa\xEDda:
22966
23164
  });
22967
23165
 
22968
23166
  // src/commands/review.ts
22969
- import { Command as Command34 } from "commander";
23167
+ import { Command as Command35 } from "commander";
22970
23168
  import { createInterface } from "node:readline/promises";
22971
23169
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
22972
23170
  async function defaultPromptLessonChoice(lessonIds) {
@@ -23102,7 +23300,7 @@ async function runReview(slug, opts, deps = defaultDeps7) {
23102
23300
  }) + "\n"
23103
23301
  );
23104
23302
  }
23105
- var logger34, defaultDeps7, reviewCommand;
23303
+ var logger35, defaultDeps7, reviewCommand;
23106
23304
  var init_review2 = __esm({
23107
23305
  "src/commands/review.ts"() {
23108
23306
  "use strict";
@@ -23110,7 +23308,7 @@ var init_review2 = __esm({
23110
23308
  init_guards();
23111
23309
  init_grades();
23112
23310
  init_resolve_enrollment();
23113
- logger34 = createLogger("cli:review");
23311
+ logger35 = createLogger("cli:review");
23114
23312
  defaultDeps7 = {
23115
23313
  requireSession,
23116
23314
  // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
@@ -23120,11 +23318,11 @@ var init_review2 = __esm({
23120
23318
  stdoutWrite: (message) => process.stdout.write(message),
23121
23319
  stderrWrite: (message) => process.stderr.write(message),
23122
23320
  exit: (code) => process.exit(code),
23123
- logger: logger34,
23321
+ logger: logger35,
23124
23322
  resolveBySlug: resolveEnrollmentBySlug,
23125
23323
  promptLessonChoice: defaultPromptLessonChoice
23126
23324
  };
23127
- reviewCommand = new Command34("review").description("Revisa a \xFAltima tentativa de valida\xE7\xE3o de uma li\xE7\xE3o").argument("<slug>", "Slug do curso (gerado a partir do t\xEDtulo)").option("--json", "Sa\xEDda em JSON (sem prompt interativo)").addHelpText(
23325
+ reviewCommand = new Command35("review").description("Revisa a \xFAltima tentativa de valida\xE7\xE3o de uma li\xE7\xE3o").argument("<slug>", "Slug do curso (gerado a partir do t\xEDtulo)").option("--json", "Sa\xEDda em JSON (sem prompt interativo)").addHelpText(
23128
23326
  "after",
23129
23327
  `
23130
23328
  Exemplos:
@@ -23153,9 +23351,9 @@ __export(cli_exports, {
23153
23351
  CLI_VERSION: () => CLI_VERSION,
23154
23352
  createProgram: () => createProgram
23155
23353
  });
23156
- import { Command as Command35 } from "commander";
23354
+ import { Command as Command36 } from "commander";
23157
23355
  function createProgram() {
23158
- const program2 = new Command35();
23356
+ const program2 = new Command36();
23159
23357
  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").option("--locale <code>", "Output locale (pt-BR | en-US); defaults to LANG env then pt-BR").addHelpText(
23160
23358
  "before",
23161
23359
  [
@@ -23181,6 +23379,7 @@ function createProgram() {
23181
23379
  program2.addCommand(knowledgeCommand);
23182
23380
  program2.addCommand(hintCommand);
23183
23381
  program2.addCommand(validateCommand);
23382
+ program2.addCommand(retryCommand);
23184
23383
  program2.addCommand(levelCommand);
23185
23384
  program2.addCommand(theoryCommand);
23186
23385
  program2.addCommand(menuCommand);
@@ -23219,6 +23418,7 @@ var init_cli = __esm({
23219
23418
  init_knowledge();
23220
23419
  init_hint();
23221
23420
  init_validate();
23421
+ init_retry();
23222
23422
  init_menu();
23223
23423
  init_init();
23224
23424
  init_workspace2();