@tostudy-ai/cli 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,11 +5,20 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ var __esm = (fn, res, err) => function __init() {
9
+ if (err) throw err[0];
10
+ try {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err = [e], e;
14
+ }
10
15
  };
11
16
  var __commonJS = (cb, mod) => function __require() {
12
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ try {
18
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
+ } catch (e) {
20
+ throw mod = 0, e;
21
+ }
13
22
  };
14
23
  var __export = (target, all) => {
15
24
  for (var name in all)
@@ -104,7 +113,7 @@ var require_picocolors = __commonJS({
104
113
  }
105
114
  });
106
115
 
107
- // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-CG4i4G4l.mjs
116
+ // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-DJVL2Rjl.mjs
108
117
  function formatJson(entry) {
109
118
  const { timestamp: timestamp2, level, message, service, environment, context, data, error: error49 } = entry;
110
119
  const output3 = {
@@ -226,18 +235,24 @@ function isSensitiveKey(key) {
226
235
  ].some((p) => lowerKey.includes(p));
227
236
  });
228
237
  }
229
- function serializeError(error49) {
238
+ function serializeError(error49, visited = /* @__PURE__ */ new WeakSet()) {
230
239
  const serialized = {
231
240
  name: error49.name,
232
241
  message: error49.message
233
242
  };
234
243
  if (error49.stack) serialized.stack = error49.stack;
235
244
  if ("code" in error49 && typeof error49.code === "string") serialized.code = error49.code;
236
- if (error49.cause instanceof Error) serialized.cause = serializeError(error49.cause);
245
+ visited.add(error49);
246
+ if (error49.cause instanceof Error) serialized.cause = visited.has(error49.cause) ? {
247
+ name: error49.cause.name,
248
+ message: "[Circular]"
249
+ } : serializeError(error49.cause, visited);
237
250
  return serialized;
238
251
  }
239
- function object(obj, additionalSensitiveKeys) {
252
+ function object(obj, additionalSensitiveKeys, visited = /* @__PURE__ */ new WeakSet()) {
240
253
  if (!obj || typeof obj !== "object") return obj;
254
+ if (visited.has(obj)) return "[Circular]";
255
+ visited.add(obj);
241
256
  const sanitized = { ...obj };
242
257
  for (const [key, value] of Object.entries(sanitized)) {
243
258
  const isAdditionalKey = additionalSensitiveKeys?.some((k) => key.toLowerCase() === k.toLowerCase());
@@ -250,15 +265,23 @@ function object(obj, additionalSensitiveKeys) {
250
265
  continue;
251
266
  }
252
267
  if (value && typeof value === "object" && !Array.isArray(value)) {
253
- sanitized[key] = object(value, additionalSensitiveKeys);
268
+ if (visited.has(value)) {
269
+ sanitized[key] = "[Circular]";
270
+ continue;
271
+ }
272
+ sanitized[key] = object(value, additionalSensitiveKeys, visited);
254
273
  continue;
255
274
  }
256
275
  if (Array.isArray(value)) sanitized[key] = value.map((item) => {
257
276
  if (item instanceof Error) return serializeError(item);
258
- if (item && typeof item === "object" && !Array.isArray(item)) return object(item, additionalSensitiveKeys);
277
+ if (item && typeof item === "object" && !Array.isArray(item)) {
278
+ if (visited.has(item)) return "[Circular]";
279
+ return object(item, additionalSensitiveKeys, visited);
280
+ }
259
281
  return item;
260
282
  });
261
283
  }
284
+ visited.delete(obj);
262
285
  return sanitized;
263
286
  }
264
287
  function headers(hdrs) {
@@ -273,19 +296,24 @@ function headers(hdrs) {
273
296
  for (const key of Object.keys(sanitized)) if (sensitiveHeaders.some((h) => key.toLowerCase() === h)) sanitized[key] = "[REDACTED]";
274
297
  return sanitized;
275
298
  }
276
- function extractErrorInfo(error49, includeStack) {
299
+ function extractErrorInfo(error49, includeStack, visited = /* @__PURE__ */ new WeakSet()) {
277
300
  const info = {
278
301
  name: error49.name,
279
302
  message: error49.message
280
303
  };
281
304
  if (includeStack && error49.stack) info.stack = error49.stack;
282
305
  if ("code" in error49 && typeof error49.code === "string") info.code = error49.code;
283
- if (error49.cause instanceof Error) info.cause = extractErrorInfo(error49.cause, includeStack);
306
+ visited.add(error49);
307
+ if (error49.cause instanceof Error) if (visited.has(error49.cause)) info.cause = {
308
+ name: error49.cause.name,
309
+ message: "[Circular]"
310
+ };
311
+ else info.cause = extractErrorInfo(error49.cause, includeStack, visited);
284
312
  return info;
285
313
  }
286
314
  var import_picocolors, LOG_LEVELS, LEVEL_COLORS, LEVEL_WIDTH, SENSITIVE_FIELDS, sanitize, BaseLogger;
287
- var init_logger_base_CG4i4G4l = __esm({
288
- "../../node_modules/@parisgroup-ai/logger/dist/logger.base-CG4i4G4l.mjs"() {
315
+ var init_logger_base_DJVL2Rjl = __esm({
316
+ "../../node_modules/@parisgroup-ai/logger/dist/logger.base-DJVL2Rjl.mjs"() {
289
317
  import_picocolors = __toESM(require_picocolors(), 1);
290
318
  LOG_LEVELS = {
291
319
  trace: 0,
@@ -611,7 +639,7 @@ function createLogger(module, options) {
611
639
  var asyncLocalStorage, ConsoleTransport, FileTransport, HttpTransport, SINGLETON_KEY, Logger, logger;
612
640
  var init_dist = __esm({
613
641
  "../../node_modules/@parisgroup-ai/logger/dist/index.mjs"() {
614
- init_logger_base_CG4i4G4l();
642
+ init_logger_base_DJVL2Rjl();
615
643
  asyncLocalStorage = new AsyncLocalStorage();
616
644
  ConsoleTransport = class {
617
645
  log(entry, formatted) {
@@ -784,14 +812,6 @@ var init_dist = __esm({
784
812
  }
785
813
  });
786
814
 
787
- // ../../packages/logger/src/index.ts
788
- var init_src = __esm({
789
- "../../packages/logger/src/index.ts"() {
790
- "use strict";
791
- init_dist();
792
- }
793
- });
794
-
795
815
  // ../../packages/tostudy-core/src/courses/list-courses.ts
796
816
  async function listCourses(input2, deps) {
797
817
  deps.logger.info("tostudy-core: listCourses", { userId: input2.userId });
@@ -836,7 +856,7 @@ var init_courses = __esm({
836
856
  });
837
857
 
838
858
  // ../../packages/tostudy-core/src/adapters/http.ts
839
- async function apiFetch(url2, token2, init) {
859
+ async function cliApiFetch(url2, token2, init) {
840
860
  const response = await fetch(url2, {
841
861
  ...init,
842
862
  headers: {
@@ -856,7 +876,7 @@ function createHttpProvider(apiUrl, token2) {
856
876
  return {
857
877
  courses: {
858
878
  list: async (_userId) => {
859
- const res = await apiFetch(`${base}/courses`, token2);
879
+ const res = await cliApiFetch(`${base}/courses`, token2);
860
880
  return res.courses;
861
881
  },
862
882
  listCreatorCourses: async (filters) => {
@@ -868,26 +888,29 @@ function createHttpProvider(apiUrl, token2) {
868
888
  if (filters.limit) params.append("limit", filters.limit.toString());
869
889
  if (filters.offset) params.append("offset", filters.offset.toString());
870
890
  if (filters.projectId) params.append("projectId", filters.projectId);
871
- const res = await apiFetch(
891
+ const res = await cliApiFetch(
872
892
  `${base}/creator-courses?${params.toString()}`,
873
893
  token2
874
894
  );
875
895
  return res.courses;
876
896
  },
877
897
  select: async (_userId, courseId) => {
878
- const res = await apiFetch(`${base}/courses/select`, token2, {
898
+ const res = await cliApiFetch(`${base}/courses/select`, token2, {
879
899
  method: "POST",
880
900
  body: JSON.stringify({ courseId })
881
901
  });
882
902
  return res.course;
883
903
  },
884
904
  getProgress: async (enrollmentId) => {
885
- return apiFetch(`${base}/progress?enrollmentId=${encodeURIComponent(enrollmentId)}`, token2);
905
+ return cliApiFetch(
906
+ `${base}/progress?enrollmentId=${encodeURIComponent(enrollmentId)}`,
907
+ token2
908
+ );
886
909
  }
887
910
  },
888
911
  lessons: {
889
912
  next: async (enrollmentId, userConfirmation) => {
890
- return apiFetch(`${base}/lessons/next`, token2, {
913
+ return cliApiFetch(`${base}/lessons/next`, token2, {
891
914
  method: "POST",
892
915
  body: JSON.stringify({ enrollmentId, userConfirmation })
893
916
  });
@@ -895,22 +918,22 @@ function createHttpProvider(apiUrl, token2) {
895
918
  getContent: async (lessonId, enrollmentId) => {
896
919
  const params = new URLSearchParams({ lessonId });
897
920
  if (enrollmentId) params.set("enrollmentId", enrollmentId);
898
- return apiFetch(`${base}/lessons/content?${params.toString()}`, token2);
921
+ return cliApiFetch(`${base}/lessons/content?${params.toString()}`, token2);
899
922
  },
900
923
  getHint: async (_userId, enrollmentId) => {
901
- return apiFetch(`${base}/lessons/hint`, token2, {
924
+ return cliApiFetch(`${base}/lessons/hint`, token2, {
902
925
  method: "POST",
903
926
  body: JSON.stringify({ enrollmentId })
904
927
  });
905
928
  },
906
929
  startModule: async (enrollmentId) => {
907
- return apiFetch(`${base}/modules/start`, token2, {
930
+ return cliApiFetch(`${base}/modules/start`, token2, {
908
931
  method: "POST",
909
932
  body: JSON.stringify({ enrollmentId })
910
933
  });
911
934
  },
912
935
  startNextModule: async (enrollmentId) => {
913
- return apiFetch(`${base}/modules/start-next`, token2, {
936
+ return cliApiFetch(`${base}/modules/start-next`, token2, {
914
937
  method: "POST",
915
938
  body: JSON.stringify({ enrollmentId })
916
939
  });
@@ -933,17 +956,17 @@ function createHttpProvider(apiUrl, token2) {
933
956
  },
934
957
  exercises: {
935
958
  validate: async (lessonId, solution, _userId, enrollmentId) => {
936
- return apiFetch(`${base}/exercises/validate`, token2, {
959
+ return cliApiFetch(`${base}/exercises/validate`, token2, {
937
960
  method: "POST",
938
961
  body: JSON.stringify({ lessonId, solution, enrollmentId })
939
962
  });
940
963
  },
941
964
  getEnrollmentLevels: async (enrollmentId) => {
942
965
  const params = new URLSearchParams({ enrollmentId });
943
- return apiFetch(`${base}/enrollments/levels?${params.toString()}`, token2);
966
+ return cliApiFetch(`${base}/enrollments/levels?${params.toString()}`, token2);
944
967
  },
945
968
  setEnrollmentLevel: async (enrollmentId, level) => {
946
- return apiFetch(`${base}/enrollments/levels`, token2, {
969
+ return cliApiFetch(`${base}/enrollments/levels`, token2, {
947
970
  method: "POST",
948
971
  body: JSON.stringify({ enrollmentId, level })
949
972
  });
@@ -955,6 +978,16 @@ function createHttpProvider(apiUrl, token2) {
955
978
  "createModuleSession is not available via HTTP adapter. Use the Drizzle adapter for session management."
956
979
  );
957
980
  }
981
+ },
982
+ memory: {
983
+ getContext: async (courseId, enrollmentId) => {
984
+ const params = new URLSearchParams({ courseId, enrollmentId });
985
+ return cliApiFetch(`${base}/memory?${params.toString()}`, token2);
986
+ },
987
+ recordInsight: async (input2) => cliApiFetch(`${base}/memory/record-insight`, token2, {
988
+ method: "POST",
989
+ body: JSON.stringify(input2)
990
+ })
958
991
  }
959
992
  };
960
993
  }
@@ -968,6 +1001,7 @@ var init_http = __esm({
968
1001
  this.status = status;
969
1002
  this.name = "CliApiError";
970
1003
  }
1004
+ status;
971
1005
  };
972
1006
  }
973
1007
  });
@@ -1607,8 +1641,9 @@ Rode \`tostudy context --json\` em sil\xEAncio para descobrir o estado do aluno.
1607
1641
  ### Se tudo pronto (workspace + curso + contexto):
1608
1642
 
1609
1643
  1. Leia os \`moduleSummaries\` do resultado para saber o que o aluno j\xE1 estudou.
1610
- 2. Se houver summaries, mencione brevemente: "Da \xFAltima vez paramos em {ultimoModulo}. Pronto pra continuar?"
1611
- 3. Siga "Como Conduzir a Aula" abaixo.`);
1644
+ 2. Rode \`tostudy memory --json\` em sil\xEAncio para carregar o perfil do aluno (dificuldades conhecidas, pontos fortes) e os resumos das li\xE7\xF5es recentes. Use esse contexto para adaptar a aula \u2014 n\xE3o repita o que o aluno j\xE1 domina, e reforce os pontos onde ele teve dificuldade. NUNCA mostre esse bloco cru ao aluno.
1645
+ 3. Se houver summaries, mencione brevemente: "Da \xFAltima vez paramos em {ultimoModulo}. Pronto pra continuar?"
1646
+ 4. Siga "Como Conduzir a Aula" abaixo.`);
1612
1647
  sections.push(renderHowToConduct());
1613
1648
  sections.push(renderSilentTools());
1614
1649
  sections.push(`## Compacta\xE7\xE3o de Contexto (Autom\xE1tico)
@@ -1808,6 +1843,10 @@ Estes comandos s\xE3o suas ferramentas. Rode em sil\xEAncio (sem anunciar), use
1808
1843
  - \`tostudy hint --json\` \u2014 dica progressiva (3 n\xEDveis).
1809
1844
  - \`tostudy validate <arquivo>\` \u2014 valida exerc\xEDcio (exit 0 = passou, 1 = falhou).
1810
1845
 
1846
+ - \`tostudy insight <tipo> "<descri\xE7\xE3o>" --json\` \u2014 registra na mem\xF3ria do curso um sinal cognitivo do aluno. Tipos: \`difficulty\` (travou ou confundiu um conceito), \`breakthrough\` (teve um clique, dominou algo), \`question\` (d\xFAvida recorrente).
1847
+
1848
+ **Quando registrar insight:** em sil\xEAncio, ao perceber um desses sinais *reais* durante a aula \u2014 priorize \`difficulty\` (\xE9 o mais valioso pra adaptar as pr\xF3ximas li\xE7\xF5es). Use uma frase curta e espec\xEDfica (\u2264240 caracteres), no idioma do aluno. N\xE3o registre trivialidades nem repita o mesmo insight; no m\xE1ximo um ou dois por li\xE7\xE3o.
1849
+
1811
1850
  Voc\xEA nunca menciona estes comandos ao aluno. Ele fala com VOC\xCA, n\xE3o com o CLI.`;
1812
1851
  }
1813
1852
  function renderHandlingSituations() {
@@ -1828,6 +1867,7 @@ function renderTechnicalReference() {
1828
1867
  - Use \`--json\` em qualquer comando para sa\xEDda estruturada.
1829
1868
  - \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado).
1830
1869
  - \`tostudy validate --stdin\` aceita solu\xE7\xE3o via pipe.
1870
+ - \`tostudy insight <difficulty|breakthrough|question> "<texto>"\` persiste um insight na mem\xF3ria cold do aluno (dedup autom\xE1tico; falha non-fatal).
1831
1871
  - \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\`.
1832
1872
  - \`tostudy progress --json\` retorna \`{ coursePercent, currentModule, currentLesson }\`.`;
1833
1873
  }
@@ -1939,7 +1979,7 @@ var logger2;
1939
1979
  var init_instruction_files = __esm({
1940
1980
  "src/workspace/instruction-files.ts"() {
1941
1981
  "use strict";
1942
- init_src();
1982
+ init_dist();
1943
1983
  init_instruction_template_v3();
1944
1984
  logger2 = createLogger("cli:instruction-files");
1945
1985
  }
@@ -1948,6 +1988,72 @@ var init_instruction_files = __esm({
1948
1988
  // src/commands/login.ts
1949
1989
  import { Command } from "commander";
1950
1990
  import { execFile as execFile2 } from "node:child_process";
1991
+ function assertTrustedApiUrl(apiUrl) {
1992
+ let url2;
1993
+ try {
1994
+ url2 = new URL(apiUrl);
1995
+ } catch {
1996
+ throw new Error(`API URL inv\xE1lida: ${apiUrl}`);
1997
+ }
1998
+ const host = url2.hostname.toLowerCase();
1999
+ const isLocalhost = host === "localhost" || host === "127.0.0.1" || host === "::1";
2000
+ const isToStudy = host === "tostudy.ai" || host.endsWith(".tostudy.ai");
2001
+ if (isToStudy && url2.protocol === "https:") return;
2002
+ if (isLocalhost && (url2.protocol === "https:" || url2.protocol === "http:")) return;
2003
+ throw new Error(
2004
+ `API URL n\xE3o confi\xE1vel: ${apiUrl}. Use https://tostudy.ai (ou um subdom\xEDnio) ou localhost.`
2005
+ );
2006
+ }
2007
+ async function persistSession(data, apiUrl) {
2008
+ await saveSession({
2009
+ token: data.token,
2010
+ refreshToken: data.refreshToken,
2011
+ sessionId: data.sessionId,
2012
+ userId: data.userId,
2013
+ userName: data.userName,
2014
+ expiresAt: data.expiresAt,
2015
+ apiUrl
2016
+ });
2017
+ }
2018
+ async function loginWithCode(code, apiUrl) {
2019
+ assertTrustedApiUrl(apiUrl);
2020
+ const res = await fetch(`${apiUrl}/api/cli/auth/exchange`, {
2021
+ method: "POST",
2022
+ headers: { "Content-Type": "application/json" },
2023
+ body: JSON.stringify({ code, client: "cli" })
2024
+ });
2025
+ if (!res.ok) {
2026
+ const body = await res.json().catch(() => ({}));
2027
+ throw new Error(body.error ?? "Falha na autentica\xE7\xE3o");
2028
+ }
2029
+ const data = await res.json();
2030
+ await persistSession(data, apiUrl);
2031
+ return data;
2032
+ }
2033
+ async function loginWithMagicLink(magicLinkUrl, apiUrl) {
2034
+ assertTrustedApiUrl(apiUrl);
2035
+ let token2;
2036
+ try {
2037
+ token2 = new URL(magicLinkUrl).searchParams.get("token");
2038
+ } catch {
2039
+ throw new Error("URL de magic-link inv\xE1lida");
2040
+ }
2041
+ if (!token2) {
2042
+ throw new Error("Magic-link sem par\xE2metro token");
2043
+ }
2044
+ const res = await fetch(`${apiUrl}/api/cli/auth/exchange-magic-link`, {
2045
+ method: "POST",
2046
+ headers: { "Content-Type": "application/json" },
2047
+ body: JSON.stringify({ token: token2 })
2048
+ });
2049
+ if (!res.ok) {
2050
+ const body = await res.json().catch(() => ({}));
2051
+ throw new Error(body.error ?? "Falha na autentica\xE7\xE3o");
2052
+ }
2053
+ const data = await res.json();
2054
+ await persistSession(data, apiUrl);
2055
+ return data;
2056
+ }
1951
2057
  function ideToPlatform(ideName) {
1952
2058
  const lower = ideName.toLowerCase();
1953
2059
  if (lower.includes("claude")) return "claude";
@@ -1961,7 +2067,7 @@ var logger3, DEFAULT_API_URL, PORT, loginCommand;
1961
2067
  var init_login = __esm({
1962
2068
  "src/commands/login.ts"() {
1963
2069
  "use strict";
1964
- init_src();
2070
+ init_dist();
1965
2071
  init_courses();
1966
2072
  init_http();
1967
2073
  init_oauth_server();
@@ -1970,8 +2076,30 @@ var init_login = __esm({
1970
2076
  logger3 = createLogger("cli:login");
1971
2077
  DEFAULT_API_URL = "https://tostudy.ai";
1972
2078
  PORT = 9876;
1973
- loginCommand = new Command("login").description("Autentica no ToStudy via browser").option("--manual", "Login manual (sem browser)").option("--api-url <url>", "API URL override", DEFAULT_API_URL).action(async (opts) => {
2079
+ loginCommand = new Command("login").description("Autentica no ToStudy via browser").option("--manual", "Login manual (sem browser)").option("--code <code>", "Login n\xE3o-interativo via c\xF3digo de autentica\xE7\xE3o").option("--magic-link <url>", "Login via URL de magic-link (recupera\xE7\xE3o)").option("--api-url <url>", "API URL override", DEFAULT_API_URL).action(async (opts) => {
1974
2080
  const apiUrl = opts.apiUrl;
2081
+ if (opts.code) {
2082
+ try {
2083
+ const data = await loginWithCode(opts.code, apiUrl);
2084
+ console.log(`
2085
+ \u2713 Logado como ${data.userName}
2086
+ `);
2087
+ return;
2088
+ } catch (err) {
2089
+ error(err instanceof Error ? err.message : "Login falhou");
2090
+ }
2091
+ }
2092
+ if (opts.magicLink) {
2093
+ try {
2094
+ const data = await loginWithMagicLink(opts.magicLink, apiUrl);
2095
+ console.log(`
2096
+ \u2713 Logado como ${data.userName}
2097
+ `);
2098
+ return;
2099
+ } catch (err) {
2100
+ error(err instanceof Error ? err.message : "Login falhou");
2101
+ }
2102
+ }
1975
2103
  if (opts.manual) {
1976
2104
  console.log(`
1977
2105
  Acesse este endere\xE7o no seu browser:`);
@@ -2333,7 +2461,8 @@ var init_errors_pt_br = __esm({
2333
2461
  workspaceCommandDescription: "Gerenciar workspace de estudo local",
2334
2462
  workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2335
2463
  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",
2336
- 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"
2464
+ 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",
2465
+ enrollmentNotEntitled: "\u{1F6AB} Sua matr\xEDcula neste curso n\xE3o est\xE1 mais ativa (reembolso, cancelamento ou preview expirado).\nPara voltar a estudar, adquira o curso novamente em https://tostudy.ai.\n"
2337
2466
  };
2338
2467
  }
2339
2468
  });
@@ -2352,7 +2481,8 @@ var init_errors_en_us = __esm({
2352
2481
  workspaceCommandDescription: "Manage local study workspace",
2353
2482
  workspaceSetupDescription: "Create the workspace structure for the active course",
2354
2483
  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",
2355
- courseArchived: "This course has been archived and is available for reading only.\nUse `tostudy lesson` to review the content you've already studied.\n"
2484
+ courseArchived: "This course has been archived and is available for reading only.\nUse `tostudy lesson` to review the content you've already studied.\n",
2485
+ enrollmentNotEntitled: "\u{1F6AB} Your enrollment in this course is no longer active (refund, cancellation, or expired preview).\nTo study again, purchase the course at https://tostudy.ai.\n"
2356
2486
  };
2357
2487
  }
2358
2488
  });
@@ -2388,6 +2518,10 @@ function isCourseArchivedError(err) {
2388
2518
  const msg = err instanceof Error ? err.message : String(err);
2389
2519
  return msg.includes("COURSE_ARCHIVED");
2390
2520
  }
2521
+ function isEnrollmentNotEntitledError(err) {
2522
+ const msg = err instanceof Error ? err.message : String(err);
2523
+ return msg.includes("ENROLLMENT_NOT_ENTITLED");
2524
+ }
2391
2525
  var BUNDLES, _cachedLocale;
2392
2526
  var init_errors = __esm({
2393
2527
  "src/errors/index.ts"() {
@@ -2584,7 +2718,7 @@ var CLI_VERSION;
2584
2718
  var init_version = __esm({
2585
2719
  "src/version.ts"() {
2586
2720
  "use strict";
2587
- CLI_VERSION = true ? "0.11.2" : "0.7.1";
2721
+ CLI_VERSION = true ? "0.12.0" : "0.7.1";
2588
2722
  }
2589
2723
  });
2590
2724
 
@@ -2598,16 +2732,9 @@ __export(update_checker_exports, {
2598
2732
  });
2599
2733
  import fs7 from "node:fs";
2600
2734
  import path9 from "node:path";
2601
- import os6 from "node:os";
2602
- function getConfigDir2() {
2603
- if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2604
- return path9.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2605
- }
2606
- return path9.join(os6.homedir(), ".tostudy");
2607
- }
2608
2735
  function readCache() {
2609
2736
  try {
2610
- const p = path9.join(getConfigDir2(), CACHE_FILE);
2737
+ const p = path9.join(getConfigDir(), CACHE_FILE);
2611
2738
  if (!fs7.existsSync(p)) return null;
2612
2739
  return JSON.parse(fs7.readFileSync(p, "utf-8"));
2613
2740
  } catch {
@@ -2616,7 +2743,7 @@ function readCache() {
2616
2743
  }
2617
2744
  function writeCache(cache) {
2618
2745
  try {
2619
- const dir = getConfigDir2();
2746
+ const dir = getConfigDir();
2620
2747
  fs7.mkdirSync(dir, { recursive: true });
2621
2748
  fs7.writeFileSync(path9.join(dir, CACHE_FILE), JSON.stringify(cache));
2622
2749
  } catch {
@@ -2679,6 +2806,7 @@ var PACKAGE_NAME, CACHE_FILE, CHECK_INTERVAL_MS, REGISTRY_TIMEOUT_MS;
2679
2806
  var init_update_checker = __esm({
2680
2807
  "src/update-checker.ts"() {
2681
2808
  "use strict";
2809
+ init_config_dir();
2682
2810
  PACKAGE_NAME = "@tostudy-ai/cli";
2683
2811
  CACHE_FILE = "update-check.json";
2684
2812
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -2688,19 +2816,12 @@ var init_update_checker = __esm({
2688
2816
 
2689
2817
  // src/learner-brief/cache.ts
2690
2818
  import fs8 from "node:fs";
2691
- import os7 from "node:os";
2692
2819
  import path10 from "node:path";
2693
- function getDefaultConfigDir() {
2694
- if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2695
- return path10.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2696
- }
2697
- return path10.join(os7.homedir(), ".tostudy");
2698
- }
2699
2820
  function resolveCachePath(configDir) {
2700
2821
  return path10.join(configDir, "student-brief.json");
2701
2822
  }
2702
2823
  async function readBriefCache(configDir) {
2703
- const dir = configDir ?? getDefaultConfigDir();
2824
+ const dir = configDir ?? getConfigDir();
2704
2825
  const p = resolveCachePath(dir);
2705
2826
  if (!fs8.existsSync(p)) return null;
2706
2827
  try {
@@ -2711,26 +2832,20 @@ async function readBriefCache(configDir) {
2711
2832
  }
2712
2833
  }
2713
2834
  async function writeBriefCache(configDir, cache) {
2714
- const dir = configDir ?? getDefaultConfigDir();
2835
+ const dir = configDir ?? getConfigDir();
2715
2836
  fs8.mkdirSync(dir, { recursive: true });
2716
2837
  fs8.writeFileSync(resolveCachePath(dir), JSON.stringify(cache, null, 2), { mode: 384 });
2717
2838
  }
2718
2839
  var init_cache = __esm({
2719
2840
  "src/learner-brief/cache.ts"() {
2720
2841
  "use strict";
2842
+ init_config_dir();
2721
2843
  }
2722
2844
  });
2723
2845
 
2724
2846
  // src/tutor-persona/cache.ts
2725
2847
  import fs9 from "node:fs";
2726
- import os8 from "node:os";
2727
2848
  import path11 from "node:path";
2728
- function getDefaultConfigDir2() {
2729
- if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2730
- return path11.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2731
- }
2732
- return path11.join(os8.homedir(), ".tostudy");
2733
- }
2734
2849
  function resolveCachePath2(configDir) {
2735
2850
  return path11.join(configDir, "tutor-personalities.json");
2736
2851
  }
@@ -2748,12 +2863,12 @@ function writeAll(configDir, cache) {
2748
2863
  fs9.writeFileSync(resolveCachePath2(configDir), JSON.stringify(cache, null, 2), { mode: 384 });
2749
2864
  }
2750
2865
  async function readPersonaCacheForCourse(configDir, courseId) {
2751
- const dir = configDir ?? getDefaultConfigDir2();
2866
+ const dir = configDir ?? getConfigDir();
2752
2867
  const all = readAll(dir);
2753
2868
  return all[courseId] ?? null;
2754
2869
  }
2755
2870
  async function writePersonaCacheForCourse(configDir, courseId, entry) {
2756
- const dir = configDir ?? getDefaultConfigDir2();
2871
+ const dir = configDir ?? getConfigDir();
2757
2872
  const all = readAll(dir);
2758
2873
  all[courseId] = entry;
2759
2874
  writeAll(dir, all);
@@ -2761,6 +2876,7 @@ async function writePersonaCacheForCourse(configDir, courseId, entry) {
2761
2876
  var init_cache2 = __esm({
2762
2877
  "src/tutor-persona/cache.ts"() {
2763
2878
  "use strict";
2879
+ init_config_dir();
2764
2880
  }
2765
2881
  });
2766
2882
 
@@ -2981,7 +3097,7 @@ var logger4, coursesCommand;
2981
3097
  var init_courses2 = __esm({
2982
3098
  "src/commands/courses.ts"() {
2983
3099
  "use strict";
2984
- init_src();
3100
+ init_dist();
2985
3101
  init_courses();
2986
3102
  init_http();
2987
3103
  init_guards();
@@ -3017,7 +3133,7 @@ var init_courses2 = __esm({
3017
3133
  // src/workspace/resolve.ts
3018
3134
  import fs10 from "node:fs/promises";
3019
3135
  import path12 from "node:path";
3020
- import os9 from "node:os";
3136
+ import os6 from "node:os";
3021
3137
  function courseSlug(title) {
3022
3138
  return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
3023
3139
  }
@@ -3048,7 +3164,7 @@ async function resolveCwdWorkspacePath(cwd = process.cwd()) {
3048
3164
  return null;
3049
3165
  }
3050
3166
  }
3051
- async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os9.homedir()) {
3167
+ async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os6.homedir()) {
3052
3168
  const home = path12.resolve(homeDir);
3053
3169
  let current = path12.resolve(startCwd);
3054
3170
  while (true) {
@@ -3079,7 +3195,7 @@ async function findExistingVault(workspacePath, slug) {
3079
3195
  return null;
3080
3196
  }
3081
3197
  }
3082
- async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE, homeDir = os9.homedir()) {
3198
+ async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE, homeDir = os6.homedir()) {
3083
3199
  const cwdWorkspace = await findCwdWorkspaceUpwards(cwd, homeDir);
3084
3200
  if (cwdWorkspace) {
3085
3201
  return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
@@ -3101,7 +3217,7 @@ var DEFAULT_BASE;
3101
3217
  var init_resolve = __esm({
3102
3218
  "src/workspace/resolve.ts"() {
3103
3219
  "use strict";
3104
- DEFAULT_BASE = path12.join(os9.homedir(), "study");
3220
+ DEFAULT_BASE = path12.join(os6.homedir(), "study");
3105
3221
  }
3106
3222
  });
3107
3223
 
@@ -3130,31 +3246,15 @@ var init_status = __esm({
3130
3246
  });
3131
3247
 
3132
3248
  // src/learner-brief/api.ts
3133
- async function apiFetch2(url2, token2, init) {
3134
- const response = await fetch(url2, {
3135
- method: init?.method ?? "GET",
3136
- body: init?.body,
3137
- headers: {
3138
- "Content-Type": "application/json",
3139
- Authorization: `Bearer ${token2}`,
3140
- ...init?.headers ?? {}
3141
- }
3142
- });
3143
- const body = await response.json();
3144
- if (!response.ok) {
3145
- throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
3146
- }
3147
- return body;
3148
- }
3149
3249
  async function fetchLearnerBrief(input2) {
3150
- const response = await apiFetch2(
3250
+ const response = await cliApiFetch(
3151
3251
  `${input2.apiUrl}/api/cli/student/learner-brief`,
3152
3252
  input2.token
3153
3253
  );
3154
3254
  return response.brief;
3155
3255
  }
3156
3256
  async function upsertLearnerBrief(input2) {
3157
- const response = await apiFetch2(
3257
+ const response = await cliApiFetch(
3158
3258
  `${input2.apiUrl}/api/cli/student/learner-brief`,
3159
3259
  input2.token,
3160
3260
  {
@@ -3265,32 +3365,17 @@ var init_api2 = __esm({
3265
3365
  });
3266
3366
 
3267
3367
  // src/onboarding/api.ts
3268
- async function apiFetch3(url2, token2, init) {
3269
- const response = await fetch(url2, {
3270
- ...init,
3271
- headers: {
3272
- "Content-Type": "application/json",
3273
- Authorization: `Bearer ${token2}`,
3274
- ...init?.headers
3275
- }
3276
- });
3277
- const body = await response.json();
3278
- if (!response.ok) {
3279
- throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
3280
- }
3281
- return body;
3282
- }
3283
3368
  async function getRemoteEnrollmentOnboarding(input2) {
3284
3369
  const url2 = new URL(`${input2.apiUrl}/api/cli/onboarding`);
3285
3370
  url2.searchParams.set("enrollmentId", input2.enrollmentId);
3286
- const response = await apiFetch3(
3371
+ const response = await cliApiFetch(
3287
3372
  url2.toString(),
3288
3373
  input2.token
3289
3374
  );
3290
3375
  return response.onboarding;
3291
3376
  }
3292
3377
  async function saveRemoteEnrollmentOnboarding(input2) {
3293
- const response = await apiFetch3(
3378
+ const response = await cliApiFetch(
3294
3379
  `${input2.apiUrl}/api/cli/onboarding`,
3295
3380
  input2.token,
3296
3381
  {
@@ -3465,7 +3550,7 @@ var logger5, defaultDeps2;
3465
3550
  var init_instruction_pipeline = __esm({
3466
3551
  "src/instruction-pipeline.ts"() {
3467
3552
  "use strict";
3468
- init_src();
3553
+ init_dist();
3469
3554
  init_workspace_marker();
3470
3555
  init_workspace_state();
3471
3556
  init_api();
@@ -3569,7 +3654,7 @@ var logger6;
3569
3654
  var init_pipeline_deps = __esm({
3570
3655
  "src/commands/_shared/pipeline-deps.ts"() {
3571
3656
  "use strict";
3572
- init_src();
3657
+ init_dist();
3573
3658
  init_courses();
3574
3659
  init_http();
3575
3660
  init_instruction_pipeline();
@@ -3580,18 +3665,19 @@ var init_pipeline_deps = __esm({
3580
3665
 
3581
3666
  // src/commands/select.ts
3582
3667
  import { Command as Command6 } from "commander";
3583
- import os10 from "node:os";
3668
+ import os7 from "node:os";
3584
3669
  var logger7, selectCommand;
3585
3670
  var init_select = __esm({
3586
3671
  "src/commands/select.ts"() {
3587
3672
  "use strict";
3588
- init_src();
3673
+ init_dist();
3589
3674
  init_courses();
3590
3675
  init_http();
3591
3676
  init_guards();
3592
3677
  init_workspace_marker();
3593
3678
  init_resolve();
3594
3679
  init_formatter();
3680
+ init_errors();
3595
3681
  init_status();
3596
3682
  init_instruction_pipeline();
3597
3683
  init_pipeline_deps();
@@ -3664,7 +3750,7 @@ var init_select = __esm({
3664
3750
  );
3665
3751
  } else {
3666
3752
  const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
3667
- const home = os10.homedir();
3753
+ const home = os7.homedir();
3668
3754
  const cwd = process.cwd();
3669
3755
  const namespacedPath = `${cwd}/.tostudy`;
3670
3756
  let wsLine;
@@ -3699,6 +3785,10 @@ var init_select = __esm({
3699
3785
  output(lines.join("\n"), { json: false });
3700
3786
  }
3701
3787
  } catch (err) {
3788
+ if (isEnrollmentNotEntitledError(err)) {
3789
+ process.stderr.write(getErrors().enrollmentNotEntitled);
3790
+ process.exit(1);
3791
+ }
3702
3792
  const msg = err instanceof Error ? err.message : String(err);
3703
3793
  error(msg);
3704
3794
  }
@@ -3712,12 +3802,13 @@ var logger8, progressCommand;
3712
3802
  var init_progress = __esm({
3713
3803
  "src/commands/progress.ts"() {
3714
3804
  "use strict";
3715
- init_src();
3805
+ init_dist();
3716
3806
  init_courses();
3717
3807
  init_http();
3718
3808
  init_guards();
3719
3809
  init_course_state();
3720
3810
  init_formatter();
3811
+ init_errors();
3721
3812
  logger8 = createLogger("cli:progress");
3722
3813
  progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
3723
3814
  try {
@@ -3734,6 +3825,10 @@ var init_progress = __esm({
3734
3825
  output(formatProgress(progress3), { json: false });
3735
3826
  }
3736
3827
  } catch (err) {
3828
+ if (isEnrollmentNotEntitledError(err)) {
3829
+ process.stderr.write(getErrors().enrollmentNotEntitled);
3830
+ process.exit(1);
3831
+ }
3737
3832
  const msg = err instanceof Error ? err.message : String(err);
3738
3833
  error(msg);
3739
3834
  }
@@ -5287,7 +5382,7 @@ var init_query_promise = __esm({
5287
5382
  function mapResultRow(columns, row, joinsNotNullableMap) {
5288
5383
  const nullifyMap = {};
5289
5384
  const result = columns.reduce(
5290
- (result2, { path: path22, field }, columnIndex) => {
5385
+ (result2, { path: path23, field }, columnIndex) => {
5291
5386
  let decoder;
5292
5387
  if (is(field, Column)) {
5293
5388
  decoder = field;
@@ -5299,8 +5394,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
5299
5394
  decoder = field.sql.decoder;
5300
5395
  }
5301
5396
  let node = result2;
5302
- for (const [pathChunkIndex, pathChunk] of path22.entries()) {
5303
- if (pathChunkIndex < path22.length - 1) {
5397
+ for (const [pathChunkIndex, pathChunk] of path23.entries()) {
5398
+ if (pathChunkIndex < path23.length - 1) {
5304
5399
  if (!(pathChunk in node)) {
5305
5400
  node[pathChunk] = {};
5306
5401
  }
@@ -5308,8 +5403,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
5308
5403
  } else {
5309
5404
  const rawValue = row[columnIndex];
5310
5405
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
5311
- if (joinsNotNullableMap && is(field, Column) && path22.length === 2) {
5312
- const objectName = path22[0];
5406
+ if (joinsNotNullableMap && is(field, Column) && path23.length === 2) {
5407
+ const objectName = path23[0];
5313
5408
  if (!(objectName in nullifyMap)) {
5314
5409
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
5315
5410
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -9256,13 +9351,13 @@ function Subscribe(postgres2, options) {
9256
9351
  }
9257
9352
  }
9258
9353
  function handle(a, b2) {
9259
- const path22 = b2.relation.schema + "." + b2.relation.table;
9354
+ const path23 = b2.relation.schema + "." + b2.relation.table;
9260
9355
  call("*", a, b2);
9261
- call("*:" + path22, a, b2);
9262
- b2.relation.keys.length && call("*:" + path22 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9356
+ call("*:" + path23, a, b2);
9357
+ b2.relation.keys.length && call("*:" + path23 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9263
9358
  call(b2.command, a, b2);
9264
- call(b2.command + ":" + path22, a, b2);
9265
- b2.relation.keys.length && call(b2.command + ":" + path22 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9359
+ call(b2.command + ":" + path23, a, b2);
9360
+ b2.relation.keys.length && call(b2.command + ":" + path23 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9266
9361
  }
9267
9362
  function pong() {
9268
9363
  const x2 = Buffer.alloc(34);
@@ -9375,8 +9470,8 @@ function parseEvent(x) {
9375
9470
  const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
9376
9471
  if (!xs)
9377
9472
  throw new Error("Malformed subscribe pattern: " + x);
9378
- const [, command, path22, key] = xs;
9379
- return (command || "*") + (path22 ? ":" + (path22.indexOf(".") === -1 ? "public." + path22 : path22) : "") + (key ? "=" + key : "");
9473
+ const [, command, path23, key] = xs;
9474
+ return (command || "*") + (path23 ? ":" + (path23.indexOf(".") === -1 ? "public." + path23 : path23) : "") + (key ? "=" + key : "");
9380
9475
  }
9381
9476
  var noop2;
9382
9477
  var init_subscribe = __esm({
@@ -9457,7 +9552,7 @@ var init_large = __esm({
9457
9552
  });
9458
9553
 
9459
9554
  // ../../node_modules/postgres/src/index.js
9460
- import os11 from "os";
9555
+ import os8 from "os";
9461
9556
  import fs11 from "fs";
9462
9557
  function Postgres(a, b2) {
9463
9558
  const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
@@ -9514,10 +9609,10 @@ function Postgres(a, b2) {
9514
9609
  });
9515
9610
  return query;
9516
9611
  }
9517
- function file2(path22, args = [], options2 = {}) {
9612
+ function file2(path23, args = [], options2 = {}) {
9518
9613
  arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
9519
9614
  const query = new Query([], args, (query2) => {
9520
- fs11.readFile(path22, "utf8", (err, string4) => {
9615
+ fs11.readFile(path23, "utf8", (err, string4) => {
9521
9616
  if (err)
9522
9617
  return query2.reject(err);
9523
9618
  query2.strings = [string4];
@@ -9835,13 +9930,13 @@ function parseUrl(url2) {
9835
9930
  }
9836
9931
  function osUsername() {
9837
9932
  try {
9838
- return os11.userInfo().username;
9933
+ return os8.userInfo().username;
9839
9934
  } catch (_) {
9840
9935
  return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
9841
9936
  }
9842
9937
  }
9843
9938
  var src_default;
9844
- var init_src2 = __esm({
9939
+ var init_src = __esm({
9845
9940
  "../../node_modules/postgres/src/index.js"() {
9846
9941
  init_types();
9847
9942
  init_connection();
@@ -14096,12 +14191,12 @@ var init_session2 = __esm({
14096
14191
  init_tracing();
14097
14192
  init_utils();
14098
14193
  PostgresJsPreparedQuery = class extends PgPreparedQuery {
14099
- constructor(client, queryString, params, logger31, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14194
+ constructor(client, queryString, params, logger33, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14100
14195
  super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
14101
14196
  this.client = client;
14102
14197
  this.queryString = queryString;
14103
14198
  this.params = params;
14104
- this.logger = logger31;
14199
+ this.logger = logger33;
14105
14200
  this.fields = fields;
14106
14201
  this._isResponseInArrayMode = _isResponseInArrayMode;
14107
14202
  this.customResultMapper = customResultMapper;
@@ -14242,11 +14337,11 @@ function construct(client, config2 = {}) {
14242
14337
  client.options.serializers["114"] = transparentParser;
14243
14338
  client.options.serializers["3802"] = transparentParser;
14244
14339
  const dialect = new PgDialect({ casing: config2.casing });
14245
- let logger31;
14340
+ let logger33;
14246
14341
  if (config2.logger === true) {
14247
- logger31 = new DefaultLogger();
14342
+ logger33 = new DefaultLogger();
14248
14343
  } else if (config2.logger !== false) {
14249
- logger31 = config2.logger;
14344
+ logger33 = config2.logger;
14250
14345
  }
14251
14346
  let schema;
14252
14347
  if (config2.schema) {
@@ -14260,7 +14355,7 @@ function construct(client, config2 = {}) {
14260
14355
  tableNamesMap: tablesConfig.tableNamesMap
14261
14356
  };
14262
14357
  }
14263
- const session = new PostgresJsSession(client, dialect, schema, { logger: logger31, cache: config2.cache });
14358
+ const session = new PostgresJsSession(client, dialect, schema, { logger: logger33, cache: config2.cache });
14264
14359
  const db2 = new PostgresJsDatabase(dialect, session, schema);
14265
14360
  db2.$client = client;
14266
14361
  db2.$cache = config2.cache;
@@ -14290,7 +14385,7 @@ function drizzle(...params) {
14290
14385
  var PostgresJsDatabase;
14291
14386
  var init_driver = __esm({
14292
14387
  "../../node_modules/drizzle-orm/postgres-js/driver.js"() {
14293
- init_src2();
14388
+ init_src();
14294
14389
  init_entity();
14295
14390
  init_logger();
14296
14391
  init_db();
@@ -14575,10 +14670,10 @@ function mergeDefs(...defs) {
14575
14670
  function cloneDef(schema) {
14576
14671
  return mergeDefs(schema._zod.def);
14577
14672
  }
14578
- function getElementAtPath(obj, path22) {
14579
- if (!path22)
14673
+ function getElementAtPath(obj, path23) {
14674
+ if (!path23)
14580
14675
  return obj;
14581
- return path22.reduce((acc, key) => acc?.[key], obj);
14676
+ return path23.reduce((acc, key) => acc?.[key], obj);
14582
14677
  }
14583
14678
  function promiseAllObject(promisesObj) {
14584
14679
  const keys = Object.keys(promisesObj);
@@ -14890,11 +14985,11 @@ function aborted(x, startIndex = 0) {
14890
14985
  }
14891
14986
  return false;
14892
14987
  }
14893
- function prefixIssues(path22, issues) {
14988
+ function prefixIssues(path23, issues) {
14894
14989
  return issues.map((iss) => {
14895
14990
  var _a2;
14896
14991
  (_a2 = iss).path ?? (_a2.path = []);
14897
- iss.path.unshift(path22);
14992
+ iss.path.unshift(path23);
14898
14993
  return iss;
14899
14994
  });
14900
14995
  }
@@ -15136,7 +15231,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
15136
15231
  }
15137
15232
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
15138
15233
  const result = { errors: [] };
15139
- const processError = (error50, path22 = []) => {
15234
+ const processError = (error50, path23 = []) => {
15140
15235
  var _a2, _b;
15141
15236
  for (const issue2 of error50.issues) {
15142
15237
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -15146,7 +15241,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
15146
15241
  } else if (issue2.code === "invalid_element") {
15147
15242
  processError({ issues: issue2.issues }, issue2.path);
15148
15243
  } else {
15149
- const fullpath = [...path22, ...issue2.path];
15244
+ const fullpath = [...path23, ...issue2.path];
15150
15245
  if (fullpath.length === 0) {
15151
15246
  result.errors.push(mapper(issue2));
15152
15247
  continue;
@@ -15178,8 +15273,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
15178
15273
  }
15179
15274
  function toDotPath(_path) {
15180
15275
  const segs = [];
15181
- const path22 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15182
- for (const seg of path22) {
15276
+ const path23 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15277
+ for (const seg of path23) {
15183
15278
  if (typeof seg === "number")
15184
15279
  segs.push(`[${seg}]`);
15185
15280
  else if (typeof seg === "symbol")
@@ -27873,13 +27968,13 @@ function resolveRef(ref, ctx) {
27873
27968
  if (!ref.startsWith("#")) {
27874
27969
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
27875
27970
  }
27876
- const path22 = ref.slice(1).split("/").filter(Boolean);
27877
- if (path22.length === 0) {
27971
+ const path23 = ref.slice(1).split("/").filter(Boolean);
27972
+ if (path23.length === 0) {
27878
27973
  return ctx.rootSchema;
27879
27974
  }
27880
27975
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
27881
- if (path22[0] === defsKey) {
27882
- const key = path22[1];
27976
+ if (path23[0] === defsKey) {
27977
+ const key = path23[1];
27883
27978
  if (!key || !ctx.defs[key]) {
27884
27979
  throw new Error(`Reference not found: ${ref}`);
27885
27980
  }
@@ -28644,6 +28739,7 @@ var init_users = __esm({
28644
28739
  "../../packages/database/src/schema/users.ts"() {
28645
28740
  "use strict";
28646
28741
  init_pg_core();
28742
+ init_drizzle_orm();
28647
28743
  userRoleEnum = pgEnum("user_role", [
28648
28744
  "student",
28649
28745
  "creator_provisional",
@@ -28691,7 +28787,7 @@ var init_users = __esm({
28691
28787
  photoUrl: text("photo_url"),
28692
28788
  // Cloudflare R2 URL for custom uploaded photo
28693
28789
  studentRole: studentRoleEnum("student_role"),
28694
- // for students only: PM, Developer, Designer, Manager, Outros
28790
+ // learner self-id, any role may set it (GH #657 — calibrates exercise difficulty): PM, Developer, Designer, Manager, Outros
28695
28791
  referralSource: varchar("referral_source", { length: 50 }),
28696
28792
  // How the user heard about tostudy (optional, for attribution)
28697
28793
  expertise: varchar("expertise", { length: 200 }),
@@ -28806,7 +28902,12 @@ var init_users = __esm({
28806
28902
  externalIdIdx: index("idx_users_external_id").on(
28807
28903
  table.provisioningSource,
28808
28904
  table.externalUserId
28809
- )
28905
+ ),
28906
+ // Partial UNIQUE guard (migration 0015): prevents two concurrent paris
28907
+ // provisioning inserts with the same external_user_id from creating two
28908
+ // distinct users. Partial (WHERE provisioning_source IS NOT NULL) so it
28909
+ // never conflicts with self-service users whose provisioning_source is null.
28910
+ uniqueParisProvisioningIdx: uniqueIndex("idx_unique_paris_provisioning").on(table.provisioningSource, table.externalUserId).where(sql`${table.provisioningSource} IS NOT NULL`)
28810
28911
  })
28811
28912
  );
28812
28913
  }
@@ -28947,6 +29048,14 @@ var init_user_preferences = __esm({
28947
29048
  emailNewReplies: boolean("email_new_replies").default(true).notNull(),
28948
29049
  /** Whether user wants push notifications for mentorship (via MCP) */
28949
29050
  pushMentoria: boolean("push_mentoria").default(true).notNull(),
29051
+ // Marketing email preferences (FEAT-1285)
29052
+ /**
29053
+ * Whether user wants marketing/nurture emails (day-3/day-7 nurture,
29054
+ * course recommendations). Defaults true (opt-out) to preserve the
29055
+ * pre-existing behavior where these sends were gated only by the
29056
+ * `emailNotifications` master toggle.
29057
+ */
29058
+ marketingEmails: boolean("marketing_emails").default(true).notNull(),
28950
29059
  // i18n nickname pilot column (dogfooding)
28951
29060
  nicknameI18n: jsonb("nickname_i18n").$type(),
28952
29061
  // Timestamps
@@ -29209,7 +29318,6 @@ var init_courses3 = __esm({
29209
29318
  tags: jsonb("tags").$type().default([]).notNull(),
29210
29319
  // Search tags: ['agent', 'mcp', 'automation', 'claude-code']
29211
29320
  categoryId: uuid("category_id").references(() => courseCategories.id, { onDelete: "set null" }),
29212
- enrollmentCount: integer("enrollment_count").default(0).notNull(),
29213
29321
  thumbnailUrl: varchar("thumbnail_url", { length: 500 }),
29214
29322
  videoIntroUrl: varchar("video_intro_url", { length: 500 }),
29215
29323
  // Story 10.8: Video upload integration
@@ -29314,8 +29422,6 @@ var init_courses3 = __esm({
29314
29422
  // B-tree index for essential course filter
29315
29423
  createdAtIdx: index("courses_created_at_idx").on(table.createdAt),
29316
29424
  // B-tree index for newest sort
29317
- enrollmentCountIdx: index("courses_enrollment_count_idx").on(table.enrollmentCount),
29318
- // B-tree for best-sellers sort
29319
29425
  // Story 11.1: Curadoria queue sorting and filtering indexes
29320
29426
  statusIdx: index("courses_status_idx").on(table.status),
29321
29427
  // B-tree for status filtering
@@ -29659,6 +29765,18 @@ var init_credit_wallets = __esm({
29659
29765
  walletCreatedAtIdx: index("cwt_wallet_created_at_idx").on(table.walletId, table.createdAt),
29660
29766
  walletTypeIdx: index("cwt_wallet_type_idx").on(table.walletId, table.type),
29661
29767
  referenceIdx: index("cwt_reference_idx").on(table.referenceId, table.referenceType),
29768
+ // TASK-1841 (GH #712 follow-up): partial UNIQUE closes the residual
29769
+ // concurrency window in ClawbackCourseSaleCreditUseCase — two distinct
29770
+ // `charge.refunded` deliveries for the same enrollment can both pass the
29771
+ // in-tx idempotency check before either commits (read committed never sees
29772
+ // the other's uncommitted insert), producing a double `refund_debit`. The
29773
+ // index makes the DB the final arbiter; the use-case catches 23505 as an
29774
+ // idempotent skip. Scoped to reference_type='enrollment' ONLY: RefundBooking
29775
+ // also writes `refund_debit` (reference_type='booking') with no idempotency
29776
+ // guard, so a general `type='refund_debit'` index could 23505 a legitimate
29777
+ // re-booking refund. Raw `sql` literal (not eq()) to dodge the drizzle-kit
29778
+ // `$N` placeholder DDL bug — see packages/database/AGENTS.md.
29779
+ refundDebitEnrollmentUnique: uniqueIndex("cwt_refund_debit_enrollment_unique").on(table.referenceId, table.referenceType, table.type).where(sql`${table.type} = 'refund_debit' AND ${table.referenceType} = 'enrollment'`),
29662
29780
  referenceTypeCheck: check(
29663
29781
  "cwt_reference_type_check",
29664
29782
  sql`${table.referenceType} IS NULL OR ${table.referenceType} IN (
@@ -30202,7 +30320,7 @@ var init_errors6 = __esm({
30202
30320
  });
30203
30321
 
30204
30322
  // ../../packages/types/src/index.ts
30205
- var init_src3 = __esm({
30323
+ var init_src2 = __esm({
30206
30324
  "../../packages/types/src/index.ts"() {
30207
30325
  "use strict";
30208
30326
  init_user();
@@ -30224,7 +30342,7 @@ var init_course_reviews = __esm({
30224
30342
  init_pg_core();
30225
30343
  init_users();
30226
30344
  init_courses3();
30227
- init_src3();
30345
+ init_src2();
30228
30346
  courseReviewStatusEnum = pgEnum("course_review_status", [
30229
30347
  "pending",
30230
30348
  "approved",
@@ -30332,6 +30450,7 @@ var init_enrollments = __esm({
30332
30450
  "../../packages/database/src/schema/enrollments.ts"() {
30333
30451
  "use strict";
30334
30452
  init_pg_core();
30453
+ init_drizzle_orm();
30335
30454
  init_users();
30336
30455
  init_courses3();
30337
30456
  init_cohorts();
@@ -30412,7 +30531,14 @@ var init_enrollments = __esm({
30412
30531
  table.currentLevel
30413
30532
  ),
30414
30533
  // Paris Group integration: partial index for cohort lookups.
30415
- cohortIdIdx: index("idx_enrollments_cohort_id").on(table.cohortId)
30534
+ cohortIdIdx: index("idx_enrollments_cohort_id").on(table.cohortId),
30535
+ // TASK-1765 (GH #660 Q2): at most ONE active enrollment per (user, course).
30536
+ // Partial on status='active' so refunded/cancelled/completed history rows
30537
+ // can coexist (re-purchase after refund creates a fresh active row).
30538
+ // Raw sql in .where() — drizzle-kit serializes eq() as a `$1` placeholder
30539
+ // (42P02, see AGENTS.md). Backfill of the 3 prod duplicate pairs lives in
30540
+ // migration 0023.
30541
+ uniqueActivePerUserCourse: uniqueIndex("enrollments_user_course_active_unique").on(table.userId, table.courseId).where(sql`${table.status} = 'active'`)
30416
30542
  })
30417
30543
  );
30418
30544
  }
@@ -33915,6 +34041,34 @@ var init_mentor_availability = __esm({
33915
34041
  }
33916
34042
  });
33917
34043
 
34044
+ // ../../packages/database/src/schema/mentor-specializations.ts
34045
+ var mentorSpecializations;
34046
+ var init_mentor_specializations = __esm({
34047
+ "../../packages/database/src/schema/mentor-specializations.ts"() {
34048
+ "use strict";
34049
+ init_pg_core();
34050
+ init_users();
34051
+ mentorSpecializations = pgTable(
34052
+ "mentor_specializations",
34053
+ {
34054
+ id: uuid("id").defaultRandom().primaryKey(),
34055
+ creatorId: uuid("creator_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
34056
+ name: varchar("name", { length: 50 }).notNull(),
34057
+ // Display order — mirrors the order the creator typed the chips in
34058
+ position: integer("position").default(0).notNull(),
34059
+ createdAt: timestamp("created_at").defaultNow().notNull()
34060
+ },
34061
+ (table) => ({
34062
+ creatorIdIdx: index("mentor_specializations_creator_id_idx").on(table.creatorId),
34063
+ creatorNameUq: uniqueIndex("mentor_specializations_creator_name_uq").on(
34064
+ table.creatorId,
34065
+ table.name
34066
+ )
34067
+ })
34068
+ );
34069
+ }
34070
+ });
34071
+
33918
34072
  // ../../packages/database/src/schema/mentorship-sessions.ts
33919
34073
  var mentorshipSessions2, sessionParticipants2, sessionWaitlist2;
33920
34074
  var init_mentorship_sessions = __esm({
@@ -34178,14 +34332,16 @@ var init_mentorship_packages = __esm({
34178
34332
  function isCreditUsable(credit) {
34179
34333
  return credit.status === "active" && credit.creditsRemaining > 0 && new Date(credit.expiresAt) > /* @__PURE__ */ new Date();
34180
34334
  }
34181
- var creditStatusEnum, mentorshipCredits2, insertMentorshipCreditSchema;
34335
+ var creditStatusEnum, creditOriginEnum, mentorshipCredits2, insertMentorshipCreditSchema;
34182
34336
  var init_mentorship_credits = __esm({
34183
34337
  "../../packages/database/src/schema/mentorship-credits.ts"() {
34184
34338
  "use strict";
34185
34339
  init_pg_core();
34340
+ init_drizzle_orm();
34186
34341
  init_users();
34187
34342
  init_mentorship_packages();
34188
34343
  init_mentorship_services();
34344
+ init_enrollments();
34189
34345
  init_zod();
34190
34346
  creditStatusEnum = pgEnum("mentorship_credit_status", [
34191
34347
  "active",
@@ -34195,12 +34351,17 @@ var init_mentorship_credits = __esm({
34195
34351
  "expired"
34196
34352
  // Credits expired (not used before expiration date)
34197
34353
  ]);
34354
+ creditOriginEnum = pgEnum("mentorship_credit_origin", ["package", "course_purchase"]);
34198
34355
  mentorshipCredits2 = pgTable(
34199
34356
  "mentorship_credits",
34200
34357
  {
34201
34358
  id: uuid("id").defaultRandom().primaryKey(),
34202
34359
  userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
34203
- packageId: uuid("package_id").references(() => mentorshipPackages2.id, { onDelete: "cascade" }).notNull(),
34360
+ // FEAT-1288: nullable since course-purchase grants have no package. The
34361
+ // origin-consistency CHECK below keeps package rows package-backed.
34362
+ packageId: uuid("package_id").references(() => mentorshipPackages2.id, {
34363
+ onDelete: "cascade"
34364
+ }),
34204
34365
  mentorId: uuid("mentor_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
34205
34366
  serviceId: uuid("service_id").references(() => mentorshipServices2.id, { onDelete: "cascade" }).notNull(),
34206
34367
  // Credit balance
@@ -34210,6 +34371,12 @@ var init_mentorship_credits = __esm({
34210
34371
  // Current available credits
34211
34372
  // Status tracking
34212
34373
  status: creditStatusEnum("status").default("active").notNull(),
34374
+ // FEAT-1288: grant origin + the enrollment that granted course credits
34375
+ // (refund revoke looks credits up by enrollment_id).
34376
+ origin: creditOriginEnum("origin").default("package").notNull(),
34377
+ enrollmentId: uuid("enrollment_id").references(() => enrollments.id, {
34378
+ onDelete: "cascade"
34379
+ }),
34213
34380
  // Payment reference
34214
34381
  stripePaymentIntentId: varchar("stripe_payment_intent_id", { length: 100 }),
34215
34382
  // Dates
@@ -34255,21 +34422,47 @@ var init_mentorship_credits = __esm({
34255
34422
  table.stripePaymentIntentId
34256
34423
  ),
34257
34424
  // ORDER BY purchasedAt DESC (credit transaction history)
34258
- purchasedAtIdx: index("mentorship_credits_purchased_at_idx").on(table.purchasedAt)
34425
+ purchasedAtIdx: index("mentorship_credits_purchased_at_idx").on(table.purchasedAt),
34426
+ // FEAT-1288: refund revoke looks up course-granted credits by enrollment
34427
+ enrollmentIdIdx: index("mentorship_credits_enrollment_id_idx").on(table.enrollmentId),
34428
+ // FEAT-1288: each origin carries its tracing FK — package rows keep a
34429
+ // package_id, course-purchase rows keep an enrollment_id.
34430
+ originConsistencyCheck: check(
34431
+ "mentorship_credits_origin_consistency_check",
34432
+ sql`(${table.origin} = 'package' AND ${table.packageId} IS NOT NULL) OR (${table.origin} = 'course_purchase' AND ${table.enrollmentId} IS NOT NULL)`
34433
+ )
34259
34434
  })
34260
34435
  );
34261
34436
  insertMentorshipCreditSchema = external_exports.object({
34262
34437
  userId: external_exports.string().uuid(),
34263
- packageId: external_exports.string().uuid(),
34438
+ // FEAT-1288: optional — required iff origin === "package" (refinement below)
34439
+ packageId: external_exports.string().uuid().optional().nullable(),
34264
34440
  mentorId: external_exports.string().uuid(),
34265
34441
  serviceId: external_exports.string().uuid(),
34266
34442
  totalCredits: external_exports.number().int().positive(),
34267
34443
  creditsRemaining: external_exports.number().int().min(0),
34268
34444
  status: external_exports.enum(["active", "depleted", "expired"]).optional().default("active"),
34445
+ origin: external_exports.enum(["package", "course_purchase"]).optional().default("package"),
34446
+ enrollmentId: external_exports.string().uuid().optional().nullable(),
34269
34447
  stripePaymentIntentId: external_exports.string().max(100).optional().nullable(),
34270
34448
  expiresAt: external_exports.date(),
34271
34449
  reminderSent30d: external_exports.boolean().optional().default(false),
34272
34450
  reminderSent7d: external_exports.boolean().optional().default(false)
34451
+ }).superRefine((value, ctx) => {
34452
+ if (value.origin === "package" && value.packageId == null) {
34453
+ ctx.addIssue({
34454
+ code: external_exports.ZodIssueCode.custom,
34455
+ path: ["packageId"],
34456
+ message: "packageId is required when origin is 'package'"
34457
+ });
34458
+ }
34459
+ if (value.origin === "course_purchase" && value.enrollmentId == null) {
34460
+ ctx.addIssue({
34461
+ code: external_exports.ZodIssueCode.custom,
34462
+ path: ["enrollmentId"],
34463
+ message: "enrollmentId is required when origin is 'course_purchase'"
34464
+ });
34465
+ }
34273
34466
  });
34274
34467
  }
34275
34468
  });
@@ -34468,6 +34661,55 @@ var init_mentorship_transactions = __esm({
34468
34661
  }
34469
34662
  });
34470
34663
 
34664
+ // ../../packages/database/src/schema/course-mentoria-offers.ts
34665
+ var courseMentoriaOffers, insertCourseMentoriaOfferSchema;
34666
+ var init_course_mentoria_offers = __esm({
34667
+ "../../packages/database/src/schema/course-mentoria-offers.ts"() {
34668
+ "use strict";
34669
+ init_pg_core();
34670
+ init_drizzle_orm();
34671
+ init_zod();
34672
+ init_courses3();
34673
+ init_mentorship_services();
34674
+ courseMentoriaOffers = pgTable(
34675
+ "course_mentoria_offers",
34676
+ {
34677
+ id: uuid("id").defaultRandom().primaryKey(),
34678
+ courseId: uuid("course_id").references(() => courses.id, { onDelete: "cascade" }).notNull(),
34679
+ serviceId: uuid("service_id").references(() => mentorshipServices2.id, { onDelete: "cascade" }).notNull(),
34680
+ /** Sessions granted to the buyer (mentorship_credits) on hybrid purchase. */
34681
+ sessionsIncluded: integer("sessions_included").notNull(),
34682
+ /** Credit validity window, mirrors mentorship_packages (3/6/12). */
34683
+ validityMonths: integer("validity_months").default(6).notNull(),
34684
+ /** Draft offers (e.g. creator without availability) stay inactive. */
34685
+ isActive: boolean("is_active").default(false).notNull(),
34686
+ createdAt: timestamp("created_at").defaultNow().notNull(),
34687
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
34688
+ },
34689
+ (table) => ({
34690
+ // 1 offer per course — the checkout gate lookup key.
34691
+ courseIdUnique: uniqueIndex("course_mentoria_offers_course_id_unique").on(table.courseId),
34692
+ serviceIdIdx: index("course_mentoria_offers_service_id_idx").on(table.serviceId),
34693
+ sessionsIncludedCheck: check(
34694
+ "course_mentoria_offers_sessions_included_check",
34695
+ sql`${table.sessionsIncluded} >= 1`
34696
+ ),
34697
+ validityMonthsCheck: check(
34698
+ "course_mentoria_offers_validity_months_check",
34699
+ sql`${table.validityMonths} IN (3, 6, 12)`
34700
+ )
34701
+ })
34702
+ );
34703
+ insertCourseMentoriaOfferSchema = external_exports.object({
34704
+ courseId: external_exports.string().uuid(),
34705
+ serviceId: external_exports.string().uuid(),
34706
+ sessionsIncluded: external_exports.number().int().min(1),
34707
+ validityMonths: external_exports.union([external_exports.literal(3), external_exports.literal(6), external_exports.literal(12)]).default(6),
34708
+ isActive: external_exports.boolean().default(false)
34709
+ });
34710
+ }
34711
+ });
34712
+
34471
34713
  // ../../packages/database/src/schema/session-reviews.ts
34472
34714
  var REVIEW_TAGS, sessionReviews2, sessionReviewVotes2;
34473
34715
  var init_session_reviews = __esm({
@@ -34552,11 +34794,13 @@ var init_mentorship_index = __esm({
34552
34794
  init_mentorship_templates();
34553
34795
  init_mentorship_services();
34554
34796
  init_mentor_availability();
34797
+ init_mentor_specializations();
34555
34798
  init_mentorship_sessions();
34556
34799
  init_session_resources();
34557
34800
  init_mentorship_transactions();
34558
34801
  init_mentorship_packages();
34559
34802
  init_mentorship_credits();
34803
+ init_course_mentoria_offers();
34560
34804
  init_session_reviews();
34561
34805
  }
34562
34806
  });
@@ -34632,6 +34876,7 @@ var init_transactions = __esm({
34632
34876
  init_pg_core();
34633
34877
  init_courses3();
34634
34878
  init_users();
34879
+ init_enrollments();
34635
34880
  transactions2 = pgTable(
34636
34881
  "transactions",
34637
34882
  {
@@ -34644,6 +34889,15 @@ var init_transactions = __esm({
34644
34889
  courseId: uuid("course_id").notNull().references(() => courses.id, { onDelete: "restrict" }),
34645
34890
  creatorId: uuid("creator_id").notNull().references(() => users.id, { onDelete: "restrict" }),
34646
34891
  studentId: uuid("student_id").notNull().references(() => users.id, { onDelete: "restrict" }),
34892
+ // CHORE-1676: links the transaction to the exact enrollment it paid for so
34893
+ // the charge.refunded webhook (ProcessRefundUseCase) flips ONLY that row,
34894
+ // never collateral (user, course) history rows. Nullable: historical txns
34895
+ // (pre-link) and TOCTOU-refused purchases (no enrollment created) leave it
34896
+ // null; the refund then falls back to a status-filtered (user, course) match.
34897
+ // onDelete: 'set null' preserves the financial record if the enrollment is deleted.
34898
+ enrollmentId: uuid("enrollment_id").references(() => enrollments.id, {
34899
+ onDelete: "set null"
34900
+ }),
34647
34901
  // Financial details (all amounts in cents)
34648
34902
  amount: integer("amount").notNull(),
34649
34903
  // Total amount charged
@@ -34663,11 +34917,24 @@ var init_transactions = __esm({
34663
34917
  paymentMethod: varchar("payment_method", { length: 50 }),
34664
34918
  // 'card' or 'pix'
34665
34919
  status: varchar("status", { length: 20 }).notNull().default("pending"),
34666
- // 'pending', 'succeeded', 'failed', 'refunded'
34920
+ // 'pending', 'succeeded', 'failed', 'refunded', 'disputed' (GH #718 chargeback)
34667
34921
  // Timestamps
34668
34922
  createdAt: timestamp("created_at").defaultNow().notNull(),
34669
34923
  processedAt: timestamp("processed_at"),
34670
34924
  refundedAt: timestamp("refunded_at"),
34925
+ refundedAmount: integer("refunded_amount").default(0).notNull(),
34926
+ // Cumulative amount refunded in cents
34927
+ refusalReason: varchar("refusal_reason", { length: 50 }),
34928
+ // TOCTOU refusal marker (e.g. 'toctou_course_status')
34929
+ // GH #718: Stripe chargeback/dispute tracking. disputedAt is stamped when a
34930
+ // charge.dispute.created arrives (status flips to 'disputed') and on a lost
34931
+ // dispute (charge.dispute.closed status=lost). disputeFee is the platform's
34932
+ // ~$15 chargeback fee (cents), recorded only on a lost dispute and netted
34933
+ // out of netPlatformRevenue. Both nullable — non-disputed transactions leave
34934
+ // them null.
34935
+ disputedAt: timestamp("disputed_at"),
34936
+ disputeFee: integer("dispute_fee"),
34937
+ // Stripe dispute fee in cents (lost disputes only)
34671
34938
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
34672
34939
  },
34673
34940
  (table) => ({
@@ -34754,6 +35021,56 @@ var init_payouts = __esm({
34754
35021
  }
34755
35022
  });
34756
35023
 
35024
+ // ../../packages/database/src/schema/subscription-pools.ts
35025
+ var subscriptionPoolStatusEnum, subscriptionSplitStatusEnum, catalogSubscriptionRevenuePools, catalogSubscriptionCreatorSplits;
35026
+ var init_subscription_pools = __esm({
35027
+ "../../packages/database/src/schema/subscription-pools.ts"() {
35028
+ "use strict";
35029
+ init_pg_core();
35030
+ init_users();
35031
+ subscriptionPoolStatusEnum = pgEnum("subscription_pool_status", [
35032
+ "open",
35033
+ "calculating",
35034
+ "distributed"
35035
+ ]);
35036
+ subscriptionSplitStatusEnum = pgEnum("subscription_split_status", [
35037
+ "pending",
35038
+ "paid",
35039
+ "failed"
35040
+ ]);
35041
+ catalogSubscriptionRevenuePools = pgTable("catalog_subscription_revenue_pools", {
35042
+ id: uuid("id").defaultRandom().primaryKey(),
35043
+ month: varchar("month", { length: 7 }).notNull().unique(),
35044
+ // "YYYY-MM"
35045
+ totalAmount: integer("total_amount").notNull(),
35046
+ // pool amount in BRL cents
35047
+ status: subscriptionPoolStatusEnum("status").default("open").notNull(),
35048
+ createdAt: timestamp("created_at").defaultNow().notNull(),
35049
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
35050
+ });
35051
+ catalogSubscriptionCreatorSplits = pgTable(
35052
+ "catalog_subscription_creator_splits",
35053
+ {
35054
+ id: uuid("id").defaultRandom().primaryKey(),
35055
+ poolId: uuid("pool_id").references(() => catalogSubscriptionRevenuePools.id, { onDelete: "cascade" }).notNull(),
35056
+ creatorId: uuid("creator_id").references(() => users.id, { onDelete: "restrict" }).notNull(),
35057
+ viewsCount: integer("views_count").notNull(),
35058
+ sharePercentage: numeric("share_percentage", { precision: 10, scale: 6 }).notNull(),
35059
+ // e.g. 0.354123
35060
+ allocatedAmount: integer("allocated_amount").notNull(),
35061
+ // share amount in BRL cents
35062
+ status: subscriptionSplitStatusEnum("status").default("pending").notNull(),
35063
+ createdAt: timestamp("created_at").defaultNow().notNull(),
35064
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
35065
+ },
35066
+ (table) => ({
35067
+ poolIdIdx: index("idx_csc_splits_pool_id").on(table.poolId),
35068
+ creatorIdIdx: index("idx_csc_splits_creator_id").on(table.creatorId)
35069
+ })
35070
+ );
35071
+ }
35072
+ });
35073
+
34757
35074
  // ../../packages/database/src/schema/creator-applications.ts
34758
35075
  var creatorApplications2;
34759
35076
  var init_creator_applications = __esm({
@@ -35016,7 +35333,10 @@ var init_course_project_enums = __esm({
35016
35333
  });
35017
35334
 
35018
35335
  // ../../packages/database/src/schema/course-project-types.ts
35019
- var CANCEL_REASONS;
35336
+ function v3CancelReasonRedisKey(courseId) {
35337
+ return `v3:cancel-reason:${courseId}`;
35338
+ }
35339
+ var CANCEL_REASONS, V3_CANCEL_REASON_TTL_SECONDS;
35020
35340
  var init_course_project_types = __esm({
35021
35341
  "../../packages/database/src/schema/course-project-types.ts"() {
35022
35342
  "use strict";
@@ -35027,6 +35347,7 @@ var init_course_project_types = __esm({
35027
35347
  "incomplete_stream",
35028
35348
  "unknown"
35029
35349
  ];
35350
+ V3_CANCEL_REASON_TTL_SECONDS = 86400;
35030
35351
  }
35031
35352
  });
35032
35353
 
@@ -35066,6 +35387,7 @@ var init_course_projects = __esm({
35066
35387
  "../../packages/database/src/schema/course-projects.ts"() {
35067
35388
  "use strict";
35068
35389
  init_pg_core();
35390
+ init_drizzle_orm();
35069
35391
  init_users();
35070
35392
  init_brainstormings();
35071
35393
  init_courses3();
@@ -35184,7 +35506,9 @@ var init_course_projects = __esm({
35184
35506
  statusIdx: index("course_projects_status_idx").on(table.status),
35185
35507
  currentPhaseIdx: index("course_projects_current_phase_idx").on(table.currentPhase),
35186
35508
  sourceTypeIdx: index("course_projects_source_type_idx").on(table.sourceType),
35187
- sourceIdeaIdIdx: index("course_projects_source_idea_id_idx").on(table.sourceIdeaId),
35509
+ // CHORE-1709: one durable project per source idea. Partial UNIQUE keeps
35510
+ // manual/context projects (NULL source_idea_id) unconstrained.
35511
+ sourceIdeaIdUnique: uniqueIndex("course_projects_source_idea_id_unique").on(table.sourceIdeaId).where(sql`${table.sourceIdeaId} IS NOT NULL`),
35188
35512
  categoryIdIdx: index("course_projects_category_id_idx").on(table.categoryId),
35189
35513
  // ORDER BY createdAt/updatedAt DESC (project listing + creator initial state)
35190
35514
  createdAtIdx: index("course_projects_created_at_idx").on(table.createdAt),
@@ -37003,6 +37327,12 @@ var init_security_audit_log = __esm({
37003
37327
  "account.role_change",
37004
37328
  "account.profile_update",
37005
37329
  "account.delete",
37330
+ "account.data_export",
37331
+ // LGPD Art. 18 I/II — personal-data export (GH #608)
37332
+ "account.deletion_requested",
37333
+ // LGPD Art. 18 VI (GH #609)
37334
+ "account.restored",
37335
+ "account.hard_deleted",
37006
37336
  // Admin events
37007
37337
  "admin.user_suspend",
37008
37338
  "admin.user_unsuspend",
@@ -38045,12 +38375,14 @@ var init_system_config = __esm({
38045
38375
  "ai.context.compression_threshold": "ai.context.compression_threshold",
38046
38376
  // Business - Pricing
38047
38377
  "business.pricing.min_course_price_brl": "business.pricing.min_course_price_brl",
38048
- "business.pricing.mentorship_platform_fee_percent": "business.pricing.mentorship_platform_fee_percent",
38049
38378
  "business.pricing.course_mentoria_creator_share": "business.pricing.course_mentoria_creator_share",
38050
38379
  "business.pricing.course_mentoria_platform_share": "business.pricing.course_mentoria_platform_share",
38051
- "business.pricing.course_selfpaced_creator_share": "business.pricing.course_selfpaced_creator_share",
38052
- "business.pricing.course_selfpaced_platform_share": "business.pricing.course_selfpaced_platform_share",
38053
38380
  "business.pricing.ai_margin_percent": "business.pricing.ai_margin_percent",
38381
+ "business.pricing.course_creator_share": "business.pricing.course_creator_share",
38382
+ "business.pricing.course_platform_share": "business.pricing.course_platform_share",
38383
+ "business.pricing.mentorship_creator_share": "business.pricing.mentorship_creator_share",
38384
+ "business.pricing.mentorship_platform_share": "business.pricing.mentorship_platform_share",
38385
+ "business.pricing.subscription_creator_share": "business.pricing.subscription_creator_share",
38054
38386
  // Business - Monitoring Thresholds
38055
38387
  "business.monitoring.cache_success_threshold": "business.monitoring.cache_success_threshold",
38056
38388
  "business.monitoring.cache_warning_threshold": "business.monitoring.cache_warning_threshold",
@@ -38639,7 +38971,7 @@ var init_course_transfer_logs = __esm({
38639
38971
  "use strict";
38640
38972
  init_pg_core();
38641
38973
  init_users();
38642
- transferTypeEnum = pgEnum("transfer_type", ["export", "import"]);
38974
+ transferTypeEnum = pgEnum("transfer_type", ["export", "import", "reassign"]);
38643
38975
  transferStatusEnum = pgEnum("transfer_status", [
38644
38976
  "pending",
38645
38977
  "processing",
@@ -38654,6 +38986,8 @@ var init_course_transfer_logs = __esm({
38654
38986
  adminId: uuid("admin_id").references(() => users.id, { onDelete: "restrict" }).notNull(),
38655
38987
  type: transferTypeEnum("type").notNull(),
38656
38988
  courseId: uuid("course_id"),
38989
+ fromCreatorId: uuid("from_creator_id").references(() => users.id, { onDelete: "set null" }),
38990
+ toCreatorId: uuid("to_creator_id").references(() => users.id, { onDelete: "set null" }),
38657
38991
  courseName: text("course_name").notNull(),
38658
38992
  sourceEnvironment: text("source_environment"),
38659
38993
  status: transferStatusEnum("status").default("pending").notNull(),
@@ -38668,9 +39002,7 @@ var init_course_transfer_logs = __esm({
38668
39002
  adminIdIdx: index("course_transfer_logs_admin_id_idx").on(table.adminId),
38669
39003
  typeIdx: index("course_transfer_logs_type_idx").on(table.type),
38670
39004
  statusIdx: index("course_transfer_logs_status_idx").on(table.status),
38671
- createdAtIdx: index("course_transfer_logs_created_at_idx").on(
38672
- table.createdAt
38673
- )
39005
+ createdAtIdx: index("course_transfer_logs_created_at_idx").on(table.createdAt)
38674
39006
  })
38675
39007
  );
38676
39008
  }
@@ -40709,34 +41041,6 @@ var init_magic_link_tokens = __esm({
40709
41041
  }
40710
41042
  });
40711
41043
 
40712
- // ../../packages/database/src/schema/platform-fees.ts
40713
- var platformFeeTypeEnum, platformFees;
40714
- var init_platform_fees = __esm({
40715
- "../../packages/database/src/schema/platform-fees.ts"() {
40716
- "use strict";
40717
- init_pg_core();
40718
- platformFeeTypeEnum = pgEnum("platform_fee_type", ["course", "mentorship"]);
40719
- platformFees = pgTable(
40720
- "platform_fees",
40721
- {
40722
- id: uuid("id").defaultRandom().primaryKey(),
40723
- countryCode: varchar("country_code", { length: 2 }).notNull(),
40724
- feeType: platformFeeTypeEnum("fee_type").notNull(),
40725
- feePercent: numeric("fee_percent", { precision: 5, scale: 2 }).notNull(),
40726
- isActive: boolean("is_active").default(true),
40727
- createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
40728
- updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
40729
- },
40730
- (table) => ({
40731
- countryFeeTypeUniq: unique("platform_fees_country_fee_type_uniq").on(
40732
- table.countryCode,
40733
- table.feeType
40734
- )
40735
- })
40736
- );
40737
- }
40738
- });
40739
-
40740
41044
  // ../../packages/database/src/schema/payout-methods.ts
40741
41045
  var payoutMethods, payoutAccounts;
40742
41046
  var init_payout_methods = __esm({
@@ -40776,6 +41080,34 @@ var init_payout_methods = __esm({
40776
41080
  }
40777
41081
  });
40778
41082
 
41083
+ // ../../packages/database/src/schema/withdrawal-taxes.ts
41084
+ var withdrawalTaxes;
41085
+ var init_withdrawal_taxes = __esm({
41086
+ "../../packages/database/src/schema/withdrawal-taxes.ts"() {
41087
+ "use strict";
41088
+ init_pg_core();
41089
+ init_drizzle_orm();
41090
+ withdrawalTaxes = pgTable(
41091
+ "withdrawal_taxes",
41092
+ {
41093
+ id: uuid("id").defaultRandom().primaryKey(),
41094
+ countryCode: varchar("country_code", { length: 2 }).notNull(),
41095
+ taxPercent: numeric("tax_percent", { precision: 5, scale: 2 }).notNull(),
41096
+ isActive: boolean("is_active").default(true).notNull(),
41097
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
41098
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
41099
+ },
41100
+ (table) => ({
41101
+ countryUniq: unique("withdrawal_taxes_country_uniq").on(table.countryCode),
41102
+ taxRange: check(
41103
+ "withdrawal_taxes_tax_range",
41104
+ sql`${table.taxPercent} >= 0 AND ${table.taxPercent} <= 100`
41105
+ )
41106
+ })
41107
+ );
41108
+ }
41109
+ });
41110
+
40779
41111
  // ../../packages/database/src/schema/media-assets.ts
40780
41112
  var mediaAssetStatusEnum, mediaAssetTypeEnum, mediaAssetDomainEnum, mediaAssets;
40781
41113
  var init_media_assets = __esm({
@@ -40857,7 +41189,7 @@ var init_push_tokens = __esm({
40857
41189
  });
40858
41190
 
40859
41191
  // ../../packages/database/src/schema/payout.ts
40860
- var payoutFeeConfig, payoutWithdrawals, payoutBankAccounts, payoutWithdrawalTransitions;
41192
+ var payoutWithdrawals, payoutBankAccounts, payoutWithdrawalTransitions;
40861
41193
  var init_payout = __esm({
40862
41194
  "../../packages/database/src/schema/payout.ts"() {
40863
41195
  "use strict";
@@ -40867,22 +41199,6 @@ var init_payout = __esm({
40867
41199
  init_credit_wallets();
40868
41200
  init_credit_wallets();
40869
41201
  init_payout_methods();
40870
- payoutFeeConfig = pgTable(
40871
- "payout_fee_config",
40872
- {
40873
- id: uuid("id").defaultRandom().primaryKey(),
40874
- mentorId: uuid("mentor_id").references(() => users.id, {
40875
- onDelete: "cascade"
40876
- }).unique(),
40877
- // null = global default
40878
- feePercent: decimal("fee_percent", { precision: 5, scale: 2 }).notNull(),
40879
- createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
40880
- updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
40881
- },
40882
- (table) => ({
40883
- feeRange: check("pfc_fee_range", sql`${table.feePercent} >= 0 AND ${table.feePercent} <= 100`)
40884
- })
40885
- );
40886
41202
  payoutWithdrawals = pgTable(
40887
41203
  "payout_withdrawals",
40888
41204
  {
@@ -40899,10 +41215,17 @@ var init_payout = __esm({
40899
41215
  precision: 10,
40900
41216
  scale: 4
40901
41217
  }).notNull(),
40902
- feePercent: decimal("fee_percent", { precision: 5, scale: 2 }).notNull(),
41218
+ // Camada 3 (ADR-0169 / FEAT-1287): the deduction at cash-out is the
41219
+ // creator's country-of-residence TAX — never a revenue split. Renamed
41220
+ // from fee_percent/fee_usd (legal: table had 0 rows at rename time).
41221
+ taxPercent: decimal("tax_percent", { precision: 5, scale: 2 }).notNull(),
40903
41222
  grossUsd: decimal("gross_usd", { precision: 10, scale: 4 }).notNull(),
40904
- feeUsd: decimal("fee_usd", { precision: 10, scale: 4 }).notNull(),
41223
+ taxUsd: decimal("tax_usd", { precision: 10, scale: 4 }).notNull(),
40905
41224
  netUsd: decimal("net_usd", { precision: 10, scale: 4 }).notNull(),
41225
+ // Audit snapshot: which country code resolved the tax at request time
41226
+ // ('*' when the mentor had no confirmed country). Nullable: rows created
41227
+ // before Camada 3 (none in prod) would not have it.
41228
+ taxCountry: varchar("tax_country", { length: 2 }),
40906
41229
  exchangeRateBrl: decimal("exchange_rate_brl", {
40907
41230
  precision: 10,
40908
41231
  scale: 4
@@ -41101,6 +41424,7 @@ var init_course_allowlist = __esm({
41101
41424
  "../../packages/database/src/schema/course-allowlist.ts"() {
41102
41425
  "use strict";
41103
41426
  init_pg_core();
41427
+ init_drizzle_orm();
41104
41428
  init_courses3();
41105
41429
  init_users();
41106
41430
  courseAllowlist = pgTable(
@@ -41114,14 +41438,20 @@ var init_course_allowlist = __esm({
41114
41438
  onDelete: "set null"
41115
41439
  }),
41116
41440
  invitedAt: timestamp("invited_at", { withTimezone: true }).defaultNow().notNull(),
41117
- resolvedAt: timestamp("resolved_at", { withTimezone: true })
41441
+ resolvedAt: timestamp("resolved_at", { withTimezone: true }),
41442
+ // GH #631: per-invite recipient binding carried in the email link as
41443
+ // ?invite=<accept_token>. NULL = legacy/expired invite (creator must resend).
41444
+ acceptToken: uuid("accept_token"),
41445
+ acceptTokenExpiresAt: timestamp("accept_token_expires_at", { withTimezone: true })
41118
41446
  },
41119
41447
  (table) => ({
41120
41448
  courseEmailUnique: uniqueIndex("course_allowlist_course_email_unique").on(
41121
41449
  table.courseId,
41122
41450
  table.email
41123
41451
  ),
41124
- courseIdIdx: index("course_allowlist_course_id_idx").on(table.courseId)
41452
+ courseIdIdx: index("course_allowlist_course_id_idx").on(table.courseId),
41453
+ // Partial unique index: multiple NULL tokens allowed; minted tokens are globally unique.
41454
+ acceptTokenUnique: uniqueIndex("course_allowlist_accept_token_unique").on(table.acceptToken).where(sql`${table.acceptToken} IS NOT NULL`)
41125
41455
  // partial indexes (user_id IS NOT NULL / IS NULL) are created via raw SQL in the migration — see drizzle-kit $N gotcha in packages/database/AGENTS.md
41126
41456
  })
41127
41457
  );
@@ -41164,6 +41494,119 @@ var init_idempotency = __esm({
41164
41494
  }
41165
41495
  });
41166
41496
 
41497
+ // ../../packages/database/src/schema/user-deletions.ts
41498
+ var userDeletionStatusEnum, userDeletions;
41499
+ var init_user_deletions = __esm({
41500
+ "../../packages/database/src/schema/user-deletions.ts"() {
41501
+ "use strict";
41502
+ init_pg_core();
41503
+ init_users();
41504
+ userDeletionStatusEnum = pgEnum("user_deletion_status", [
41505
+ "pending",
41506
+ // grace period running
41507
+ "restored",
41508
+ // user reverted within grace
41509
+ "hard_deleted"
41510
+ // cron purged the account
41511
+ ]);
41512
+ userDeletions = pgTable(
41513
+ "user_deletions",
41514
+ {
41515
+ id: uuid("id").primaryKey().defaultRandom(),
41516
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
41517
+ requestedAt: timestamp("requested_at", { withTimezone: true }).defaultNow().notNull(),
41518
+ scheduledHardDeleteAt: timestamp("scheduled_hard_delete_at", {
41519
+ withTimezone: true
41520
+ }).notNull(),
41521
+ // SHA-256 of the restore JWT (the raw token only lives in the email link).
41522
+ restoreTokenHash: text("restore_token_hash"),
41523
+ reason: text("reason"),
41524
+ status: userDeletionStatusEnum("status").default("pending").notNull(),
41525
+ anonymizedAt: timestamp("anonymized_at", { withTimezone: true })
41526
+ },
41527
+ (table) => ({
41528
+ userIdIdx: index("user_deletions_user_id_idx").on(table.userId),
41529
+ statusScheduledIdx: index("user_deletions_status_scheduled_idx").on(
41530
+ table.status,
41531
+ table.scheduledHardDeleteAt
41532
+ )
41533
+ })
41534
+ );
41535
+ }
41536
+ });
41537
+
41538
+ // ../../packages/database/src/schema/user-profiles.ts
41539
+ var profileVisibilityEnum, userProfiles;
41540
+ var init_user_profiles = __esm({
41541
+ "../../packages/database/src/schema/user-profiles.ts"() {
41542
+ "use strict";
41543
+ init_pg_core();
41544
+ init_users();
41545
+ profileVisibilityEnum = pgEnum("profile_visibility", ["public", "private"]);
41546
+ userProfiles = pgTable("user_profiles", {
41547
+ // One row per user — userId is both PK and FK (singleton satellite, same
41548
+ // pattern as user_preferences).
41549
+ userId: uuid("user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }),
41550
+ // Short tagline shown under the name, e.g. "Full-stack dev & educator"
41551
+ headline: varchar("headline", { length: 120 }),
41552
+ // Free-form location, e.g. "São Paulo, Brasil"
41553
+ location: varchar("location", { length: 100 }),
41554
+ // Emoji avatar fallback when no photo is uploaded, e.g. "🧑‍💻"
41555
+ avatarEmoji: varchar("avatar_emoji", { length: 16 }),
41556
+ // Profile-wide visibility (distinct from users.portfolioPublic, which is
41557
+ // scoped to the creator portfolio feature only)
41558
+ visibility: profileVisibilityEnum("visibility").default("public").notNull(),
41559
+ createdAt: timestamp("created_at").defaultNow().notNull(),
41560
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
41561
+ });
41562
+ }
41563
+ });
41564
+
41565
+ // ../../packages/database/src/schema/user-resource-audits.ts
41566
+ var userProfileAudits, userPreferencesAudits;
41567
+ var init_user_resource_audits = __esm({
41568
+ "../../packages/database/src/schema/user-resource-audits.ts"() {
41569
+ "use strict";
41570
+ init_pg_core();
41571
+ userProfileAudits = pgTable(
41572
+ "user_profile_audits",
41573
+ {
41574
+ id: uuid("id").defaultRandom().primaryKey(),
41575
+ resource: text("resource").notNull(),
41576
+ row_id: text("row_id").notNull(),
41577
+ op: text("op").notNull(),
41578
+ actor: jsonb("actor").notNull(),
41579
+ before: jsonb("before"),
41580
+ after: jsonb("after"),
41581
+ at: timestamp("at", { withTimezone: true }).defaultNow().notNull()
41582
+ },
41583
+ (table) => ({
41584
+ resourceIdx: index("user_profile_audits_resource_idx").on(table.resource),
41585
+ rowIdIdx: index("user_profile_audits_row_id_idx").on(table.row_id),
41586
+ atIdx: index("user_profile_audits_at_idx").on(table.at)
41587
+ })
41588
+ );
41589
+ userPreferencesAudits = pgTable(
41590
+ "user_preferences_audits",
41591
+ {
41592
+ id: uuid("id").defaultRandom().primaryKey(),
41593
+ resource: text("resource").notNull(),
41594
+ row_id: text("row_id").notNull(),
41595
+ op: text("op").notNull(),
41596
+ actor: jsonb("actor").notNull(),
41597
+ before: jsonb("before"),
41598
+ after: jsonb("after"),
41599
+ at: timestamp("at", { withTimezone: true }).defaultNow().notNull()
41600
+ },
41601
+ (table) => ({
41602
+ resourceIdx: index("user_preferences_audits_resource_idx").on(table.resource),
41603
+ rowIdIdx: index("user_preferences_audits_row_id_idx").on(table.row_id),
41604
+ atIdx: index("user_preferences_audits_at_idx").on(table.at)
41605
+ })
41606
+ );
41607
+ }
41608
+ });
41609
+
41167
41610
  // ../../packages/database/src/schema/index.ts
41168
41611
  var schema_exports = {};
41169
41612
  __export(schema_exports, {
@@ -41178,6 +41621,7 @@ __export(schema_exports, {
41178
41621
  PACKAGE_VALIDITY_OPTIONS: () => PACKAGE_VALIDITY_OPTIONS,
41179
41622
  REVIEW_TAGS: () => REVIEW_TAGS,
41180
41623
  SUGGESTED_DISCOUNTS: () => SUGGESTED_DISCOUNTS,
41624
+ V3_CANCEL_REASON_TTL_SECONDS: () => V3_CANCEL_REASON_TTL_SECONDS,
41181
41625
  accounts: () => accounts,
41182
41626
  accountsRelations: () => accountsRelations2,
41183
41627
  actionTypeEnum: () => actionTypeEnum,
@@ -41206,6 +41650,8 @@ __export(schema_exports, {
41206
41650
  browserSessions: () => browserSessions,
41207
41651
  browserSessionsRelations: () => browserSessionsRelations,
41208
41652
  cancellationReasonEnum: () => cancellationReasonEnum,
41653
+ catalogSubscriptionCreatorSplits: () => catalogSubscriptionCreatorSplits,
41654
+ catalogSubscriptionRevenuePools: () => catalogSubscriptionRevenuePools,
41209
41655
  certificates: () => certificates,
41210
41656
  certificatesRelations: () => certificatesRelations2,
41211
41657
  challengeType: () => challengeType,
@@ -41251,6 +41697,7 @@ __export(schema_exports, {
41251
41697
  courseKnowledgeBases: () => courseKnowledgeBases,
41252
41698
  courseKnowledgeBasesRelations: () => courseKnowledgeBasesRelations,
41253
41699
  courseLevelEnum: () => courseLevelEnum,
41700
+ courseMentoriaOffers: () => courseMentoriaOffers,
41254
41701
  courseProjects: () => courseProjects,
41255
41702
  courseProjectsRelations: () => courseProjectsRelations,
41256
41703
  courseProposals: () => courseProposals2,
@@ -41291,6 +41738,7 @@ __export(schema_exports, {
41291
41738
  creatorVariationAxesRelations: () => creatorVariationAxesRelations,
41292
41739
  creditMovementAuditLog: () => creditMovementAuditLog,
41293
41740
  creditMovementAuditLogRelations: () => creditMovementAuditLogRelations,
41741
+ creditOriginEnum: () => creditOriginEnum,
41294
41742
  creditPackages: () => creditPackages,
41295
41743
  creditStatusEnum: () => creditStatusEnum,
41296
41744
  creditSubscriptionPlans: () => creditSubscriptionPlans,
@@ -41340,6 +41788,7 @@ __export(schema_exports, {
41340
41788
  helpArticleRatings: () => helpArticleRatings,
41341
41789
  ideas: () => ideas,
41342
41790
  ideasRelations: () => ideasRelations,
41791
+ insertCourseMentoriaOfferSchema: () => insertCourseMentoriaOfferSchema,
41343
41792
  insertMentorshipCreditSchema: () => insertMentorshipCreditSchema,
41344
41793
  insertMentorshipPackageSchema: () => insertMentorshipPackageSchema,
41345
41794
  insertPlatformSettingSchema: () => insertPlatformSettingSchema,
@@ -41389,6 +41838,7 @@ __export(schema_exports, {
41389
41838
  mentorAvailabilityExceptions: () => mentorAvailabilityExceptions2,
41390
41839
  mentorAvailabilityExceptionsRelations: () => mentorAvailabilityExceptionsRelations2,
41391
41840
  mentorAvailabilityRelations: () => mentorAvailabilityRelations2,
41841
+ mentorSpecializations: () => mentorSpecializations,
41392
41842
  mentorshipCredits: () => mentorshipCredits2,
41393
41843
  mentorshipCreditsRelations: () => mentorshipCreditsRelations2,
41394
41844
  mentorshipMessages: () => mentorshipMessages2,
@@ -41439,7 +41889,6 @@ __export(schema_exports, {
41439
41889
  pathStatusEnum: () => pathStatusEnum,
41440
41890
  payoutAccounts: () => payoutAccounts,
41441
41891
  payoutBankAccounts: () => payoutBankAccounts,
41442
- payoutFeeConfig: () => payoutFeeConfig,
41443
41892
  payoutMethods: () => payoutMethods,
41444
41893
  payoutStatusEnum: () => payoutStatusEnum,
41445
41894
  payoutTransactions: () => payoutTransactions2,
@@ -41452,8 +41901,6 @@ __export(schema_exports, {
41452
41901
  pgBackendIdempotencyKeys: () => pgBackendIdempotencyKeys,
41453
41902
  pixKeyTypeEnum: () => pixKeyTypeEnum,
41454
41903
  platformConfig: () => platformConfig,
41455
- platformFeeTypeEnum: () => platformFeeTypeEnum,
41456
- platformFees: () => platformFees,
41457
41904
  platformSettings: () => platformSettings2,
41458
41905
  playgroundEvalScores: () => playgroundEvalScores,
41459
41906
  playgroundEvalScoresRelations: () => playgroundEvalScoresRelations,
@@ -41471,6 +41918,7 @@ __export(schema_exports, {
41471
41918
  portfolioViews: () => portfolioViews,
41472
41919
  portfolioViewsRelations: () => portfolioViewsRelations,
41473
41920
  pricingTiers: () => pricingTiers,
41921
+ profileVisibilityEnum: () => profileVisibilityEnum,
41474
41922
  progress: () => progress,
41475
41923
  progressRelations: () => progressRelations2,
41476
41924
  progressStatusEnum: () => progressStatusEnum,
@@ -41576,6 +42024,8 @@ __export(schema_exports, {
41576
42024
  studyModuleSessionsRelations: () => studyModuleSessionsRelations,
41577
42025
  studyPhaseV2Values: () => studyPhaseV2Values,
41578
42026
  studyPhaseValues: () => studyPhaseValues,
42027
+ subscriptionPoolStatusEnum: () => subscriptionPoolStatusEnum,
42028
+ subscriptionSplitStatusEnum: () => subscriptionSplitStatusEnum,
41579
42029
  supportedCurrencies: () => supportedCurrencies,
41580
42030
  syncLogs: () => syncLogs,
41581
42031
  syncLogsRelations: () => syncLogsRelations2,
@@ -41601,12 +42051,17 @@ __export(schema_exports, {
41601
42051
  userBadgesRelations: () => userBadgesRelations2,
41602
42052
  userChallengeScores: () => userChallengeScores,
41603
42053
  userChallengeScoresRelations: () => userChallengeScoresRelations,
42054
+ userDeletionStatusEnum: () => userDeletionStatusEnum,
42055
+ userDeletions: () => userDeletions,
41604
42056
  userFollows: () => userFollows2,
41605
42057
  userFollowsRelations: () => userFollowsRelations2,
41606
42058
  userPathEnrollments: () => userPathEnrollments,
41607
42059
  userPathEnrollmentsRelations: () => userPathEnrollmentsRelations,
41608
42060
  userPreferences: () => userPreferences,
42061
+ userPreferencesAudits: () => userPreferencesAudits,
41609
42062
  userPreferencesRelations: () => userPreferencesRelations2,
42063
+ userProfileAudits: () => userProfileAudits,
42064
+ userProfiles: () => userProfiles,
41610
42065
  userRegionalSettings: () => userRegionalSettings,
41611
42066
  userRegionalSettingsRelations: () => userRegionalSettingsRelations,
41612
42067
  userRoleEnum: () => userRoleEnum,
@@ -41614,6 +42069,7 @@ __export(schema_exports, {
41614
42069
  userUsageQuotas: () => userUsageQuotas2,
41615
42070
  users: () => users,
41616
42071
  usersRelations: () => usersRelations2,
42072
+ v3CancelReasonRedisKey: () => v3CancelReasonRedisKey,
41617
42073
  validationAttempts: () => validationAttempts2,
41618
42074
  validationAttemptsRelations: () => validationAttemptsRelations2,
41619
42075
  validationLessonProgress: () => validationLessonProgress,
@@ -41634,7 +42090,8 @@ __export(schema_exports, {
41634
42090
  videoTemplateNameEnum: () => videoTemplateNameEnum,
41635
42091
  videoTypeEnum: () => videoTypeEnum,
41636
42092
  webhookEvents: () => webhookEvents2,
41637
- wishlists: () => wishlists
42093
+ wishlists: () => wishlists,
42094
+ withdrawalTaxes: () => withdrawalTaxes
41638
42095
  });
41639
42096
  var init_schema3 = __esm({
41640
42097
  "../../packages/database/src/schema/index.ts"() {
@@ -41650,6 +42107,7 @@ var init_schema3 = __esm({
41650
42107
  init_mentorship_index();
41651
42108
  init_reviews();
41652
42109
  init_payouts();
42110
+ init_subscription_pools();
41653
42111
  init_creator_index();
41654
42112
  init_webhook_events();
41655
42113
  init_transactions();
@@ -41725,8 +42183,8 @@ var init_schema3 = __esm({
41725
42183
  init_tutor_content_issues();
41726
42184
  init_magic_link_tokens();
41727
42185
  init_mobile_refresh_tokens();
41728
- init_platform_fees();
41729
42186
  init_payout_methods();
42187
+ init_withdrawal_taxes();
41730
42188
  init_media_assets();
41731
42189
  init_push_tokens();
41732
42190
  init_payout();
@@ -41736,6 +42194,9 @@ var init_schema3 = __esm({
41736
42194
  init_course_visibility();
41737
42195
  init_course_allowlist();
41738
42196
  init_idempotency();
42197
+ init_user_deletions();
42198
+ init_user_profiles();
42199
+ init_user_resource_audits();
41739
42200
  }
41740
42201
  });
41741
42202
 
@@ -41743,7 +42204,7 @@ var init_schema3 = __esm({
41743
42204
  var init_check_module_unlocked = __esm({
41744
42205
  "../../packages/database/src/utils/check-module-unlocked.ts"() {
41745
42206
  "use strict";
41746
- init_src4();
42207
+ init_src3();
41747
42208
  init_schema3();
41748
42209
  }
41749
42210
  });
@@ -41772,11 +42233,11 @@ var init_ids = __esm({
41772
42233
 
41773
42234
  // ../../packages/database/src/index.ts
41774
42235
  var envSchema, _db, createDb, getDb, db;
41775
- var init_src4 = __esm({
42236
+ var init_src3 = __esm({
41776
42237
  "../../packages/database/src/index.ts"() {
41777
42238
  "use strict";
41778
42239
  init_postgres_js();
41779
- init_src2();
42240
+ init_src();
41780
42241
  init_zod();
41781
42242
  init_schema3();
41782
42243
  init_schema3();
@@ -41873,7 +42334,7 @@ var init_formats = __esm({
41873
42334
  var init_parser = __esm({
41874
42335
  "../../packages/tostudy-core/src/exercises/parser.ts"() {
41875
42336
  "use strict";
41876
- init_src3();
42337
+ init_src2();
41877
42338
  init_formats();
41878
42339
  init_formats();
41879
42340
  }
@@ -41897,8 +42358,8 @@ var init_lesson_exercise_data = __esm({
41897
42358
  var init_sync_enrollment_progress = __esm({
41898
42359
  "../../packages/tostudy-core/src/learning/sync-enrollment-progress.ts"() {
41899
42360
  "use strict";
41900
- init_src4();
41901
- init_src();
42361
+ init_src3();
42362
+ init_dist();
41902
42363
  }
41903
42364
  });
41904
42365
 
@@ -41907,21 +42368,38 @@ var DEFAULT_MAX_SESSION_TIME_SECONDS;
41907
42368
  var init_study_state_sync = __esm({
41908
42369
  "../../packages/tostudy-core/src/learning/study-state-sync.ts"() {
41909
42370
  "use strict";
41910
- init_src4();
42371
+ init_src3();
41911
42372
  DEFAULT_MAX_SESSION_TIME_SECONDS = 2 * 60 * 60;
41912
42373
  }
41913
42374
  });
41914
42375
 
42376
+ // ../../packages/tostudy-core/src/enrollments/entitlement.ts
42377
+ var init_entitlement = __esm({
42378
+ "../../packages/tostudy-core/src/enrollments/entitlement.ts"() {
42379
+ "use strict";
42380
+ }
42381
+ });
42382
+
42383
+ // ../../packages/tostudy-core/src/enrollments/archived-access.ts
42384
+ var init_archived_access = __esm({
42385
+ "../../packages/tostudy-core/src/enrollments/archived-access.ts"() {
42386
+ "use strict";
42387
+ init_src3();
42388
+ }
42389
+ });
42390
+
41915
42391
  // ../../packages/tostudy-core/src/learning/start-module-full.ts
41916
42392
  var init_start_module_full = __esm({
41917
42393
  "../../packages/tostudy-core/src/learning/start-module-full.ts"() {
41918
42394
  "use strict";
41919
- init_src4();
42395
+ init_src3();
41920
42396
  init_parser();
41921
42397
  init_exercise_types();
41922
42398
  init_lesson_exercise_data();
41923
42399
  init_sync_enrollment_progress();
41924
42400
  init_study_state_sync();
42401
+ init_entitlement();
42402
+ init_archived_access();
41925
42403
  }
41926
42404
  });
41927
42405
 
@@ -41929,10 +42407,13 @@ var init_start_module_full = __esm({
41929
42407
  var init_next_lesson_full = __esm({
41930
42408
  "../../packages/tostudy-core/src/learning/next-lesson-full.ts"() {
41931
42409
  "use strict";
41932
- init_src4();
42410
+ init_src3();
42411
+ init_dist();
41933
42412
  init_parser();
41934
42413
  init_lesson_exercise_data();
41935
42414
  init_sync_enrollment_progress();
42415
+ init_entitlement();
42416
+ init_archived_access();
41936
42417
  init_study_state_sync();
41937
42418
  }
41938
42419
  });
@@ -41941,8 +42422,8 @@ var init_next_lesson_full = __esm({
41941
42422
  var init_student_memory = __esm({
41942
42423
  "../../packages/tostudy-core/src/memory/student-memory.ts"() {
41943
42424
  "use strict";
41944
- init_src4();
41945
- init_src();
42425
+ init_src3();
42426
+ init_dist();
41946
42427
  }
41947
42428
  });
41948
42429
 
@@ -41950,7 +42431,7 @@ var init_student_memory = __esm({
41950
42431
  var init_summarize_lesson = __esm({
41951
42432
  "../../packages/tostudy-core/src/learning/summarize-lesson.ts"() {
41952
42433
  "use strict";
41953
- init_src();
42434
+ init_dist();
41954
42435
  }
41955
42436
  });
41956
42437
 
@@ -41958,14 +42439,16 @@ var init_summarize_lesson = __esm({
41958
42439
  var init_validate_solution_full = __esm({
41959
42440
  "../../packages/tostudy-core/src/learning/validate-solution-full.ts"() {
41960
42441
  "use strict";
41961
- init_src4();
41962
- init_src();
42442
+ init_src3();
42443
+ init_dist();
41963
42444
  init_parser();
41964
42445
  init_extractors();
41965
42446
  init_exercise_types();
41966
42447
  init_study_state_sync();
41967
42448
  init_sync_enrollment_progress();
41968
42449
  init_student_memory();
42450
+ init_entitlement();
42451
+ init_archived_access();
41969
42452
  init_summarize_lesson();
41970
42453
  }
41971
42454
  });
@@ -41974,8 +42457,10 @@ var init_validate_solution_full = __esm({
41974
42457
  var init_go_to_lesson = __esm({
41975
42458
  "../../packages/tostudy-core/src/learning/go-to-lesson.ts"() {
41976
42459
  "use strict";
41977
- init_src4();
42460
+ init_src3();
41978
42461
  init_parser();
42462
+ init_entitlement();
42463
+ init_archived_access();
41979
42464
  }
41980
42465
  });
41981
42466
 
@@ -41983,8 +42468,9 @@ var init_go_to_lesson = __esm({
41983
42468
  var init_get_lesson_content_full = __esm({
41984
42469
  "../../packages/tostudy-core/src/learning/get-lesson-content-full.ts"() {
41985
42470
  "use strict";
41986
- init_src4();
42471
+ init_src3();
41987
42472
  init_parser();
42473
+ init_entitlement();
41988
42474
  }
41989
42475
  });
41990
42476
 
@@ -42000,9 +42486,11 @@ var init_formatter2 = __esm({
42000
42486
  var init_get_hint_full = __esm({
42001
42487
  "../../packages/tostudy-core/src/learning/get-hint-full.ts"() {
42002
42488
  "use strict";
42003
- init_src4();
42489
+ init_src3();
42004
42490
  init_parser();
42005
42491
  init_formatter2();
42492
+ init_entitlement();
42493
+ init_archived_access();
42006
42494
  }
42007
42495
  });
42008
42496
 
@@ -42010,8 +42498,8 @@ var init_get_hint_full = __esm({
42010
42498
  var init_explain_concept = __esm({
42011
42499
  "../../packages/tostudy-core/src/learning/explain-concept.ts"() {
42012
42500
  "use strict";
42013
- init_src4();
42014
- init_src();
42501
+ init_src3();
42502
+ init_dist();
42015
42503
  }
42016
42504
  });
42017
42505
 
@@ -42051,10 +42539,6 @@ async function runStart(opts, deps = defaultDeps3) {
42051
42539
  const session = await deps.requireSession();
42052
42540
  const activeCourse = await deps.requireActiveCourse();
42053
42541
  const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
42054
- if (!onboarding.initReady) {
42055
- deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
42056
- `);
42057
- }
42058
42542
  const driftWarning = await deps.checkCourseDrift();
42059
42543
  if (driftWarning) deps.stderrWrite(driftWarning + "\n");
42060
42544
  const data = deps.createHttpProvider(session.apiUrl, session.token);
@@ -42073,12 +42557,20 @@ async function runStart(opts, deps = defaultDeps3) {
42073
42557
  } else {
42074
42558
  deps.output(deps.formatModuleStart(moduleData), { json: false });
42075
42559
  }
42560
+ if (!onboarding.initReady) {
42561
+ deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
42562
+ `);
42563
+ }
42076
42564
  } catch (err) {
42077
42565
  if (err instanceof CliApiError && err.status === 403 && err.message === "CHANNEL_MISMATCH") {
42078
42566
  process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
42079
42567
  process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
42080
42568
  process.exit(1);
42081
42569
  }
42570
+ if (isEnrollmentNotEntitledError(err)) {
42571
+ deps.stderrWrite(getErrors().enrollmentNotEntitled);
42572
+ process.exit(1);
42573
+ }
42082
42574
  if (isCourseArchivedError(err)) {
42083
42575
  deps.stderrWrite(getErrors().courseArchived);
42084
42576
  process.exit(1);
@@ -42091,7 +42583,7 @@ var logger9, defaultDeps3, startCommand;
42091
42583
  var init_start = __esm({
42092
42584
  "src/commands/start.ts"() {
42093
42585
  "use strict";
42094
- init_src();
42586
+ init_dist();
42095
42587
  init_lessons2();
42096
42588
  init_http();
42097
42589
  init_guards();
@@ -42128,7 +42620,7 @@ var logger10, startNextCommand;
42128
42620
  var init_start_next = __esm({
42129
42621
  "src/commands/start-next.ts"() {
42130
42622
  "use strict";
42131
- init_src();
42623
+ init_dist();
42132
42624
  init_lessons2();
42133
42625
  init_http();
42134
42626
  init_guards();
@@ -42163,6 +42655,10 @@ var init_start_next = __esm({
42163
42655
  process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
42164
42656
  process.exit(0);
42165
42657
  }
42658
+ if (isEnrollmentNotEntitledError(err)) {
42659
+ process.stderr.write(getErrors().enrollmentNotEntitled);
42660
+ process.exit(1);
42661
+ }
42166
42662
  if (isCourseArchivedError(err)) {
42167
42663
  process.stderr.write(getErrors().courseArchived);
42168
42664
  process.exit(1);
@@ -42181,7 +42677,7 @@ var logger11, nextCommand;
42181
42677
  var init_next = __esm({
42182
42678
  "src/commands/next.ts"() {
42183
42679
  "use strict";
42184
- init_src();
42680
+ init_dist();
42185
42681
  init_lessons2();
42186
42682
  init_http();
42187
42683
  init_guards();
@@ -42214,6 +42710,12 @@ var init_next = __esm({
42214
42710
  output(formatLesson(lessonData), { json: false });
42215
42711
  }
42216
42712
  } catch (err) {
42713
+ if (isEnrollmentNotEntitledError(err)) {
42714
+ const friendly = getErrors().enrollmentNotEntitled;
42715
+ if (opts.json) jsonError("enrollment_not_entitled", { message: friendly });
42716
+ process.stderr.write(friendly);
42717
+ process.exit(1);
42718
+ }
42217
42719
  if (isCourseArchivedError(err)) {
42218
42720
  const friendly = getErrors().courseArchived;
42219
42721
  if (opts.json) jsonError("course_archived", { message: friendly });
@@ -42252,6 +42754,7 @@ var init_next = __esm({
42252
42754
 
42253
42755
  // src/commands/lesson.ts
42254
42756
  import { Command as Command11 } from "commander";
42757
+ import path13 from "node:path";
42255
42758
  function adjustTimeEstimate(type, baseMinutes) {
42256
42759
  if (type === "exercise") return Math.max(baseMinutes, 30);
42257
42760
  return baseMinutes;
@@ -42259,15 +42762,29 @@ function adjustTimeEstimate(type, baseMinutes) {
42259
42762
  function isCheckpoint(title) {
42260
42763
  return /^checkpoint/i.test(title.trim());
42261
42764
  }
42262
- function formatLessonContent(data) {
42765
+ function replacePlaceholders(text2, workspacePath, courseTitle) {
42766
+ const base = path13.basename(workspacePath) === ".tostudy" ? path13.dirname(workspacePath) : workspacePath;
42767
+ const slug = courseSlug(courseTitle);
42768
+ const vaultPath = resolveVaultPath(workspacePath, slug);
42769
+ return text2.replaceAll("$WORKSPACE", base).replaceAll("$VAULT", vaultPath);
42770
+ }
42771
+ function formatLessonContent(data, workspacePath, courseTitle) {
42263
42772
  const time4 = adjustTimeEstimate(data.type, data.estimatedTimeMinutes);
42264
- const lines = [
42265
- `\u2501\u2501\u2501 Li\xE7\xE3o: ${data.title} \u2501\u2501\u2501`,
42266
- "",
42267
- `Tipo: ${data.type} | Dura\xE7\xE3o estimada: ~${time4} min`,
42268
- "",
42269
- data.content
42270
- ];
42773
+ const isPractical = data.type === "exercise" || isCheckpoint(data.title);
42774
+ const lines = [`\u2501\u2501\u2501 Li\xE7\xE3o: ${data.title} \u2501\u2501\u2501`];
42775
+ if (isPractical && workspacePath) {
42776
+ const base = path13.basename(workspacePath) === ".tostudy" ? path13.dirname(workspacePath) : workspacePath;
42777
+ const slug = courseSlug(courseTitle);
42778
+ const vaultPath = resolveVaultPath(workspacePath, slug);
42779
+ lines.push(
42780
+ `Pasta de trabalho (workspace): ${base}`,
42781
+ `Cofre Obsidian (vault): ${vaultPath}`,
42782
+ ""
42783
+ );
42784
+ } else {
42785
+ lines.push("");
42786
+ }
42787
+ lines.push(`Tipo: ${data.type} | Dura\xE7\xE3o estimada: ~${time4} min`, "", data.content);
42271
42788
  if (data.acceptanceCriteria) {
42272
42789
  lines.push("", "Crit\xE9rios de aceita\xE7\xE3o:", data.acceptanceCriteria);
42273
42790
  }
@@ -42294,7 +42811,7 @@ var logger12, lessonCommand;
42294
42811
  var init_lesson = __esm({
42295
42812
  "src/commands/lesson.ts"() {
42296
42813
  "use strict";
42297
- init_src();
42814
+ init_dist();
42298
42815
  init_lessons2();
42299
42816
  init_courses();
42300
42817
  init_http();
@@ -42332,24 +42849,51 @@ var init_lesson = __esm({
42332
42849
  error("Nenhuma li\xE7\xE3o encontrada para leitura. Rode: tostudy start ou tostudy next");
42333
42850
  }
42334
42851
  const content = await getContent({ lessonId, enrollmentId: activeCourse.enrollmentId }, deps);
42852
+ const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
42853
+ const ws = await resolveEffectiveWorkspace(
42854
+ activeCourse.courseTitle,
42855
+ onboardingState?.workspacePath
42856
+ );
42857
+ if (ws.found && ws.workspacePath) {
42858
+ if (content.content) {
42859
+ content.content = replacePlaceholders(
42860
+ content.content,
42861
+ ws.workspacePath,
42862
+ activeCourse.courseTitle
42863
+ );
42864
+ }
42865
+ if (content.acceptanceCriteria) {
42866
+ content.acceptanceCriteria = replacePlaceholders(
42867
+ content.acceptanceCriteria,
42868
+ ws.workspacePath,
42869
+ activeCourse.courseTitle
42870
+ );
42871
+ }
42872
+ }
42335
42873
  if (opts.json) {
42336
42874
  output(content, { json: true });
42337
42875
  } else {
42338
- output(formatLessonContent(content), { json: false });
42876
+ output(
42877
+ formatLessonContent(
42878
+ content,
42879
+ ws.found ? ws.workspacePath : null,
42880
+ activeCourse.courseTitle
42881
+ ),
42882
+ { json: false }
42883
+ );
42339
42884
  }
42340
- if (content.type === "exercise") {
42341
- const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
42342
- const ws = await resolveEffectiveWorkspace(
42343
- activeCourse.courseTitle,
42344
- onboardingState?.workspacePath
42885
+ if (content.type === "exercise" && !ws.found) {
42886
+ process.stderr.write(
42887
+ "\n\u{1F4A1} Dica: rode `tostudy select` desta pasta para us\xE1-la como workspace, ou `tostudy workspace setup` para criar em ~/study/.\n"
42345
42888
  );
42346
- if (!ws.found) {
42347
- process.stderr.write(
42348
- "\n\u{1F4A1} Dica: rode `tostudy select` desta pasta para us\xE1-la como workspace, ou `tostudy workspace setup` para criar em ~/study/.\n"
42349
- );
42350
- }
42351
42889
  }
42352
42890
  } catch (err) {
42891
+ if (isEnrollmentNotEntitledError(err)) {
42892
+ const friendly = getErrors().enrollmentNotEntitled;
42893
+ if (opts.json) jsonError("enrollment_not_entitled", { message: friendly });
42894
+ else process.stderr.write(friendly);
42895
+ process.exit(1);
42896
+ }
42353
42897
  if (isInsufficientCreditsError(err)) {
42354
42898
  const friendly = getErrors().insufficientCredits;
42355
42899
  if (opts.json) jsonError("insufficient_credits", { message: friendly });
@@ -42370,7 +42914,7 @@ var logger13, hintCommand;
42370
42914
  var init_hint = __esm({
42371
42915
  "src/commands/hint.ts"() {
42372
42916
  "use strict";
42373
- init_src();
42917
+ init_dist();
42374
42918
  init_lessons2();
42375
42919
  init_http();
42376
42920
  init_guards();
@@ -42396,11 +42940,17 @@ var init_hint = __esm({
42396
42940
  output(formatHint(hint), { json: false });
42397
42941
  }
42398
42942
  } catch (err) {
42943
+ if (isEnrollmentNotEntitledError(err)) {
42944
+ const friendly = getErrors().enrollmentNotEntitled;
42945
+ if (opts.json) jsonError("enrollment_not_entitled", { message: friendly });
42946
+ else process.stderr.write(friendly);
42947
+ process.exit(1);
42948
+ }
42399
42949
  if (isCourseArchivedError(err)) {
42400
42950
  const friendly = getErrors().courseArchived;
42401
42951
  if (opts.json) jsonError("course_archived", { message: friendly });
42402
- error(friendly);
42403
- return;
42952
+ else process.stderr.write(friendly);
42953
+ process.exit(1);
42404
42954
  }
42405
42955
  if (isInsufficientCreditsError(err)) {
42406
42956
  const friendly = getErrors().insufficientCredits;
@@ -42436,7 +42986,7 @@ var init_validate_solution = __esm({
42436
42986
  var init_level_validator = __esm({
42437
42987
  "../../packages/tostudy-core/src/exercises/level-validator.ts"() {
42438
42988
  "use strict";
42439
- init_src3();
42989
+ init_src2();
42440
42990
  }
42441
42991
  });
42442
42992
 
@@ -42731,14 +43281,15 @@ var init_init_template = __esm({
42731
43281
 
42732
43282
  // src/commands/validate.ts
42733
43283
  import fs12 from "node:fs";
42734
- import path13 from "node:path";
43284
+ import path14 from "node:path";
42735
43285
  import { Command as Command13 } from "commander";
42736
43286
  var logger14, validateCommand;
42737
43287
  var init_validate = __esm({
42738
43288
  "src/commands/validate.ts"() {
42739
43289
  "use strict";
42740
- init_src();
43290
+ init_dist();
42741
43291
  init_exercises();
43292
+ init_courses();
42742
43293
  init_http();
42743
43294
  init_guards();
42744
43295
  init_course_state();
@@ -42752,7 +43303,22 @@ var init_validate = __esm({
42752
43303
  const activeCourse = await requireActiveCourse();
42753
43304
  const driftWarning = await checkCourseDrift();
42754
43305
  if (driftWarning) process.stderr.write(driftWarning + "\n");
42755
- const lessonId = activeCourse.currentLessonId;
43306
+ const data = createHttpProvider(session.apiUrl, session.token);
43307
+ const deps = { data, logger: logger14 };
43308
+ let lessonId = activeCourse.currentLessonId;
43309
+ if (!lessonId) {
43310
+ try {
43311
+ const prog = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
43312
+ if (prog.isArchived) {
43313
+ const friendly = getErrors().courseArchived;
43314
+ if (opts.json) jsonError("course_archived", { message: friendly });
43315
+ else process.stderr.write(friendly);
43316
+ process.exit(1);
43317
+ }
43318
+ lessonId = prog.currentLesson?.id || void 0;
43319
+ } catch {
43320
+ }
43321
+ }
42756
43322
  if (!lessonId) {
42757
43323
  if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
42758
43324
  error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
@@ -42773,7 +43339,7 @@ var init_validate = __esm({
42773
43339
  error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
42774
43340
  }
42775
43341
  if (file2 && activeCourse.courseTags?.length) {
42776
- const ext = path13.extname(file2).toLowerCase();
43342
+ const ext = path14.extname(file2).toLowerCase();
42777
43343
  const LANG_EXTENSIONS = {
42778
43344
  ".html": ["html", "html5"],
42779
43345
  ".css": ["css"],
@@ -42808,8 +43374,6 @@ var init_validate = __esm({
42808
43374
  }
42809
43375
  }
42810
43376
  }
42811
- const data = createHttpProvider(session.apiUrl, session.token);
42812
- const deps = { data, logger: logger14 };
42813
43377
  const result = await validateSolution(
42814
43378
  {
42815
43379
  lessonId,
@@ -42826,11 +43390,17 @@ var init_validate = __esm({
42826
43390
  }
42827
43391
  process.exit(result.passed ? 0 : 1);
42828
43392
  } catch (err) {
43393
+ if (isEnrollmentNotEntitledError(err)) {
43394
+ const friendly = getErrors().enrollmentNotEntitled;
43395
+ if (opts.json) jsonError("enrollment_not_entitled", { message: friendly });
43396
+ else process.stderr.write(friendly);
43397
+ process.exit(1);
43398
+ }
42829
43399
  if (isCourseArchivedError(err)) {
42830
43400
  const friendly = getErrors().courseArchived;
42831
43401
  if (opts.json) jsonError("course_archived", { message: friendly });
42832
- error(friendly);
42833
- return;
43402
+ else process.stderr.write(friendly);
43403
+ process.exit(1);
42834
43404
  }
42835
43405
  if (isInsufficientCreditsError(err)) {
42836
43406
  const friendly = getErrors().insufficientCredits;
@@ -43187,7 +43757,7 @@ var logger15, defaultDeps4, initCommand;
43187
43757
  var init_init = __esm({
43188
43758
  "src/commands/init.ts"() {
43189
43759
  "use strict";
43190
- init_src();
43760
+ init_dist();
43191
43761
  init_courses();
43192
43762
  init_http();
43193
43763
  init_session_store();
@@ -43226,13 +43796,13 @@ var init_init = __esm({
43226
43796
 
43227
43797
  // ../../packages/tostudy-core/src/workspace/setup-workspace.ts
43228
43798
  import fs13 from "node:fs/promises";
43229
- import path14 from "node:path";
43799
+ import path15 from "node:path";
43230
43800
  async function setupWorkspace(input2) {
43231
- const workspacePath = path14.join(input2.basePath, input2.courseSlug);
43801
+ const workspacePath = path15.join(input2.basePath, input2.courseSlug);
43232
43802
  for (const dir of WORKSPACE_DIRS) {
43233
- await fs13.mkdir(path14.join(workspacePath, dir), { recursive: true });
43803
+ await fs13.mkdir(path15.join(workspacePath, dir), { recursive: true });
43234
43804
  }
43235
- const configPath = path14.join(workspacePath, ".ana-config.json");
43805
+ const configPath = path15.join(workspacePath, ".ana-config.json");
43236
43806
  const config2 = {
43237
43807
  courseId: input2.courseId,
43238
43808
  courseSlug: input2.courseSlug,
@@ -43267,7 +43837,7 @@ async function setupWorkspace(input2) {
43267
43837
  "tostudy vault sync # Sincronizar progresso",
43268
43838
  "```"
43269
43839
  ].join("\n");
43270
- await fs13.writeFile(path14.join(workspacePath, "README.md"), readme, "utf-8");
43840
+ await fs13.writeFile(path15.join(workspacePath, "README.md"), readme, "utf-8");
43271
43841
  return { workspacePath, directories: WORKSPACE_DIRS, configPath };
43272
43842
  }
43273
43843
  var WORKSPACE_DIRS;
@@ -43369,7 +43939,7 @@ var init_templates = __esm({
43369
43939
 
43370
43940
  // ../../packages/tostudy-core/src/workspace/extract-exercise.ts
43371
43941
  import fs14 from "node:fs/promises";
43372
- import path15 from "node:path";
43942
+ import path16 from "node:path";
43373
43943
  function padOrder(n) {
43374
43944
  return String(n).padStart(2, "0");
43375
43945
  }
@@ -43393,15 +43963,15 @@ async function extractExercise(input2) {
43393
43963
  const { lessonData, exerciseTier, workspacePath } = input2;
43394
43964
  const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
43395
43965
  const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
43396
- const exercisePath = path15.join(workspacePath, "exercises", moduleDir, lessonDir);
43966
+ const exercisePath = path16.join(workspacePath, "exercises", moduleDir, lessonDir);
43397
43967
  await fs14.mkdir(exercisePath, { recursive: true });
43398
43968
  const extractedFiles = [];
43399
43969
  let hasStarterCode = false;
43400
43970
  if (lessonData.sandpackConfig?.files) {
43401
43971
  for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
43402
43972
  const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
43403
- const fullPath = path15.join(exercisePath, cleanPath);
43404
- await fs14.mkdir(path15.dirname(fullPath), { recursive: true });
43973
+ const fullPath = path16.join(exercisePath, cleanPath);
43974
+ await fs14.mkdir(path16.dirname(fullPath), { recursive: true });
43405
43975
  await fs14.writeFile(fullPath, fileData.code, "utf-8");
43406
43976
  extractedFiles.push(cleanPath);
43407
43977
  hasStarterCode = true;
@@ -43410,13 +43980,13 @@ async function extractExercise(input2) {
43410
43980
  const tierData = getTierData(lessonData.structuredData, exerciseTier);
43411
43981
  const tierCode = tierData?.code;
43412
43982
  if (tierCode) {
43413
- await fs14.writeFile(path15.join(exercisePath, "exercise.js"), tierCode, "utf-8");
43983
+ await fs14.writeFile(path16.join(exercisePath, "exercise.js"), tierCode, "utf-8");
43414
43984
  extractedFiles.push("exercise.js");
43415
43985
  hasStarterCode = true;
43416
43986
  } else {
43417
43987
  const starter = getStarterCode(lessonData.structuredData);
43418
43988
  if (starter) {
43419
- await fs14.writeFile(path15.join(exercisePath, "exercise.js"), starter, "utf-8");
43989
+ await fs14.writeFile(path16.join(exercisePath, "exercise.js"), starter, "utf-8");
43420
43990
  extractedFiles.push("exercise.js");
43421
43991
  hasStarterCode = true;
43422
43992
  }
@@ -43435,7 +44005,7 @@ async function extractExercise(input2) {
43435
44005
  }
43436
44006
  };
43437
44007
  await fs14.writeFile(
43438
- path15.join(exercisePath, "package.json"),
44008
+ path16.join(exercisePath, "package.json"),
43439
44009
  JSON.stringify(pkgJson, null, 2),
43440
44010
  "utf-8"
43441
44011
  );
@@ -43447,19 +44017,19 @@ async function extractExercise(input2) {
43447
44017
  );
43448
44018
  for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
43449
44019
  if (!sandpackFileNames.has(configFile)) {
43450
- await fs14.writeFile(path15.join(exercisePath, configFile), configContent, "utf-8");
44020
+ await fs14.writeFile(path16.join(exercisePath, configFile), configContent, "utf-8");
43451
44021
  extractedFiles.push(configFile);
43452
44022
  }
43453
44023
  }
43454
44024
  const setupSh = `#!/bin/sh
43455
44025
  ${scaffold.setupScript}
43456
44026
  `;
43457
- await fs14.writeFile(path15.join(exercisePath, "setup.sh"), setupSh, "utf-8");
44027
+ await fs14.writeFile(path16.join(exercisePath, "setup.sh"), setupSh, "utf-8");
43458
44028
  extractedFiles.push("setup.sh");
43459
44029
  }
43460
44030
  }
43461
44031
  const readme = generateReadme(lessonData, exerciseTier);
43462
- const readmePath = path15.join(exercisePath, "README.md");
44032
+ const readmePath = path16.join(exercisePath, "README.md");
43463
44033
  await fs14.writeFile(readmePath, readme, "utf-8");
43464
44034
  extractedFiles.push("README.md");
43465
44035
  return {
@@ -43532,14 +44102,14 @@ var init_workspace = __esm({
43532
44102
 
43533
44103
  // src/commands/workspace.ts
43534
44104
  import { Command as Command16 } from "commander";
43535
- import path16 from "node:path";
43536
- import os12 from "node:os";
44105
+ import path17 from "node:path";
44106
+ import os9 from "node:os";
43537
44107
  import fs15 from "node:fs/promises";
43538
44108
  var logger16, workspaceCommand;
43539
44109
  var init_workspace2 = __esm({
43540
44110
  "src/commands/workspace.ts"() {
43541
44111
  "use strict";
43542
- init_src();
44112
+ init_dist();
43543
44113
  init_workspace();
43544
44114
  init_guards();
43545
44115
  init_course_state();
@@ -43558,13 +44128,13 @@ var init_workspace2 = __esm({
43558
44128
  let directories;
43559
44129
  if (!opts.path && cwdIsWorkspace) {
43560
44130
  const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
43561
- workspacePath = resolvedCwd ?? path16.join(process.cwd(), ".tostudy");
44131
+ workspacePath = resolvedCwd ?? path17.join(process.cwd(), ".tostudy");
43562
44132
  await fs15.mkdir(workspacePath, { recursive: true });
43563
44133
  directories = ["exercises", "generated", "notes", "diagrams"];
43564
44134
  for (const dir of directories) {
43565
- await fs15.mkdir(path16.join(workspacePath, dir), { recursive: true });
44135
+ await fs15.mkdir(path17.join(workspacePath, dir), { recursive: true });
43566
44136
  }
43567
- const configPath = path16.join(workspacePath, ".ana-config.json");
44137
+ const configPath = path17.join(workspacePath, ".ana-config.json");
43568
44138
  await fs15.writeFile(
43569
44139
  configPath,
43570
44140
  JSON.stringify(
@@ -43583,7 +44153,7 @@ var init_workspace2 = __esm({
43583
44153
  "utf-8"
43584
44154
  );
43585
44155
  } else {
43586
- const basePath = opts.path ?? path16.join(os12.homedir(), "study");
44156
+ const basePath = opts.path ?? path17.join(os9.homedir(), "study");
43587
44157
  const result2 = await setupWorkspace({
43588
44158
  courseId: activeCourse.courseId,
43589
44159
  courseSlug: courseSlug(activeCourse.courseTitle),
@@ -43617,7 +44187,7 @@ Pr\xF3ximo passo: tostudy export
43617
44187
  process.exit(1);
43618
44188
  }
43619
44189
  });
43620
- workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path16.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
44190
+ workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path17.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43621
44191
  try {
43622
44192
  const activeCourse = await requireActiveCourse();
43623
44193
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -43634,22 +44204,22 @@ Pr\xF3ximo passo: tostudy export
43634
44204
  const workspacePath = ws.workspacePath;
43635
44205
  let configData = null;
43636
44206
  try {
43637
- const raw = await fs15.readFile(path16.join(workspacePath, ".ana-config.json"), "utf-8");
44207
+ const raw = await fs15.readFile(path17.join(workspacePath, ".ana-config.json"), "utf-8");
43638
44208
  configData = JSON.parse(raw);
43639
44209
  } catch {
43640
44210
  configData = null;
43641
44211
  }
43642
- const exercisesDir = path16.join(workspacePath, "exercises");
44212
+ const exercisesDir = path17.join(workspacePath, "exercises");
43643
44213
  let exerciseCount = 0;
43644
44214
  try {
43645
44215
  const moduleDirs = await fs15.readdir(exercisesDir);
43646
44216
  for (const modDir of moduleDirs) {
43647
- const modPath = path16.join(exercisesDir, modDir);
44217
+ const modPath = path17.join(exercisesDir, modDir);
43648
44218
  const stat = await fs15.stat(modPath);
43649
44219
  if (stat.isDirectory()) {
43650
44220
  const lessonDirs = await fs15.readdir(modPath);
43651
44221
  for (const lessonDir of lessonDirs) {
43652
- const lessonPath = path16.join(modPath, lessonDir);
44222
+ const lessonPath = path17.join(modPath, lessonDir);
43653
44223
  const lstat = await fs15.stat(lessonPath);
43654
44224
  if (lstat.isDirectory()) exerciseCount++;
43655
44225
  }
@@ -43657,14 +44227,14 @@ Pr\xF3ximo passo: tostudy export
43657
44227
  }
43658
44228
  } catch {
43659
44229
  }
43660
- const generatedDir = path16.join(workspacePath, "generated");
44230
+ const generatedDir = path17.join(workspacePath, "generated");
43661
44231
  let artifactCount = 0;
43662
44232
  try {
43663
44233
  const files = await fs15.readdir(generatedDir);
43664
44234
  artifactCount = files.length;
43665
44235
  } catch {
43666
44236
  }
43667
- const diagramsDir = path16.join(workspacePath, "diagrams");
44237
+ const diagramsDir = path17.join(workspacePath, "diagrams");
43668
44238
  let diagramCount = 0;
43669
44239
  try {
43670
44240
  const files = await fs15.readdir(diagramsDir);
@@ -43716,14 +44286,14 @@ Pr\xF3ximo passo: tostudy export
43716
44286
 
43717
44287
  // src/commands/export.ts
43718
44288
  import { Command as Command17 } from "commander";
43719
- import path17 from "node:path";
43720
- import os13 from "node:os";
44289
+ import path18 from "node:path";
44290
+ import os10 from "node:os";
43721
44291
  import fs16 from "node:fs/promises";
43722
44292
  var logger17, exportCommand;
43723
44293
  var init_export = __esm({
43724
44294
  "src/commands/export.ts"() {
43725
44295
  "use strict";
43726
- init_src();
44296
+ init_dist();
43727
44297
  init_workspace();
43728
44298
  init_http();
43729
44299
  init_guards();
@@ -43731,7 +44301,7 @@ var init_export = __esm({
43731
44301
  init_resolve();
43732
44302
  init_errors();
43733
44303
  logger17 = createLogger("cli:export");
43734
- exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path17.join(os13.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
44304
+ exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path18.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43735
44305
  try {
43736
44306
  const session = await requireSession();
43737
44307
  const activeCourse = await requireActiveCourse();
@@ -43749,7 +44319,7 @@ var init_export = __esm({
43749
44319
  opts.path
43750
44320
  );
43751
44321
  if (ws.found && ws.source === "cwd" && ws.workspacePath) {
43752
- const configPath = path17.join(ws.workspacePath, ".ana-config.json");
44322
+ const configPath = path18.join(ws.workspacePath, ".ana-config.json");
43753
44323
  let hasConfig = false;
43754
44324
  try {
43755
44325
  await fs16.access(configPath);
@@ -43761,7 +44331,7 @@ var init_export = __esm({
43761
44331
  logger17.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
43762
44332
  await fs16.mkdir(ws.workspacePath, { recursive: true });
43763
44333
  for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
43764
- await fs16.mkdir(path17.join(ws.workspacePath, dir), { recursive: true });
44334
+ await fs16.mkdir(path18.join(ws.workspacePath, dir), { recursive: true });
43765
44335
  }
43766
44336
  await fs16.writeFile(
43767
44337
  configPath,
@@ -43781,7 +44351,7 @@ var init_export = __esm({
43781
44351
  "utf-8"
43782
44352
  );
43783
44353
  await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
43784
- const isNamespaced = ws.workspacePath === path17.join(process.cwd(), ".tostudy");
44354
+ const isNamespaced = ws.workspacePath === path18.join(process.cwd(), ".tostudy");
43785
44355
  process.stderr.write(
43786
44356
  isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
43787
44357
  ` : `\u2728 Workspace inicializado nesta pasta.
@@ -43832,19 +44402,19 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
43832
44402
  // src/commands/open.ts
43833
44403
  import { Command as Command18 } from "commander";
43834
44404
  import { execFile as execFile3 } from "node:child_process";
43835
- import path18 from "node:path";
43836
- import os14 from "node:os";
44405
+ import path19 from "node:path";
44406
+ import os11 from "node:os";
43837
44407
  var logger18, openCommand;
43838
44408
  var init_open = __esm({
43839
44409
  "src/commands/open.ts"() {
43840
44410
  "use strict";
43841
- init_src();
44411
+ init_dist();
43842
44412
  init_guards();
43843
44413
  init_course_state();
43844
44414
  init_resolve();
43845
44415
  init_errors();
43846
44416
  logger18 = createLogger("cli:open");
43847
- openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path18.join(os14.homedir(), "study")).action(async (opts) => {
44417
+ openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path19.join(os11.homedir(), "study")).action(async (opts) => {
43848
44418
  try {
43849
44419
  const activeCourse = await requireActiveCourse();
43850
44420
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -43898,14 +44468,14 @@ var init_types2 = __esm({
43898
44468
 
43899
44469
  // ../../packages/tostudy-core/src/vault/write-vault.ts
43900
44470
  import fs17 from "node:fs/promises";
43901
- import path19 from "node:path";
44471
+ import path20 from "node:path";
43902
44472
  async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
43903
44473
  for (const file2 of files) {
43904
- const fullPath = path19.join(outputPath, file2.relativePath);
43905
- await fs17.mkdir(path19.dirname(fullPath), { recursive: true });
44474
+ const fullPath = path20.join(outputPath, file2.relativePath);
44475
+ await fs17.mkdir(path20.dirname(fullPath), { recursive: true });
43906
44476
  await fs17.writeFile(fullPath, file2.content, "utf-8");
43907
44477
  }
43908
- const vaultPath = path19.join(outputPath, courseSlug2);
44478
+ const vaultPath = path20.join(outputPath, courseSlug2);
43909
44479
  const marker = {
43910
44480
  courseId,
43911
44481
  courseSlug: courseSlug2,
@@ -43914,7 +44484,7 @@ async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
43914
44484
  };
43915
44485
  await fs17.mkdir(vaultPath, { recursive: true });
43916
44486
  await fs17.writeFile(
43917
- path19.join(vaultPath, VAULT_MARKER_FILENAME),
44487
+ path20.join(vaultPath, VAULT_MARKER_FILENAME),
43918
44488
  JSON.stringify(marker, null, 2),
43919
44489
  "utf-8"
43920
44490
  );
@@ -43939,14 +44509,14 @@ var init_vault = __esm({
43939
44509
 
43940
44510
  // src/commands/vault.ts
43941
44511
  import { Command as Command19 } from "commander";
43942
- import path20 from "node:path";
43943
- import os15 from "node:os";
44512
+ import path21 from "node:path";
44513
+ import os12 from "node:os";
43944
44514
  import fs18 from "node:fs/promises";
43945
44515
  var logger19, vaultCommand;
43946
44516
  var init_vault2 = __esm({
43947
44517
  "src/commands/vault.ts"() {
43948
44518
  "use strict";
43949
- init_src();
44519
+ init_dist();
43950
44520
  init_vault();
43951
44521
  init_courses();
43952
44522
  init_http();
@@ -43956,7 +44526,7 @@ var init_vault2 = __esm({
43956
44526
  init_errors();
43957
44527
  logger19 = createLogger("cli:vault");
43958
44528
  vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
43959
- vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path20.join(os15.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
44529
+ vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path21.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43960
44530
  try {
43961
44531
  const session = await requireSession();
43962
44532
  const activeCourse = await requireActiveCourse();
@@ -44038,7 +44608,7 @@ Para visualizar:
44038
44608
  process.exit(1);
44039
44609
  }
44040
44610
  });
44041
- vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path20.join(os15.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
44611
+ vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path21.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
44042
44612
  try {
44043
44613
  const session = await requireSession();
44044
44614
  const activeCourse = await requireActiveCourse();
@@ -44064,7 +44634,7 @@ Para visualizar:
44064
44634
  const data = createHttpProvider(session.apiUrl, session.token);
44065
44635
  const deps = { data, logger: logger19 };
44066
44636
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
44067
- const markerPath = path20.join(vaultPath, ".ana-vault.json");
44637
+ const markerPath = path21.join(vaultPath, ".ana-vault.json");
44068
44638
  const markerRaw = await fs18.readFile(markerPath, "utf-8");
44069
44639
  const marker = JSON.parse(markerRaw);
44070
44640
  marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -44074,7 +44644,7 @@ Para visualizar:
44074
44644
  currentLesson: progress3.currentLesson.title
44075
44645
  };
44076
44646
  await fs18.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
44077
- const courseIndexPath = path20.join(vaultPath, slug, "index.md");
44647
+ const courseIndexPath = path21.join(vaultPath, slug, "index.md");
44078
44648
  try {
44079
44649
  let indexContent = await fs18.readFile(courseIndexPath, "utf-8");
44080
44650
  indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
@@ -44200,7 +44770,7 @@ var logger20, syncCommand;
44200
44770
  var init_sync = __esm({
44201
44771
  "src/commands/sync.ts"() {
44202
44772
  "use strict";
44203
- init_src();
44773
+ init_dist();
44204
44774
  init_guards();
44205
44775
  init_formatter();
44206
44776
  init_instruction_pipeline();
@@ -44257,7 +44827,7 @@ var logger21, briefCommand;
44257
44827
  var init_brief = __esm({
44258
44828
  "src/commands/brief.ts"() {
44259
44829
  "use strict";
44260
- init_src();
44830
+ init_dist();
44261
44831
  init_guards();
44262
44832
  init_cache();
44263
44833
  init_api();
@@ -44309,7 +44879,7 @@ var logger22, briefCreateCommand;
44309
44879
  var init_brief_create = __esm({
44310
44880
  "src/commands/brief-create.ts"() {
44311
44881
  "use strict";
44312
- init_src();
44882
+ init_dist();
44313
44883
  init_guards();
44314
44884
  init_bootstrap();
44315
44885
  init_api();
@@ -44387,15 +44957,15 @@ var init_brief_open = __esm({
44387
44957
 
44388
44958
  // src/sessions/storage.ts
44389
44959
  import fs19 from "node:fs";
44390
- import path21 from "node:path";
44960
+ import path22 from "node:path";
44391
44961
  function sessionsDir(workspacePath) {
44392
- return path21.join(workspacePath, ".tostudy", "sessions");
44962
+ return path22.join(workspacePath, ".tostudy", "sessions");
44393
44963
  }
44394
44964
  async function saveModuleSummary(workspacePath, input2) {
44395
44965
  const dir = sessionsDir(workspacePath);
44396
44966
  fs19.mkdirSync(dir, { recursive: true });
44397
44967
  const filename = `module-${input2.moduleId}-summary.md`;
44398
- const filePath = path21.join(dir, filename);
44968
+ const filePath = path22.join(dir, filename);
44399
44969
  const header = [
44400
44970
  "---",
44401
44971
  `moduleId: ${input2.moduleId}`,
@@ -44414,7 +44984,7 @@ async function loadSessionContext(workspacePath) {
44414
44984
  const files = fs19.readdirSync(dir).filter((f) => f.startsWith("module-") && f.endsWith("-summary.md")).sort();
44415
44985
  const summaries = [];
44416
44986
  for (const file2 of files) {
44417
- const content = fs19.readFileSync(path21.join(dir, file2), "utf-8");
44987
+ const content = fs19.readFileSync(path22.join(dir, file2), "utf-8");
44418
44988
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
44419
44989
  if (!frontmatterMatch) continue;
44420
44990
  const meta3 = frontmatterMatch[1];
@@ -44430,7 +45000,7 @@ var logger23;
44430
45000
  var init_storage = __esm({
44431
45001
  "src/sessions/storage.ts"() {
44432
45002
  "use strict";
44433
- init_src();
45003
+ init_dist();
44434
45004
  logger23 = createLogger("cli:sessions");
44435
45005
  }
44436
45006
  });
@@ -44442,7 +45012,7 @@ var logger24, compactCommand;
44442
45012
  var init_compact = __esm({
44443
45013
  "src/commands/compact.ts"() {
44444
45014
  "use strict";
44445
- init_src();
45015
+ init_dist();
44446
45016
  init_guards();
44447
45017
  init_workspace_state();
44448
45018
  init_storage();
@@ -44491,7 +45061,7 @@ var logger25, contextCommand;
44491
45061
  var init_context = __esm({
44492
45062
  "src/commands/context.ts"() {
44493
45063
  "use strict";
44494
- init_src();
45064
+ init_dist();
44495
45065
  init_workspace_state();
44496
45066
  init_storage();
44497
45067
  init_formatter();
@@ -44545,21 +45115,125 @@ var init_context = __esm({
44545
45115
  }
44546
45116
  });
44547
45117
 
44548
- // src/commands/level.ts
45118
+ // src/commands/memory.ts
44549
45119
  import { Command as Command27 } from "commander";
45120
+ var logger26, memoryCommand;
45121
+ var init_memory = __esm({
45122
+ "src/commands/memory.ts"() {
45123
+ "use strict";
45124
+ init_dist();
45125
+ init_http();
45126
+ init_workspace_state();
45127
+ init_guards();
45128
+ init_formatter();
45129
+ logger26 = createLogger("cli:memory");
45130
+ memoryCommand = new Command27("memory").description("Load accumulated student memory (learning profile + recent lessons) for the tutor").option("--json", "Output structured JSON").action(async (opts) => {
45131
+ try {
45132
+ const ws = await findWorkspaceState();
45133
+ if (!ws) {
45134
+ if (opts.json) return jsonError("no_workspace");
45135
+ return error("Nenhum workspace encontrado. Rode: tostudy setup");
45136
+ }
45137
+ const { courseId, enrollmentId } = ws.state;
45138
+ if (!courseId || !enrollmentId) {
45139
+ if (opts.json) return jsonError("no_active_course");
45140
+ return error("Nenhum curso ativo. Rode: tostudy select");
45141
+ }
45142
+ const session = await requireSession();
45143
+ const data = createHttpProvider(session.apiUrl, session.token);
45144
+ const { context } = await data.memory.getContext(courseId, enrollmentId);
45145
+ if (opts.json) {
45146
+ output({ context }, { json: true });
45147
+ } else {
45148
+ output(context, { json: false });
45149
+ }
45150
+ } catch (err) {
45151
+ const msg = err instanceof Error ? err.message : String(err);
45152
+ if (msg.includes("process.exit")) return;
45153
+ logger26.warn("memory failed", { error: msg });
45154
+ if (opts.json) jsonError(msg);
45155
+ else error(msg);
45156
+ }
45157
+ });
45158
+ }
45159
+ });
45160
+
45161
+ // src/commands/insight.ts
45162
+ import { Command as Command28 } from "commander";
45163
+ var logger27, VALID_TYPES, insightCommand;
45164
+ var init_insight = __esm({
45165
+ "src/commands/insight.ts"() {
45166
+ "use strict";
45167
+ init_dist();
45168
+ init_http();
45169
+ init_workspace_state();
45170
+ init_guards();
45171
+ init_formatter();
45172
+ logger27 = createLogger("cli:insight");
45173
+ VALID_TYPES = ["difficulty", "breakthrough", "question"];
45174
+ insightCommand = new Command28("insight").description(
45175
+ "Persist a student cognitive insight (difficulty | breakthrough | question) into course memory"
45176
+ ).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) => {
45177
+ try {
45178
+ if (!VALID_TYPES.includes(type)) {
45179
+ if (opts.json) return jsonError("invalid_type");
45180
+ return error(`Tipo inv\xE1lido. Use: ${VALID_TYPES.join(" | ")}`);
45181
+ }
45182
+ const trimmed = content.trim();
45183
+ if (!trimmed) {
45184
+ if (opts.json) return jsonError("empty_content");
45185
+ return error("Conte\xFAdo vazio.");
45186
+ }
45187
+ const ws = await findWorkspaceState();
45188
+ if (!ws) {
45189
+ if (opts.json) return jsonError("no_workspace");
45190
+ return error("Nenhum workspace encontrado. Rode: tostudy setup");
45191
+ }
45192
+ const { courseId, enrollmentId } = ws.state;
45193
+ if (!courseId || !enrollmentId) {
45194
+ if (opts.json) return jsonError("no_active_course");
45195
+ return error("Nenhum curso ativo. Rode: tostudy select");
45196
+ }
45197
+ const session = await requireSession();
45198
+ const data = createHttpProvider(session.apiUrl, session.token);
45199
+ const res = await data.memory.recordInsight({
45200
+ courseId,
45201
+ enrollmentId,
45202
+ type,
45203
+ content: trimmed,
45204
+ ...opts.moduleId ? { moduleId: opts.moduleId } : {}
45205
+ });
45206
+ if (opts.json) {
45207
+ output(res, { json: true });
45208
+ } else {
45209
+ output(res.message, { json: false });
45210
+ }
45211
+ } catch (err) {
45212
+ const msg = err instanceof Error ? err.message : String(err);
45213
+ if (msg.includes("process.exit")) return;
45214
+ logger27.warn("insight failed", { error: msg });
45215
+ if (opts.json) jsonError(msg);
45216
+ else error(msg);
45217
+ }
45218
+ });
45219
+ }
45220
+ });
45221
+
45222
+ // src/commands/level.ts
45223
+ import { Command as Command29 } from "commander";
44550
45224
  function isExerciseLevel2(value) {
44551
45225
  return ALL_LEVELS.includes(value);
44552
45226
  }
44553
- var logger26, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
45227
+ var logger28, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
44554
45228
  var init_level = __esm({
44555
45229
  "src/commands/level.ts"() {
44556
45230
  "use strict";
44557
- init_src();
45231
+ init_dist();
44558
45232
  init_http();
44559
45233
  init_exercises();
44560
45234
  init_guards();
44561
45235
  init_formatter();
44562
- logger26 = createLogger("cli:level");
45236
+ logger28 = createLogger("cli:level");
44563
45237
  ALL_LEVELS = ["L0", "L1", "L2", "L3", "L4"];
44564
45238
  LEVEL_LABELS2 = {
44565
45239
  L0: "Pr\xE9-check (perguntas conceituais)",
@@ -44568,12 +45242,12 @@ var init_level = __esm({
44568
45242
  L3: "Guiado (passo a passo + checkpoints)",
44569
45243
  L4: "Desafio livre (folha em branco)"
44570
45244
  };
44571
- levelCommand = new Command27("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) => {
45245
+ levelCommand = new Command29("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) => {
44572
45246
  try {
44573
45247
  const session = await requireSession();
44574
45248
  const activeCourse = await requireActiveCourse();
44575
45249
  const data = createHttpProvider(session.apiUrl, session.token);
44576
- const deps = { data, logger: logger26 };
45250
+ const deps = { data, logger: logger28 };
44577
45251
  if (!rawLevel) {
44578
45252
  const levels = await getEnrollmentLevels({ enrollmentId: activeCourse.enrollmentId }, deps);
44579
45253
  if (opts.json) {
@@ -44631,18 +45305,18 @@ var init_level = __esm({
44631
45305
  });
44632
45306
 
44633
45307
  // src/commands/theory.ts
44634
- import { Command as Command28 } from "commander";
44635
- var logger27, EXERCISE_LEVELS2, theoryCommand;
45308
+ import { Command as Command30 } from "commander";
45309
+ var logger29, EXERCISE_LEVELS2, theoryCommand;
44636
45310
  var init_theory = __esm({
44637
45311
  "src/commands/theory.ts"() {
44638
45312
  "use strict";
44639
- init_src();
45313
+ init_dist();
44640
45314
  init_guards();
44641
45315
  init_formatter();
44642
45316
  init_errors();
44643
- logger27 = createLogger("cli:theory");
45317
+ logger29 = createLogger("cli:theory");
44644
45318
  EXERCISE_LEVELS2 = ["L0", "L1", "L2", "L3", "L4"];
44645
- theoryCommand = new Command28("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) => {
45319
+ theoryCommand = new Command30("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) => {
44646
45320
  try {
44647
45321
  const session = await requireSession();
44648
45322
  const active = await requireActiveCourse();
@@ -44679,7 +45353,7 @@ var init_theory = __esm({
44679
45353
  currentLevel = levels.currentLevel;
44680
45354
  }
44681
45355
  const requestUrl = `${session.apiUrl}/api/cli/exercises/more-theory`;
44682
- logger27.debug("Requesting more theory", { lessonId, currentLevel });
45356
+ logger29.debug("Requesting more theory", { lessonId, currentLevel });
44683
45357
  const res = await fetch(requestUrl, {
44684
45358
  method: "POST",
44685
45359
  headers: {
@@ -44997,7 +45671,7 @@ var init_resolve_enrollment = __esm({
44997
45671
  });
44998
45672
 
44999
45673
  // src/commands/export-grades.ts
45000
- import { Command as Command29 } from "commander";
45674
+ import { Command as Command31 } from "commander";
45001
45675
  function isFormat(value) {
45002
45676
  return SUPPORTED_FORMATS.includes(value);
45003
45677
  }
@@ -45084,15 +45758,15 @@ async function runExportGrades(opts, deps = defaultDeps5) {
45084
45758
  if (!rendered.endsWith("\n")) rendered += "\n";
45085
45759
  deps.stdoutWrite(rendered);
45086
45760
  }
45087
- var logger28, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
45761
+ var logger30, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
45088
45762
  var init_export_grades = __esm({
45089
45763
  "src/commands/export-grades.ts"() {
45090
45764
  "use strict";
45091
- init_src();
45765
+ init_dist();
45092
45766
  init_guards();
45093
45767
  init_grades();
45094
45768
  init_resolve_enrollment();
45095
- logger28 = createLogger("cli:export-grades");
45769
+ logger30 = createLogger("cli:export-grades");
45096
45770
  SUPPORTED_FORMATS = ["json", "csv", "md"];
45097
45771
  defaultDeps5 = {
45098
45772
  requireSession,
@@ -45102,10 +45776,10 @@ var init_export_grades = __esm({
45102
45776
  stderrWrite: (message) => process.stderr.write(message),
45103
45777
  // process.exit is typed as `never` — wrap so the cast is local to one line.
45104
45778
  exit: (code) => process.exit(code),
45105
- logger: logger28,
45779
+ logger: logger30,
45106
45780
  resolveBySlug: resolveEnrollmentBySlug
45107
45781
  };
45108
- exportGradesCommand = new Command29("export-grades").description("Exporta o hist\xF3rico de valida\xE7\xF5es (notas) do curso ativo").option(
45782
+ exportGradesCommand = new Command31("export-grades").description("Exporta o hist\xF3rico de valida\xE7\xF5es (notas) do curso ativo").option(
45109
45783
  "--course <slug>",
45110
45784
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, exporta o curso ativo."
45111
45785
  ).option("--format <json|csv|md>", "Formato de sa\xEDda (default: json)", "json").addHelpText(
@@ -45130,7 +45804,7 @@ C\xF3digos de sa\xEDda:
45130
45804
  });
45131
45805
 
45132
45806
  // src/commands/grades.ts
45133
- import { Command as Command30 } from "commander";
45807
+ import { Command as Command32 } from "commander";
45134
45808
  async function fetchAttempts(session, enrollmentId, deps) {
45135
45809
  const url2 = `${session.apiUrl}/api/cli/validations/by-enrollment?enrollmentId=${encodeURIComponent(
45136
45810
  enrollmentId
@@ -45271,29 +45945,29 @@ async function runGrades(opts, deps = defaultDeps6) {
45271
45945
  );
45272
45946
  deps.stdoutWrite(formatGradesAllEnrollmentsTable(summaries) + "\n");
45273
45947
  }
45274
- var logger29, defaultDeps6, gradesCommand;
45948
+ var logger31, defaultDeps6, gradesCommand;
45275
45949
  var init_grades2 = __esm({
45276
45950
  "src/commands/grades.ts"() {
45277
45951
  "use strict";
45278
- init_src();
45952
+ init_dist();
45279
45953
  init_http();
45280
45954
  init_courses();
45281
45955
  init_guards();
45282
45956
  init_grades();
45283
45957
  init_resolve_enrollment();
45284
- logger29 = createLogger("cli:grades");
45958
+ logger31 = createLogger("cli:grades");
45285
45959
  defaultDeps6 = {
45286
45960
  requireSession,
45287
45961
  fetch: globalThis.fetch.bind(globalThis),
45288
45962
  stdoutWrite: (message) => process.stdout.write(message),
45289
45963
  stderrWrite: (message) => process.stderr.write(message),
45290
45964
  exit: (code) => process.exit(code),
45291
- logger: logger29,
45965
+ logger: logger31,
45292
45966
  resolveBySlug: resolveEnrollmentBySlug,
45293
45967
  buildDataProvider: createHttpProvider,
45294
45968
  listCoursesFn: listCourses
45295
45969
  };
45296
- gradesCommand = new Command30("grades").description("Mostra um resumo das notas (todas as matr\xEDculas ou um curso espec\xEDfico)").option(
45970
+ gradesCommand = new Command32("grades").description("Mostra um resumo das notas (todas as matr\xEDculas ou um curso espec\xEDfico)").option(
45297
45971
  "--course <slug>",
45298
45972
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, lista todos os cursos."
45299
45973
  ).addHelpText(
@@ -45317,7 +45991,7 @@ C\xF3digos de sa\xEDda:
45317
45991
  });
45318
45992
 
45319
45993
  // src/commands/review.ts
45320
- import { Command as Command31 } from "commander";
45994
+ import { Command as Command33 } from "commander";
45321
45995
  import { createInterface } from "node:readline/promises";
45322
45996
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
45323
45997
  async function defaultPromptLessonChoice(lessonIds) {
@@ -45453,26 +46127,26 @@ async function runReview(slug, opts, deps = defaultDeps7) {
45453
46127
  }) + "\n"
45454
46128
  );
45455
46129
  }
45456
- var logger30, defaultDeps7, reviewCommand;
46130
+ var logger32, defaultDeps7, reviewCommand;
45457
46131
  var init_review2 = __esm({
45458
46132
  "src/commands/review.ts"() {
45459
46133
  "use strict";
45460
- init_src();
46134
+ init_dist();
45461
46135
  init_guards();
45462
46136
  init_grades();
45463
46137
  init_resolve_enrollment();
45464
- logger30 = createLogger("cli:review");
46138
+ logger32 = createLogger("cli:review");
45465
46139
  defaultDeps7 = {
45466
46140
  requireSession,
45467
46141
  fetch: globalThis.fetch.bind(globalThis),
45468
46142
  stdoutWrite: (message) => process.stdout.write(message),
45469
46143
  stderrWrite: (message) => process.stderr.write(message),
45470
46144
  exit: (code) => process.exit(code),
45471
- logger: logger30,
46145
+ logger: logger32,
45472
46146
  resolveBySlug: resolveEnrollmentBySlug,
45473
46147
  promptLessonChoice: defaultPromptLessonChoice
45474
46148
  };
45475
- reviewCommand = new Command31("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(
46149
+ reviewCommand = new Command33("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(
45476
46150
  "after",
45477
46151
  `
45478
46152
  Exemplos:
@@ -45501,9 +46175,9 @@ __export(cli_exports, {
45501
46175
  CLI_VERSION: () => CLI_VERSION,
45502
46176
  createProgram: () => createProgram
45503
46177
  });
45504
- import { Command as Command32 } from "commander";
46178
+ import { Command as Command34 } from "commander";
45505
46179
  function createProgram() {
45506
- const program2 = new Command32();
46180
+ const program2 = new Command34();
45507
46181
  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");
45508
46182
  program2.addCommand(setupCommand);
45509
46183
  program2.addCommand(doctorCommand);
@@ -45536,6 +46210,8 @@ function createProgram() {
45536
46210
  program2.addCommand(vaultCommand);
45537
46211
  program2.addCommand(compactCommand);
45538
46212
  program2.addCommand(contextCommand);
46213
+ program2.addCommand(memoryCommand);
46214
+ program2.addCommand(insightCommand);
45539
46215
  return program2;
45540
46216
  }
45541
46217
  var init_cli = __esm({
@@ -45567,6 +46243,8 @@ var init_cli = __esm({
45567
46243
  init_brief_open();
45568
46244
  init_compact();
45569
46245
  init_context();
46246
+ init_memory();
46247
+ init_insight();
45570
46248
  init_level();
45571
46249
  init_theory();
45572
46250
  init_export_grades();