@tostudy-ai/cli 0.17.4 → 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 {
@@ -2866,6 +2928,7 @@ var init_errors_pt_br = __esm({
2866
2928
  workspaceCommandDescription: "Gerenciar workspace de estudo local",
2867
2929
  workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2868
2930
  insufficientCredits: "\u274C Voc\xEA est\xE1 sem cr\xE9ditos. Cada resposta do tutor de IA consome cr\xE9ditos para cobrir o processamento. Recarregue em https://tostudy.ai/student/credits para continuar de onde parou.\n",
2931
+ dailyAiLimit: "\u274C Voc\xEA atingiu o teto di\xE1rio de gastos com IA da sua conta. N\xE3o \xE9 falta de cr\xE9ditos: o teto reinicia \xE0 meia-noite (UTC). Para um teto maior, fa\xE7a upgrade do plano em https://tostudy.ai/student/plan.\n",
2869
2932
  workspaceRefusedAtHome: "N\xE3o d\xE1 para configurar o estudo direto na sua pasta pessoal.\nCrie uma pasta para o curso e entre nela antes de continuar:\n\n mkdir ~/meus-cursos && cd ~/meus-cursos\n",
2870
2933
  courseArchived: "Este curso foi arquivado e est\xE1 dispon\xEDvel somente para leitura.\nUse `tostudy lesson` para revisar o conte\xFAdo j\xE1 estudado.\n",
2871
2934
  noActiveCourse: "Nenhum curso ativo nesta pasta.\nVeja seus cursos com `tostudy courses` e ative um com `tostudy select <n>`.\n",
@@ -2888,6 +2951,7 @@ var init_errors_en_us = __esm({
2888
2951
  workspaceCommandDescription: "Manage local study workspace",
2889
2952
  workspaceSetupDescription: "Create the workspace structure for the active course",
2890
2953
  insufficientCredits: "\u274C You're out of credits. Every reply from the AI tutor uses credits to cover processing. Top up at https://tostudy.ai/student/credits to continue from where you stopped.\n",
2954
+ dailyAiLimit: "\u274C You have reached your account's daily AI spending limit. This is not an empty wallet: the limit resets at midnight (UTC). For a higher limit, upgrade your plan at https://tostudy.ai/student/plan.\n",
2891
2955
  workspaceRefusedAtHome: "Study can't be set up directly in your home folder.\nCreate a folder for the course and switch into it first:\n\n mkdir ~/my-courses && cd ~/my-courses\n",
2892
2956
  courseArchived: "This course has been archived and is available for reading only.\nUse `tostudy lesson` to review the content you've already studied.\n",
2893
2957
  noActiveCourse: "No active course in this folder.\nList your courses with `tostudy courses` and activate one with `tostudy select <n>`.\n",
@@ -2934,6 +2998,9 @@ function resolveErrorCode(err) {
2934
2998
  function isInsufficientCreditsError(err) {
2935
2999
  return resolveErrorCode(err).includes("INSUFFICIENT_CREDITS");
2936
3000
  }
3001
+ function isDailyAiLimitError(err) {
3002
+ return resolveErrorCode(err).includes("DAILY_AI_COST_LIMIT_EXCEEDED");
3003
+ }
2937
3004
  function isCourseArchivedError(err) {
2938
3005
  return resolveErrorCode(err).includes("COURSE_ARCHIVED");
2939
3006
  }
@@ -3531,7 +3598,7 @@ var CLI_VERSION;
3531
3598
  var init_version = __esm({
3532
3599
  "src/version.ts"() {
3533
3600
  "use strict";
3534
- CLI_VERSION = true ? "0.17.3" : "0.7.1";
3601
+ CLI_VERSION = true ? "0.17.6" : "0.7.1";
3535
3602
  }
3536
3603
  });
3537
3604
 
@@ -3697,6 +3764,26 @@ var init_cache2 = __esm({
3697
3764
 
3698
3765
  // src/commands/doctor.ts
3699
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
+ }
3700
3787
  var doctorCommand;
3701
3788
  var init_doctor = __esm({
3702
3789
  "src/commands/doctor.ts"() {
@@ -3739,10 +3826,22 @@ var init_doctor = __esm({
3739
3826
  session = await getSession();
3740
3827
  } catch {
3741
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
+ }
3742
3840
  checks["auth"] = {
3743
3841
  loggedIn: !!session,
3744
3842
  userName: session?.userName ?? null,
3745
- expiresAt: session?.expiresAt ?? null
3843
+ expiresAt: session?.expiresAt ?? null,
3844
+ serverVerdict
3746
3845
  };
3747
3846
  try {
3748
3847
  const apiUrl = session?.apiUrl ?? "https://tostudy.ai";
@@ -3842,7 +3941,17 @@ var init_doctor = __esm({
3842
3941
  console.log(` ${pnpmVersion ? "\u2713" : "\u25CB"} pnpm ${pnpmVersion ?? errs.notFound}`);
3843
3942
  console.log(` ${gitVersion ? "\u2713" : "\u25CB"} git ${gitVersion ?? errs.notFound}`);
3844
3943
  console.log("\n Autentica\xE7\xE3o");
3845
- 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
+ }
3846
3955
  if (session) {
3847
3956
  console.log(` \u2713 Usu\xE1rio ${session.userName}`);
3848
3957
  }
@@ -4711,6 +4820,7 @@ async function runStart(opts, deps = defaultDeps3) {
4711
4820
  if (ws)
4712
4821
  await deps.updateWorkspaceState(ws.workspacePath, {
4713
4822
  currentLessonId: moduleData.firstLesson.id,
4823
+ retryLessonId: void 0,
4714
4824
  currentModuleId: moduleData.module.id
4715
4825
  });
4716
4826
  if (opts.json) {
@@ -4803,6 +4913,7 @@ var init_start_next = __esm({
4803
4913
  if (ws)
4804
4914
  await updateWorkspaceState(ws.workspacePath, {
4805
4915
  currentLessonId: moduleData.firstLesson.id,
4916
+ retryLessonId: void 0,
4806
4917
  currentModuleId: moduleData.module.id
4807
4918
  });
4808
4919
  if (opts.json) {
@@ -4863,6 +4974,7 @@ var init_next = __esm({
4863
4974
  if (ws)
4864
4975
  await updateWorkspaceState(ws.workspacePath, {
4865
4976
  currentLessonId: lessonData.lesson.id,
4977
+ retryLessonId: void 0,
4866
4978
  ...lessonData.lesson.moduleId ? { currentModuleId: lessonData.lesson.moduleId } : {}
4867
4979
  });
4868
4980
  if (opts.json) {
@@ -5359,7 +5471,7 @@ __export(util_exports, {
5359
5471
  getSizableOrigin: () => getSizableOrigin,
5360
5472
  hexToUint8Array: () => hexToUint8Array,
5361
5473
  isObject: () => isObject,
5362
- isPlainObject: () => isPlainObject,
5474
+ isPlainObject: () => isPlainObject2,
5363
5475
  issue: () => issue,
5364
5476
  joinValues: () => joinValues,
5365
5477
  jsonStringifyReplacer: () => jsonStringifyReplacer,
@@ -5528,7 +5640,7 @@ function slugify2(input2) {
5528
5640
  function isObject(data) {
5529
5641
  return typeof data === "object" && data !== null && !Array.isArray(data);
5530
5642
  }
5531
- function isPlainObject(o) {
5643
+ function isPlainObject2(o) {
5532
5644
  if (isObject(o) === false)
5533
5645
  return false;
5534
5646
  const ctor = o.constructor;
@@ -5545,7 +5657,7 @@ function isPlainObject(o) {
5545
5657
  return true;
5546
5658
  }
5547
5659
  function shallowClone(o) {
5548
- if (isPlainObject(o))
5660
+ if (isPlainObject2(o))
5549
5661
  return { ...o };
5550
5662
  if (Array.isArray(o))
5551
5663
  return [...o];
@@ -5681,7 +5793,7 @@ function omit(schema, mask) {
5681
5793
  return clone(schema, def);
5682
5794
  }
5683
5795
  function extend(schema, shape) {
5684
- if (!isPlainObject(shape)) {
5796
+ if (!isPlainObject2(shape)) {
5685
5797
  throw new Error("Invalid input to extend: expected a plain object");
5686
5798
  }
5687
5799
  const checks = schema._zod.def.checks;
@@ -5704,7 +5816,7 @@ function extend(schema, shape) {
5704
5816
  return clone(schema, def);
5705
5817
  }
5706
5818
  function safeExtend(schema, shape) {
5707
- if (!isPlainObject(shape)) {
5819
+ if (!isPlainObject2(shape)) {
5708
5820
  throw new Error("Invalid input to safeExtend: expected a plain object");
5709
5821
  }
5710
5822
  const def = mergeDefs(schema._zod.def, {
@@ -7180,7 +7292,7 @@ function mergeValues(a, b) {
7180
7292
  if (a instanceof Date && b instanceof Date && +a === +b) {
7181
7293
  return { valid: true, data: a };
7182
7294
  }
7183
- if (isPlainObject(a) && isPlainObject(b)) {
7295
+ if (isPlainObject2(a) && isPlainObject2(b)) {
7184
7296
  const bKeys = Object.keys(b);
7185
7297
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
7186
7298
  const newObj = { ...a, ...b };
@@ -8374,7 +8486,7 @@ var init_schemas = __esm({
8374
8486
  $ZodType.init(inst, def);
8375
8487
  inst._zod.parse = (payload, ctx) => {
8376
8488
  const input2 = payload.value;
8377
- if (!isPlainObject(input2)) {
8489
+ if (!isPlainObject2(input2)) {
8378
8490
  payload.issues.push({
8379
8491
  expected: "record",
8380
8492
  code: "invalid_type",
@@ -20142,6 +20254,12 @@ var init_init_template = __esm({
20142
20254
  import fs11 from "node:fs";
20143
20255
  import path14 from "node:path";
20144
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
+ }
20145
20263
  var logger16, validateCommand;
20146
20264
  var init_validate = __esm({
20147
20265
  "src/commands/validate.ts"() {
@@ -20152,6 +20270,7 @@ var init_validate = __esm({
20152
20270
  init_http();
20153
20271
  init_guards();
20154
20272
  init_course_state();
20273
+ init_workspace_state();
20155
20274
  init_formatter();
20156
20275
  init_init_template();
20157
20276
  init_errors();
@@ -20165,7 +20284,8 @@ var init_validate = __esm({
20165
20284
  if (driftWarning) process.stderr.write(driftWarning + "\n");
20166
20285
  const data = createHttpProvider(session.apiUrl, session.token);
20167
20286
  const deps = { data, logger: logger16 };
20168
- let lessonId = activeCourse.currentLessonId;
20287
+ const retryLessonId = activeCourse.retryLessonId;
20288
+ let lessonId = retryLessonId ?? activeCourse.currentLessonId;
20169
20289
  if (!lessonId) {
20170
20290
  try {
20171
20291
  const prog = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
@@ -20249,8 +20369,19 @@ var init_validate = __esm({
20249
20369
  } else {
20250
20370
  output(formatValidation(result), { json: false });
20251
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
+ }
20252
20380
  process.exit(result.passed ? 0 : 1);
20253
20381
  } catch (err) {
20382
+ if (err instanceof CliApiError && err.code === "LESSON_NOT_REACHED") {
20383
+ await clearRetryPin();
20384
+ }
20254
20385
  if (isEnrollmentNotEntitledError(err)) {
20255
20386
  const friendly = getErrors().enrollmentNotEntitled;
20256
20387
  if (opts.json) jsonError("enrollment_not_entitled", { message: friendly });
@@ -20269,6 +20400,12 @@ var init_validate = __esm({
20269
20400
  error(friendly);
20270
20401
  return;
20271
20402
  }
20403
+ if (isDailyAiLimitError(err)) {
20404
+ const friendly = getErrors().dailyAiLimit;
20405
+ if (opts.json) jsonError("daily_ai_limit", { message: friendly });
20406
+ error(friendly);
20407
+ return;
20408
+ }
20272
20409
  const msg = err instanceof Error ? err.message : String(err);
20273
20410
  if (opts.json) jsonError(msg);
20274
20411
  error(msg);
@@ -20277,8 +20414,81 @@ var init_validate = __esm({
20277
20414
  }
20278
20415
  });
20279
20416
 
20280
- // src/commands/menu.ts
20417
+ // src/commands/retry.ts
20281
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";
20282
20492
  var menuCommand;
20283
20493
  var init_menu = __esm({
20284
20494
  "src/commands/menu.ts"() {
@@ -20286,7 +20496,7 @@ var init_menu = __esm({
20286
20496
  init_session_store();
20287
20497
  init_workspace_state();
20288
20498
  init_formatter();
20289
- 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 () => {
20290
20500
  const session = await getSession();
20291
20501
  const wsResult = session ? await findWorkspaceState() : null;
20292
20502
  const activeCourse = wsResult?.state ?? null;
@@ -20463,7 +20673,7 @@ var init_learner_context = __esm({
20463
20673
  });
20464
20674
 
20465
20675
  // src/commands/init.ts
20466
- import { Command as Command16 } from "commander";
20676
+ import { Command as Command17 } from "commander";
20467
20677
  function isCompleteProfile(flags) {
20468
20678
  return Boolean(
20469
20679
  flags.segment && flags.company && flags.products && flags.region && flags.team && flags.goal && flags.level
@@ -20634,7 +20844,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
20634
20844
  deps.output(artifacts.learnerBrief, { json: false });
20635
20845
  }
20636
20846
  }
20637
- var logger17, defaultDeps4, initCommand;
20847
+ var logger18, defaultDeps4, initCommand;
20638
20848
  var init_init = __esm({
20639
20849
  "src/commands/init.ts"() {
20640
20850
  "use strict";
@@ -20651,7 +20861,7 @@ var init_init = __esm({
20651
20861
  init_instruction_pipeline();
20652
20862
  init_root_agents_consent();
20653
20863
  init_pipeline_deps();
20654
- logger17 = createLogger("cli:init");
20864
+ logger18 = createLogger("cli:init");
20655
20865
  defaultDeps4 = {
20656
20866
  // GH #1419 item 3 — `init` used the raw session, so an expired access
20657
20867
  // token met the POST as a 401. Refresh through the guard when one exists.
@@ -20671,12 +20881,12 @@ var init_init = __esm({
20671
20881
  saveCourseLearnerProfile,
20672
20882
  buildInitArtifacts,
20673
20883
  output,
20674
- logger: logger17,
20884
+ logger: logger18,
20675
20885
  createHttpProvider,
20676
20886
  resolveAndGenerate,
20677
20887
  buildPipelineDeps
20678
20888
  };
20679
- 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(
20680
20890
  "--root-agents",
20681
20891
  "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
20682
20892
  ).option("--json", "Output structured JSON").action(async (opts) => {
@@ -21004,11 +21214,11 @@ var init_workspace = __esm({
21004
21214
  });
21005
21215
 
21006
21216
  // src/commands/workspace.ts
21007
- import { Command as Command17 } from "commander";
21217
+ import { Command as Command18 } from "commander";
21008
21218
  import path17 from "node:path";
21009
21219
  import os8 from "node:os";
21010
21220
  import fs14 from "node:fs/promises";
21011
- var logger18, workspaceCommand;
21221
+ var logger19, workspaceCommand;
21012
21222
  var init_workspace2 = __esm({
21013
21223
  "src/commands/workspace.ts"() {
21014
21224
  "use strict";
@@ -21018,8 +21228,8 @@ var init_workspace2 = __esm({
21018
21228
  init_course_state();
21019
21229
  init_resolve();
21020
21230
  init_errors();
21021
- logger18 = createLogger("cli:workspace");
21022
- workspaceCommand = new Command17("workspace").description(
21231
+ logger19 = createLogger("cli:workspace");
21232
+ workspaceCommand = new Command18("workspace").description(
21023
21233
  getErrors().workspaceCommandDescription
21024
21234
  );
21025
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) => {
@@ -21084,7 +21294,7 @@ Pr\xF3ximo passo: tostudy export
21084
21294
  );
21085
21295
  }
21086
21296
  } catch (err) {
21087
- logger18.error("workspace setup failed", { error: err });
21297
+ logger19.error("workspace setup failed", { error: err });
21088
21298
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21089
21299
  `);
21090
21300
  process.exit(1);
@@ -21188,11 +21398,11 @@ Pr\xF3ximo passo: tostudy export
21188
21398
  });
21189
21399
 
21190
21400
  // src/commands/export.ts
21191
- import { Command as Command18 } from "commander";
21401
+ import { Command as Command19 } from "commander";
21192
21402
  import path18 from "node:path";
21193
21403
  import os9 from "node:os";
21194
21404
  import fs15 from "node:fs/promises";
21195
- var logger19, exportCommand;
21405
+ var logger20, exportCommand;
21196
21406
  var init_export = __esm({
21197
21407
  "src/commands/export.ts"() {
21198
21408
  "use strict";
@@ -21203,8 +21413,8 @@ var init_export = __esm({
21203
21413
  init_course_state();
21204
21414
  init_resolve();
21205
21415
  init_errors();
21206
- logger19 = createLogger("cli:export");
21207
- 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) => {
21208
21418
  try {
21209
21419
  const session = await requireSession();
21210
21420
  const activeCourse = await requireActiveCourse();
@@ -21231,7 +21441,7 @@ var init_export = __esm({
21231
21441
  }
21232
21442
  if (!hasConfig) {
21233
21443
  const slug = courseSlug(activeCourse.courseTitle);
21234
- logger19.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
21444
+ logger20.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
21235
21445
  await fs15.mkdir(ws.workspacePath, { recursive: true });
21236
21446
  for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
21237
21447
  await fs15.mkdir(path18.join(ws.workspacePath, dir), { recursive: true });
@@ -21296,7 +21506,7 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
21296
21506
  );
21297
21507
  }
21298
21508
  } catch (err) {
21299
- logger19.error("export failed", { error: err });
21509
+ logger20.error("export failed", { error: err });
21300
21510
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21301
21511
  `);
21302
21512
  process.exit(1);
@@ -21306,11 +21516,11 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
21306
21516
  });
21307
21517
 
21308
21518
  // src/commands/open.ts
21309
- import { Command as Command19 } from "commander";
21519
+ import { Command as Command20 } from "commander";
21310
21520
  import { execFile as execFile3 } from "node:child_process";
21311
21521
  import path19 from "node:path";
21312
21522
  import os10 from "node:os";
21313
- var logger20, openCommand;
21523
+ var logger21, openCommand;
21314
21524
  var init_open = __esm({
21315
21525
  "src/commands/open.ts"() {
21316
21526
  "use strict";
@@ -21319,8 +21529,8 @@ var init_open = __esm({
21319
21529
  init_course_state();
21320
21530
  init_resolve();
21321
21531
  init_errors();
21322
- logger20 = createLogger("cli:open");
21323
- 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) => {
21324
21534
  try {
21325
21535
  const activeCourse = await requireActiveCourse();
21326
21536
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -21337,7 +21547,7 @@ var init_open = __esm({
21337
21547
  const editor = process.env["EDITOR"] ?? "code";
21338
21548
  execFile3(editor, [ws.workspacePath], (err) => {
21339
21549
  if (err) {
21340
- logger20.error("open failed", { editor, workspacePath: ws.workspacePath });
21550
+ logger21.error("open failed", { editor, workspacePath: ws.workspacePath });
21341
21551
  process.stderr.write(`\u274C Falha ao abrir: ${err.message}
21342
21552
  `);
21343
21553
  process.exit(1);
@@ -21346,7 +21556,7 @@ var init_open = __esm({
21346
21556
  `);
21347
21557
  });
21348
21558
  } catch (err) {
21349
- logger20.error("open command failed", { error: err });
21559
+ logger21.error("open command failed", { error: err });
21350
21560
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21351
21561
  `);
21352
21562
  process.exit(1);
@@ -21415,11 +21625,11 @@ var init_vault = __esm({
21415
21625
  });
21416
21626
 
21417
21627
  // src/commands/vault.ts
21418
- import { Command as Command20 } from "commander";
21628
+ import { Command as Command21 } from "commander";
21419
21629
  import path21 from "node:path";
21420
21630
  import os11 from "node:os";
21421
21631
  import fs17 from "node:fs/promises";
21422
- var logger21, vaultCommand;
21632
+ var logger22, vaultCommand;
21423
21633
  var init_vault2 = __esm({
21424
21634
  "src/commands/vault.ts"() {
21425
21635
  "use strict";
@@ -21432,8 +21642,8 @@ var init_vault2 = __esm({
21432
21642
  init_course_state();
21433
21643
  init_resolve();
21434
21644
  init_errors();
21435
- logger21 = createLogger("cli:vault");
21436
- 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");
21437
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) => {
21438
21648
  try {
21439
21649
  const session = await requireSession();
@@ -21476,7 +21686,7 @@ var init_vault2 = __esm({
21476
21686
  cliWorkspacePaths(workspacePath, activeCourse.courseTitle)
21477
21687
  );
21478
21688
  const result = await writeVaultFiles(files, vaultOutputPath, activeCourse.courseId, slug);
21479
- logger21.info("Vault generated", {
21689
+ logger22.info("Vault generated", {
21480
21690
  courseId: activeCourse.courseId,
21481
21691
  vaultPath: result.vaultPath,
21482
21692
  filesWritten: result.filesWritten
@@ -21509,7 +21719,7 @@ Para visualizar:
21509
21719
  );
21510
21720
  }
21511
21721
  } catch (err) {
21512
- logger21.error("vault init failed", { error: err });
21722
+ logger22.error("vault init failed", { error: err });
21513
21723
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21514
21724
  `);
21515
21725
  process.exit(1);
@@ -21539,7 +21749,7 @@ Para visualizar:
21539
21749
  process.exit(1);
21540
21750
  }
21541
21751
  const data = createHttpProvider(session.apiUrl, session.token);
21542
- const deps = { data, logger: logger21 };
21752
+ const deps = { data, logger: logger22 };
21543
21753
  const progress = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
21544
21754
  const markerPath = path21.join(vaultPath, ".ana-vault.json");
21545
21755
  const markerRaw = await fs17.readFile(markerPath, "utf-8");
@@ -21593,7 +21803,7 @@ Para visualizar:
21593
21803
  );
21594
21804
  }
21595
21805
  } catch (err) {
21596
- logger21.error("vault sync failed", { error: err });
21806
+ logger22.error("vault sync failed", { error: err });
21597
21807
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
21598
21808
  `);
21599
21809
  process.exit(1);
@@ -21603,7 +21813,7 @@ Para visualizar:
21603
21813
  });
21604
21814
 
21605
21815
  // src/commands/profile.ts
21606
- import { Command as Command21 } from "commander";
21816
+ import { Command as Command22 } from "commander";
21607
21817
  var profileCommand;
21608
21818
  var init_profile = __esm({
21609
21819
  "src/commands/profile.ts"() {
@@ -21611,7 +21821,7 @@ var init_profile = __esm({
21611
21821
  init_guards();
21612
21822
  init_course_state();
21613
21823
  init_user_profile();
21614
- 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) => {
21615
21825
  const activeCourse = await requireActiveCourse();
21616
21826
  const onboarding = await getCourseOnboardingState(activeCourse.courseId);
21617
21827
  const profile = onboarding?.learnerProfile ?? await getUserProfile();
@@ -21672,8 +21882,8 @@ var init_profile = __esm({
21672
21882
  });
21673
21883
 
21674
21884
  // src/commands/sync.ts
21675
- import { Command as Command22 } from "commander";
21676
- var logger22, syncCommand;
21885
+ import { Command as Command23 } from "commander";
21886
+ var logger23, syncCommand;
21677
21887
  var init_sync = __esm({
21678
21888
  "src/commands/sync.ts"() {
21679
21889
  "use strict";
@@ -21684,8 +21894,8 @@ var init_sync = __esm({
21684
21894
  init_workspace_state();
21685
21895
  init_root_agents_consent();
21686
21896
  init_pipeline_deps();
21687
- logger22 = createLogger("cli:sync");
21688
- 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(
21689
21899
  "--all-runtimes",
21690
21900
  "Write instruction files for every supported runtime, even undetected ones"
21691
21901
  ).option(
@@ -21749,7 +21959,7 @@ var init_sync = __esm({
21749
21959
  } catch (err) {
21750
21960
  const msg = err instanceof Error ? err.message : String(err);
21751
21961
  if (msg.includes("process.exit")) return;
21752
- logger22.warn("sync failed", { error: msg });
21962
+ logger23.warn("sync failed", { error: msg });
21753
21963
  error(msg);
21754
21964
  }
21755
21965
  });
@@ -21757,8 +21967,8 @@ var init_sync = __esm({
21757
21967
  });
21758
21968
 
21759
21969
  // src/commands/brief.ts
21760
- import { Command as Command23 } from "commander";
21761
- var logger23, briefCommand;
21970
+ import { Command as Command24 } from "commander";
21971
+ var logger24, briefCommand;
21762
21972
  var init_brief = __esm({
21763
21973
  "src/commands/brief.ts"() {
21764
21974
  "use strict";
@@ -21767,8 +21977,8 @@ var init_brief = __esm({
21767
21977
  init_cache();
21768
21978
  init_api();
21769
21979
  init_formatter();
21770
- logger23 = createLogger("cli:brief");
21771
- 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) => {
21772
21982
  try {
21773
21983
  const session = await requireSession();
21774
21984
  const cached2 = await readBriefCache();
@@ -21801,7 +22011,7 @@ var init_brief = __esm({
21801
22011
  }
21802
22012
  output(lines.join("\n"), { json: false });
21803
22013
  } catch (err) {
21804
- logger23.error("Failed to show brief", { err });
22014
+ logger24.error("Failed to show brief", { err });
21805
22015
  error(`Erro ao buscar brief: ${err instanceof Error ? err.message : String(err)}`);
21806
22016
  }
21807
22017
  });
@@ -21809,8 +22019,8 @@ var init_brief = __esm({
21809
22019
  });
21810
22020
 
21811
22021
  // src/commands/brief-create.ts
21812
- import { Command as Command24 } from "commander";
21813
- var logger24, briefCreateCommand;
22022
+ import { Command as Command25 } from "commander";
22023
+ var logger25, briefCreateCommand;
21814
22024
  var init_brief_create = __esm({
21815
22025
  "src/commands/brief-create.ts"() {
21816
22026
  "use strict";
@@ -21820,8 +22030,8 @@ var init_brief_create = __esm({
21820
22030
  init_api();
21821
22031
  init_cache();
21822
22032
  init_formatter();
21823
- logger24 = createLogger("cli:brief-create");
21824
- 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 () => {
21825
22035
  try {
21826
22036
  const session = await requireSession();
21827
22037
  const answers = await collectBootstrapAnswers({ userName: session.userName });
@@ -21845,7 +22055,7 @@ var init_brief_create = __esm({
21845
22055
  ];
21846
22056
  output(doneLines.join("\n"), { json: false });
21847
22057
  } catch (err) {
21848
- logger24.error("Failed to create brief", { err });
22058
+ logger25.error("Failed to create brief", { err });
21849
22059
  error(`Erro ao criar brief: ${err instanceof Error ? err.message : String(err)}`);
21850
22060
  }
21851
22061
  });
@@ -21853,7 +22063,7 @@ var init_brief_create = __esm({
21853
22063
  });
21854
22064
 
21855
22065
  // src/commands/brief-open.ts
21856
- import { Command as Command25 } from "commander";
22066
+ import { Command as Command26 } from "commander";
21857
22067
  import { execFile as execFile4 } from "node:child_process";
21858
22068
  import { platform } from "node:process";
21859
22069
  function openUrl(url2) {
@@ -21882,7 +22092,7 @@ var init_brief_open = __esm({
21882
22092
  init_guards();
21883
22093
  init_formatter();
21884
22094
  BRIEF_URL = "https://tostudy.ai/student/settings/learner-brief";
21885
- 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 () => {
21886
22096
  await requireSession();
21887
22097
  output(`Abrindo ${BRIEF_URL} no navegador...`, { json: false });
21888
22098
  openUrl(BRIEF_URL);
@@ -21910,7 +22120,7 @@ async function saveModuleSummary(workspacePath, input2) {
21910
22120
  ""
21911
22121
  ].join("\n");
21912
22122
  fs18.writeFileSync(filePath, header + input2.summary, { mode: 384 });
21913
- logger25.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
22123
+ logger26.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
21914
22124
  return filePath;
21915
22125
  }
21916
22126
  async function loadSessionContext(workspacePath) {
@@ -21931,19 +22141,19 @@ async function loadSessionContext(workspacePath) {
21931
22141
  }
21932
22142
  return { moduleSummaries: summaries };
21933
22143
  }
21934
- var logger25;
22144
+ var logger26;
21935
22145
  var init_storage = __esm({
21936
22146
  "src/sessions/storage.ts"() {
21937
22147
  "use strict";
21938
22148
  init_dist();
21939
- logger25 = createLogger("cli:sessions");
22149
+ logger26 = createLogger("cli:sessions");
21940
22150
  }
21941
22151
  });
21942
22152
 
21943
22153
  // src/commands/compact.ts
21944
22154
  import fs19 from "node:fs";
21945
- import { Command as Command26 } from "commander";
21946
- var logger26, compactCommand;
22155
+ import { Command as Command27 } from "commander";
22156
+ var logger27, compactCommand;
21947
22157
  var init_compact = __esm({
21948
22158
  "src/commands/compact.ts"() {
21949
22159
  "use strict";
@@ -21952,8 +22162,8 @@ var init_compact = __esm({
21952
22162
  init_workspace_state();
21953
22163
  init_storage();
21954
22164
  init_formatter();
21955
- logger26 = createLogger("cli:compact");
21956
- 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) => {
21957
22167
  try {
21958
22168
  const activeCourse = await requireActiveCourse();
21959
22169
  const ws = await findWorkspaceState();
@@ -21973,7 +22183,7 @@ var init_compact = __esm({
21973
22183
  moduleTitle,
21974
22184
  summary
21975
22185
  });
21976
- logger26.debug("Compact summary saved", { moduleId, filePath });
22186
+ logger27.debug("Compact summary saved", { moduleId, filePath });
21977
22187
  if (opts.json) {
21978
22188
  output({ saved: true, path: filePath, moduleId }, { json: true });
21979
22189
  } else {
@@ -21982,7 +22192,7 @@ var init_compact = __esm({
21982
22192
  } catch (err) {
21983
22193
  const msg = err instanceof Error ? err.message : String(err);
21984
22194
  if (msg.includes("process.exit")) return;
21985
- logger26.warn("compact failed", { error: msg });
22195
+ logger27.warn("compact failed", { error: msg });
21986
22196
  if (opts.json) jsonError(msg);
21987
22197
  error(msg);
21988
22198
  }
@@ -21991,8 +22201,8 @@ var init_compact = __esm({
21991
22201
  });
21992
22202
 
21993
22203
  // src/commands/context.ts
21994
- import { Command as Command27 } from "commander";
21995
- var logger27, contextCommand;
22204
+ import { Command as Command28 } from "commander";
22205
+ var logger28, contextCommand;
21996
22206
  var init_context = __esm({
21997
22207
  "src/commands/context.ts"() {
21998
22208
  "use strict";
@@ -22002,8 +22212,8 @@ var init_context = __esm({
22002
22212
  init_course_state();
22003
22213
  init_formatter();
22004
22214
  init_errors();
22005
- logger27 = createLogger("cli:context");
22006
- 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) => {
22007
22217
  try {
22008
22218
  const ws = await findWorkspaceState();
22009
22219
  if (!ws) {
@@ -22033,7 +22243,7 @@ var init_context = __esm({
22033
22243
  totalModulesCompleted: sessionCtx.moduleSummaries.length,
22034
22244
  driftWarning: driftWarning ?? null
22035
22245
  };
22036
- logger27.debug("Context loaded", {
22246
+ logger28.debug("Context loaded", {
22037
22247
  courseId: ws.state.courseId,
22038
22248
  moduleSummaries: sessionCtx.moduleSummaries.length
22039
22249
  });
@@ -22047,7 +22257,7 @@ var init_context = __esm({
22047
22257
  } catch (err) {
22048
22258
  const msg = err instanceof Error ? err.message : String(err);
22049
22259
  if (msg.includes("process.exit")) return;
22050
- logger27.warn("context failed", { error: msg });
22260
+ logger28.warn("context failed", { error: msg });
22051
22261
  if (opts.json) jsonError(msg);
22052
22262
  error(msg);
22053
22263
  }
@@ -22056,8 +22266,8 @@ var init_context = __esm({
22056
22266
  });
22057
22267
 
22058
22268
  // src/commands/memory.ts
22059
- import { Command as Command28 } from "commander";
22060
- var logger28, memoryCommand;
22269
+ import { Command as Command29 } from "commander";
22270
+ var logger29, memoryCommand;
22061
22271
  var init_memory = __esm({
22062
22272
  "src/commands/memory.ts"() {
22063
22273
  "use strict";
@@ -22067,8 +22277,8 @@ var init_memory = __esm({
22067
22277
  init_guards();
22068
22278
  init_formatter();
22069
22279
  init_errors();
22070
- logger28 = createLogger("cli:memory");
22071
- 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) => {
22072
22282
  try {
22073
22283
  const ws = await findWorkspaceState();
22074
22284
  if (!ws) {
@@ -22091,7 +22301,7 @@ var init_memory = __esm({
22091
22301
  } catch (err) {
22092
22302
  const msg = err instanceof Error ? err.message : String(err);
22093
22303
  if (msg.includes("process.exit")) return;
22094
- logger28.warn("memory failed", { error: msg });
22304
+ logger29.warn("memory failed", { error: msg });
22095
22305
  if (opts.json) jsonError(msg);
22096
22306
  else error(msg);
22097
22307
  }
@@ -22100,8 +22310,8 @@ var init_memory = __esm({
22100
22310
  });
22101
22311
 
22102
22312
  // src/commands/insight.ts
22103
- import { Command as Command29 } from "commander";
22104
- var logger29, VALID_TYPES, insightCommand;
22313
+ import { Command as Command30 } from "commander";
22314
+ var logger30, VALID_TYPES, insightCommand;
22105
22315
  var init_insight = __esm({
22106
22316
  "src/commands/insight.ts"() {
22107
22317
  "use strict";
@@ -22111,9 +22321,9 @@ var init_insight = __esm({
22111
22321
  init_guards();
22112
22322
  init_formatter();
22113
22323
  init_errors();
22114
- logger29 = createLogger("cli:insight");
22324
+ logger30 = createLogger("cli:insight");
22115
22325
  VALID_TYPES = ["difficulty", "breakthrough", "question"];
22116
- insightCommand = new Command29("insight").description(
22326
+ insightCommand = new Command30("insight").description(
22117
22327
  "Persist a student cognitive insight (difficulty | breakthrough | question) into course memory"
22118
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) => {
22119
22329
  try {
@@ -22153,7 +22363,7 @@ var init_insight = __esm({
22153
22363
  } catch (err) {
22154
22364
  const msg = err instanceof Error ? err.message : String(err);
22155
22365
  if (msg.includes("process.exit")) return;
22156
- logger29.warn("insight failed", { error: msg });
22366
+ logger30.warn("insight failed", { error: msg });
22157
22367
  if (opts.json) jsonError(msg);
22158
22368
  else error(msg);
22159
22369
  }
@@ -22162,11 +22372,11 @@ var init_insight = __esm({
22162
22372
  });
22163
22373
 
22164
22374
  // src/commands/level.ts
22165
- import { Command as Command30 } from "commander";
22375
+ import { Command as Command31 } from "commander";
22166
22376
  function isExerciseLevel2(value) {
22167
22377
  return ALL_LEVELS.includes(value);
22168
22378
  }
22169
- var logger30, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
22379
+ var logger31, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
22170
22380
  var init_level = __esm({
22171
22381
  "src/commands/level.ts"() {
22172
22382
  "use strict";
@@ -22175,7 +22385,7 @@ var init_level = __esm({
22175
22385
  init_exercises();
22176
22386
  init_guards();
22177
22387
  init_formatter();
22178
- logger30 = createLogger("cli:level");
22388
+ logger31 = createLogger("cli:level");
22179
22389
  ALL_LEVELS = ["L0", "L1", "L2", "L3", "L4"];
22180
22390
  LEVEL_LABELS2 = {
22181
22391
  L0: "Pr\xE9-check (perguntas conceituais)",
@@ -22184,12 +22394,12 @@ var init_level = __esm({
22184
22394
  L3: "Guiado (passo a passo + checkpoints)",
22185
22395
  L4: "Desafio livre (folha em branco)"
22186
22396
  };
22187
- 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) => {
22188
22398
  try {
22189
22399
  const session = await requireSession();
22190
22400
  const activeCourse = await requireActiveCourse();
22191
22401
  const data = createHttpProvider(session.apiUrl, session.token);
22192
- const deps = { data, logger: logger30 };
22402
+ const deps = { data, logger: logger31 };
22193
22403
  if (!rawLevel) {
22194
22404
  const levels = await getEnrollmentLevels({ enrollmentId: activeCourse.enrollmentId }, deps);
22195
22405
  if (opts.json) {
@@ -22247,8 +22457,8 @@ var init_level = __esm({
22247
22457
  });
22248
22458
 
22249
22459
  // src/commands/theory.ts
22250
- import { Command as Command31 } from "commander";
22251
- var logger31, EXERCISE_LEVELS2, theoryCommand;
22460
+ import { Command as Command32 } from "commander";
22461
+ var logger32, EXERCISE_LEVELS2, theoryCommand;
22252
22462
  var init_theory = __esm({
22253
22463
  "src/commands/theory.ts"() {
22254
22464
  "use strict";
@@ -22256,9 +22466,9 @@ var init_theory = __esm({
22256
22466
  init_guards();
22257
22467
  init_formatter();
22258
22468
  init_errors();
22259
- logger31 = createLogger("cli:theory");
22469
+ logger32 = createLogger("cli:theory");
22260
22470
  EXERCISE_LEVELS2 = ["L0", "L1", "L2", "L3", "L4"];
22261
- 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) => {
22262
22472
  try {
22263
22473
  const session = await requireSession();
22264
22474
  const active = await requireActiveCourse();
@@ -22295,7 +22505,7 @@ var init_theory = __esm({
22295
22505
  currentLevel = levels.currentLevel;
22296
22506
  }
22297
22507
  const requestUrl = `${session.apiUrl}/api/cli/exercises/more-theory`;
22298
- logger31.debug("Requesting more theory", { lessonId, currentLevel });
22508
+ logger32.debug("Requesting more theory", { lessonId, currentLevel });
22299
22509
  const res = await fetch(requestUrl, {
22300
22510
  method: "POST",
22301
22511
  headers: {
@@ -22316,6 +22526,11 @@ var init_theory = __esm({
22316
22526
  if (opts.json) jsonError("insufficient_credits", { message: friendly });
22317
22527
  error(friendly);
22318
22528
  }
22529
+ if (body.error === "DAILY_AI_COST_LIMIT_EXCEEDED") {
22530
+ const friendly = getErrors().dailyAiLimit;
22531
+ if (opts.json) jsonError("daily_ai_limit", { message: friendly });
22532
+ error(friendly);
22533
+ }
22319
22534
  const msg = body.error ?? body.message ?? `Falha ao pedir teoria (${res.status})`;
22320
22535
  if (opts.json) jsonError("request_failed", { message: msg, code: res.status });
22321
22536
  error(msg);
@@ -22333,6 +22548,12 @@ var init_theory = __esm({
22333
22548
  error(friendly);
22334
22549
  return;
22335
22550
  }
22551
+ if (isDailyAiLimitError(err)) {
22552
+ const friendly = getErrors().dailyAiLimit;
22553
+ if (opts.json) jsonError("daily_ai_limit", { message: friendly });
22554
+ error(friendly);
22555
+ return;
22556
+ }
22336
22557
  const msg = err instanceof Error ? err.message : String(err);
22337
22558
  if (opts.json) jsonError("unexpected_error", { message: msg });
22338
22559
  error(msg);
@@ -22618,7 +22839,7 @@ var init_resolve_enrollment = __esm({
22618
22839
  });
22619
22840
 
22620
22841
  // src/commands/export-grades.ts
22621
- import { Command as Command32 } from "commander";
22842
+ import { Command as Command33 } from "commander";
22622
22843
  function isFormat(value) {
22623
22844
  return SUPPORTED_FORMATS.includes(value);
22624
22845
  }
@@ -22705,7 +22926,7 @@ async function runExportGrades(opts, deps = defaultDeps5) {
22705
22926
  if (!rendered.endsWith("\n")) rendered += "\n";
22706
22927
  deps.stdoutWrite(rendered);
22707
22928
  }
22708
- var logger32, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
22929
+ var logger33, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
22709
22930
  var init_export_grades = __esm({
22710
22931
  "src/commands/export-grades.ts"() {
22711
22932
  "use strict";
@@ -22713,7 +22934,7 @@ var init_export_grades = __esm({
22713
22934
  init_guards();
22714
22935
  init_grades();
22715
22936
  init_resolve_enrollment();
22716
- logger32 = createLogger("cli:export-grades");
22937
+ logger33 = createLogger("cli:export-grades");
22717
22938
  SUPPORTED_FORMATS = ["json", "csv", "md"];
22718
22939
  defaultDeps5 = {
22719
22940
  requireSession,
@@ -22726,10 +22947,10 @@ var init_export_grades = __esm({
22726
22947
  stderrWrite: (message) => process.stderr.write(message),
22727
22948
  // process.exit is typed as `never` — wrap so the cast is local to one line.
22728
22949
  exit: (code) => process.exit(code),
22729
- logger: logger32,
22950
+ logger: logger33,
22730
22951
  resolveBySlug: resolveEnrollmentBySlug
22731
22952
  };
22732
- 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(
22733
22954
  "--course <slug>",
22734
22955
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, exporta o curso ativo."
22735
22956
  ).option("--format <json|csv|md>", "Formato de sa\xEDda (default: json)", "json").addHelpText(
@@ -22754,7 +22975,7 @@ C\xF3digos de sa\xEDda:
22754
22975
  });
22755
22976
 
22756
22977
  // src/commands/grades.ts
22757
- import { Command as Command33 } from "commander";
22978
+ import { Command as Command34 } from "commander";
22758
22979
  async function fetchAttempts(session, enrollmentId, deps) {
22759
22980
  const url2 = `${session.apiUrl}/api/cli/validations/by-enrollment?enrollmentId=${encodeURIComponent(
22760
22981
  enrollmentId
@@ -22789,15 +23010,13 @@ function summarizeAttempts(courseTitle, attempts) {
22789
23010
  };
22790
23011
  }
22791
23012
  let passedCount = 0;
22792
- let scoreSum = 0;
22793
- let scoreCount = 0;
22794
23013
  let latestTs = -Infinity;
22795
23014
  let latestAt = attempts[0].attemptedAt;
23015
+ const bestByLesson = /* @__PURE__ */ new Map();
22796
23016
  for (const a of attempts) {
22797
23017
  if (a.passed) passedCount++;
22798
23018
  if (a.score !== null) {
22799
- scoreSum += a.score;
22800
- scoreCount++;
23019
+ bestByLesson.set(a.lessonId, Math.max(a.score, bestByLesson.get(a.lessonId) ?? -Infinity));
22801
23020
  }
22802
23021
  const t = (a.attemptedAt instanceof Date ? a.attemptedAt : new Date(a.attemptedAt)).getTime();
22803
23022
  if (t > latestTs) {
@@ -22805,13 +23024,14 @@ function summarizeAttempts(courseTitle, attempts) {
22805
23024
  latestAt = a.attemptedAt;
22806
23025
  }
22807
23026
  }
23027
+ const bestScores = [...bestByLesson.values()];
22808
23028
  return {
22809
23029
  courseTitle,
22810
23030
  lastAttemptAt: latestAt,
22811
23031
  attemptsCount: attempts.length,
22812
23032
  passedCount,
22813
23033
  failedCount: attempts.length - passedCount,
22814
- averageScore: scoreCount === 0 ? null : scoreSum / scoreCount
23034
+ averageScore: bestScores.length === 0 ? null : bestScores.reduce((a, b) => a + b, 0) / bestScores.length
22815
23035
  };
22816
23036
  }
22817
23037
  async function runGrades(opts, deps = defaultDeps6) {
@@ -22895,7 +23115,7 @@ async function runGrades(opts, deps = defaultDeps6) {
22895
23115
  );
22896
23116
  deps.stdoutWrite(formatGradesAllEnrollmentsTable(summaries) + "\n");
22897
23117
  }
22898
- var logger33, defaultDeps6, gradesCommand;
23118
+ var logger34, defaultDeps6, gradesCommand;
22899
23119
  var init_grades2 = __esm({
22900
23120
  "src/commands/grades.ts"() {
22901
23121
  "use strict";
@@ -22905,7 +23125,7 @@ var init_grades2 = __esm({
22905
23125
  init_guards();
22906
23126
  init_grades();
22907
23127
  init_resolve_enrollment();
22908
- logger33 = createLogger("cli:grades");
23128
+ logger34 = createLogger("cli:grades");
22909
23129
  defaultDeps6 = {
22910
23130
  requireSession,
22911
23131
  // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
@@ -22915,12 +23135,12 @@ var init_grades2 = __esm({
22915
23135
  stdoutWrite: (message) => process.stdout.write(message),
22916
23136
  stderrWrite: (message) => process.stderr.write(message),
22917
23137
  exit: (code) => process.exit(code),
22918
- logger: logger33,
23138
+ logger: logger34,
22919
23139
  resolveBySlug: resolveEnrollmentBySlug,
22920
23140
  buildDataProvider: createHttpProvider,
22921
23141
  listCoursesFn: listCourses
22922
23142
  };
22923
- 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(
22924
23144
  "--course <slug>",
22925
23145
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, lista todos os cursos."
22926
23146
  ).addHelpText(
@@ -22944,7 +23164,7 @@ C\xF3digos de sa\xEDda:
22944
23164
  });
22945
23165
 
22946
23166
  // src/commands/review.ts
22947
- import { Command as Command34 } from "commander";
23167
+ import { Command as Command35 } from "commander";
22948
23168
  import { createInterface } from "node:readline/promises";
22949
23169
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
22950
23170
  async function defaultPromptLessonChoice(lessonIds) {
@@ -23080,7 +23300,7 @@ async function runReview(slug, opts, deps = defaultDeps7) {
23080
23300
  }) + "\n"
23081
23301
  );
23082
23302
  }
23083
- var logger34, defaultDeps7, reviewCommand;
23303
+ var logger35, defaultDeps7, reviewCommand;
23084
23304
  var init_review2 = __esm({
23085
23305
  "src/commands/review.ts"() {
23086
23306
  "use strict";
@@ -23088,7 +23308,7 @@ var init_review2 = __esm({
23088
23308
  init_guards();
23089
23309
  init_grades();
23090
23310
  init_resolve_enrollment();
23091
- logger34 = createLogger("cli:review");
23311
+ logger35 = createLogger("cli:review");
23092
23312
  defaultDeps7 = {
23093
23313
  requireSession,
23094
23314
  // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
@@ -23098,11 +23318,11 @@ var init_review2 = __esm({
23098
23318
  stdoutWrite: (message) => process.stdout.write(message),
23099
23319
  stderrWrite: (message) => process.stderr.write(message),
23100
23320
  exit: (code) => process.exit(code),
23101
- logger: logger34,
23321
+ logger: logger35,
23102
23322
  resolveBySlug: resolveEnrollmentBySlug,
23103
23323
  promptLessonChoice: defaultPromptLessonChoice
23104
23324
  };
23105
- 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(
23106
23326
  "after",
23107
23327
  `
23108
23328
  Exemplos:
@@ -23131,9 +23351,9 @@ __export(cli_exports, {
23131
23351
  CLI_VERSION: () => CLI_VERSION,
23132
23352
  createProgram: () => createProgram
23133
23353
  });
23134
- import { Command as Command35 } from "commander";
23354
+ import { Command as Command36 } from "commander";
23135
23355
  function createProgram() {
23136
- const program2 = new Command35();
23356
+ const program2 = new Command36();
23137
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(
23138
23358
  "before",
23139
23359
  [
@@ -23159,6 +23379,7 @@ function createProgram() {
23159
23379
  program2.addCommand(knowledgeCommand);
23160
23380
  program2.addCommand(hintCommand);
23161
23381
  program2.addCommand(validateCommand);
23382
+ program2.addCommand(retryCommand);
23162
23383
  program2.addCommand(levelCommand);
23163
23384
  program2.addCommand(theoryCommand);
23164
23385
  program2.addCommand(menuCommand);
@@ -23197,6 +23418,7 @@ var init_cli = __esm({
23197
23418
  init_knowledge();
23198
23419
  init_hint();
23199
23420
  init_validate();
23421
+ init_retry();
23200
23422
  init_menu();
23201
23423
  init_init();
23202
23424
  init_workspace2();