@tostudy-ai/cli 0.11.1 → 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
  });
@@ -1310,10 +1344,19 @@ function formatProgress(data) {
1310
1344
  `Li\xE7\xF5es conclu\xEDdas: ${data.completedLessons}`,
1311
1345
  `Tempo restante estimado: ~${data.estimatedMinutesRemaining} min`,
1312
1346
  "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1313
- "",
1314
- "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
1315
- "\u2192 tostudy lesson para ver o conte\xFAdo da li\xE7\xE3o atual"
1347
+ ""
1316
1348
  ];
1349
+ if (data.isArchived) {
1350
+ lines.push(
1351
+ "\u26A0 Curso arquivado \u2014 somente leitura.",
1352
+ "\u2192 tostudy lesson para reler o conte\xFAdo j\xE1 estudado"
1353
+ );
1354
+ } else {
1355
+ lines.push(
1356
+ "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
1357
+ "\u2192 tostudy lesson para ver o conte\xFAdo da li\xE7\xE3o atual"
1358
+ );
1359
+ }
1317
1360
  return lines.join("\n");
1318
1361
  }
1319
1362
  function formatLesson(data) {
@@ -1598,8 +1641,9 @@ Rode \`tostudy context --json\` em sil\xEAncio para descobrir o estado do aluno.
1598
1641
  ### Se tudo pronto (workspace + curso + contexto):
1599
1642
 
1600
1643
  1. Leia os \`moduleSummaries\` do resultado para saber o que o aluno j\xE1 estudou.
1601
- 2. Se houver summaries, mencione brevemente: "Da \xFAltima vez paramos em {ultimoModulo}. Pronto pra continuar?"
1602
- 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.`);
1603
1647
  sections.push(renderHowToConduct());
1604
1648
  sections.push(renderSilentTools());
1605
1649
  sections.push(`## Compacta\xE7\xE3o de Contexto (Autom\xE1tico)
@@ -1799,6 +1843,10 @@ Estes comandos s\xE3o suas ferramentas. Rode em sil\xEAncio (sem anunciar), use
1799
1843
  - \`tostudy hint --json\` \u2014 dica progressiva (3 n\xEDveis).
1800
1844
  - \`tostudy validate <arquivo>\` \u2014 valida exerc\xEDcio (exit 0 = passou, 1 = falhou).
1801
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
+
1802
1850
  Voc\xEA nunca menciona estes comandos ao aluno. Ele fala com VOC\xCA, n\xE3o com o CLI.`;
1803
1851
  }
1804
1852
  function renderHandlingSituations() {
@@ -1819,6 +1867,7 @@ function renderTechnicalReference() {
1819
1867
  - Use \`--json\` em qualquer comando para sa\xEDda estruturada.
1820
1868
  - \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado).
1821
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).
1822
1871
  - \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\`.
1823
1872
  - \`tostudy progress --json\` retorna \`{ coursePercent, currentModule, currentLesson }\`.`;
1824
1873
  }
@@ -1930,7 +1979,7 @@ var logger2;
1930
1979
  var init_instruction_files = __esm({
1931
1980
  "src/workspace/instruction-files.ts"() {
1932
1981
  "use strict";
1933
- init_src();
1982
+ init_dist();
1934
1983
  init_instruction_template_v3();
1935
1984
  logger2 = createLogger("cli:instruction-files");
1936
1985
  }
@@ -1939,6 +1988,72 @@ var init_instruction_files = __esm({
1939
1988
  // src/commands/login.ts
1940
1989
  import { Command } from "commander";
1941
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
+ }
1942
2057
  function ideToPlatform(ideName) {
1943
2058
  const lower = ideName.toLowerCase();
1944
2059
  if (lower.includes("claude")) return "claude";
@@ -1952,7 +2067,7 @@ var logger3, DEFAULT_API_URL, PORT, loginCommand;
1952
2067
  var init_login = __esm({
1953
2068
  "src/commands/login.ts"() {
1954
2069
  "use strict";
1955
- init_src();
2070
+ init_dist();
1956
2071
  init_courses();
1957
2072
  init_http();
1958
2073
  init_oauth_server();
@@ -1961,8 +2076,30 @@ var init_login = __esm({
1961
2076
  logger3 = createLogger("cli:login");
1962
2077
  DEFAULT_API_URL = "https://tostudy.ai";
1963
2078
  PORT = 9876;
1964
- 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) => {
1965
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
+ }
1966
2103
  if (opts.manual) {
1967
2104
  console.log(`
1968
2105
  Acesse este endere\xE7o no seu browser:`);
@@ -2323,7 +2460,9 @@ var init_errors_pt_br = __esm({
2323
2460
  logoutSuccess: "\n Deslogado com sucesso.\n",
2324
2461
  workspaceCommandDescription: "Gerenciar workspace de estudo local",
2325
2462
  workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2326
- 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"
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",
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"
2327
2466
  };
2328
2467
  }
2329
2468
  });
@@ -2341,7 +2480,9 @@ var init_errors_en_us = __esm({
2341
2480
  logoutSuccess: "\n Logged out successfully.\n",
2342
2481
  workspaceCommandDescription: "Manage local study workspace",
2343
2482
  workspaceSetupDescription: "Create the workspace structure for the active course",
2344
- 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"
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",
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"
2345
2486
  };
2346
2487
  }
2347
2488
  });
@@ -2373,6 +2514,14 @@ function isInsufficientCreditsError(err) {
2373
2514
  const msg = err instanceof Error ? err.message : String(err);
2374
2515
  return msg.includes("INSUFFICIENT_CREDITS");
2375
2516
  }
2517
+ function isCourseArchivedError(err) {
2518
+ const msg = err instanceof Error ? err.message : String(err);
2519
+ return msg.includes("COURSE_ARCHIVED");
2520
+ }
2521
+ function isEnrollmentNotEntitledError(err) {
2522
+ const msg = err instanceof Error ? err.message : String(err);
2523
+ return msg.includes("ENROLLMENT_NOT_ENTITLED");
2524
+ }
2376
2525
  var BUNDLES, _cachedLocale;
2377
2526
  var init_errors = __esm({
2378
2527
  "src/errors/index.ts"() {
@@ -2569,7 +2718,7 @@ var CLI_VERSION;
2569
2718
  var init_version = __esm({
2570
2719
  "src/version.ts"() {
2571
2720
  "use strict";
2572
- CLI_VERSION = true ? "0.11.1" : "0.7.1";
2721
+ CLI_VERSION = true ? "0.12.0" : "0.7.1";
2573
2722
  }
2574
2723
  });
2575
2724
 
@@ -2583,16 +2732,9 @@ __export(update_checker_exports, {
2583
2732
  });
2584
2733
  import fs7 from "node:fs";
2585
2734
  import path9 from "node:path";
2586
- import os6 from "node:os";
2587
- function getConfigDir2() {
2588
- if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2589
- return path9.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2590
- }
2591
- return path9.join(os6.homedir(), ".tostudy");
2592
- }
2593
2735
  function readCache() {
2594
2736
  try {
2595
- const p = path9.join(getConfigDir2(), CACHE_FILE);
2737
+ const p = path9.join(getConfigDir(), CACHE_FILE);
2596
2738
  if (!fs7.existsSync(p)) return null;
2597
2739
  return JSON.parse(fs7.readFileSync(p, "utf-8"));
2598
2740
  } catch {
@@ -2601,7 +2743,7 @@ function readCache() {
2601
2743
  }
2602
2744
  function writeCache(cache) {
2603
2745
  try {
2604
- const dir = getConfigDir2();
2746
+ const dir = getConfigDir();
2605
2747
  fs7.mkdirSync(dir, { recursive: true });
2606
2748
  fs7.writeFileSync(path9.join(dir, CACHE_FILE), JSON.stringify(cache));
2607
2749
  } catch {
@@ -2664,6 +2806,7 @@ var PACKAGE_NAME, CACHE_FILE, CHECK_INTERVAL_MS, REGISTRY_TIMEOUT_MS;
2664
2806
  var init_update_checker = __esm({
2665
2807
  "src/update-checker.ts"() {
2666
2808
  "use strict";
2809
+ init_config_dir();
2667
2810
  PACKAGE_NAME = "@tostudy-ai/cli";
2668
2811
  CACHE_FILE = "update-check.json";
2669
2812
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -2673,19 +2816,12 @@ var init_update_checker = __esm({
2673
2816
 
2674
2817
  // src/learner-brief/cache.ts
2675
2818
  import fs8 from "node:fs";
2676
- import os7 from "node:os";
2677
2819
  import path10 from "node:path";
2678
- function getDefaultConfigDir() {
2679
- if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2680
- return path10.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2681
- }
2682
- return path10.join(os7.homedir(), ".tostudy");
2683
- }
2684
2820
  function resolveCachePath(configDir) {
2685
2821
  return path10.join(configDir, "student-brief.json");
2686
2822
  }
2687
2823
  async function readBriefCache(configDir) {
2688
- const dir = configDir ?? getDefaultConfigDir();
2824
+ const dir = configDir ?? getConfigDir();
2689
2825
  const p = resolveCachePath(dir);
2690
2826
  if (!fs8.existsSync(p)) return null;
2691
2827
  try {
@@ -2696,26 +2832,20 @@ async function readBriefCache(configDir) {
2696
2832
  }
2697
2833
  }
2698
2834
  async function writeBriefCache(configDir, cache) {
2699
- const dir = configDir ?? getDefaultConfigDir();
2835
+ const dir = configDir ?? getConfigDir();
2700
2836
  fs8.mkdirSync(dir, { recursive: true });
2701
2837
  fs8.writeFileSync(resolveCachePath(dir), JSON.stringify(cache, null, 2), { mode: 384 });
2702
2838
  }
2703
2839
  var init_cache = __esm({
2704
2840
  "src/learner-brief/cache.ts"() {
2705
2841
  "use strict";
2842
+ init_config_dir();
2706
2843
  }
2707
2844
  });
2708
2845
 
2709
2846
  // src/tutor-persona/cache.ts
2710
2847
  import fs9 from "node:fs";
2711
- import os8 from "node:os";
2712
2848
  import path11 from "node:path";
2713
- function getDefaultConfigDir2() {
2714
- if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2715
- return path11.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2716
- }
2717
- return path11.join(os8.homedir(), ".tostudy");
2718
- }
2719
2849
  function resolveCachePath2(configDir) {
2720
2850
  return path11.join(configDir, "tutor-personalities.json");
2721
2851
  }
@@ -2733,12 +2863,12 @@ function writeAll(configDir, cache) {
2733
2863
  fs9.writeFileSync(resolveCachePath2(configDir), JSON.stringify(cache, null, 2), { mode: 384 });
2734
2864
  }
2735
2865
  async function readPersonaCacheForCourse(configDir, courseId) {
2736
- const dir = configDir ?? getDefaultConfigDir2();
2866
+ const dir = configDir ?? getConfigDir();
2737
2867
  const all = readAll(dir);
2738
2868
  return all[courseId] ?? null;
2739
2869
  }
2740
2870
  async function writePersonaCacheForCourse(configDir, courseId, entry) {
2741
- const dir = configDir ?? getDefaultConfigDir2();
2871
+ const dir = configDir ?? getConfigDir();
2742
2872
  const all = readAll(dir);
2743
2873
  all[courseId] = entry;
2744
2874
  writeAll(dir, all);
@@ -2746,6 +2876,7 @@ async function writePersonaCacheForCourse(configDir, courseId, entry) {
2746
2876
  var init_cache2 = __esm({
2747
2877
  "src/tutor-persona/cache.ts"() {
2748
2878
  "use strict";
2879
+ init_config_dir();
2749
2880
  }
2750
2881
  });
2751
2882
 
@@ -2966,7 +3097,7 @@ var logger4, coursesCommand;
2966
3097
  var init_courses2 = __esm({
2967
3098
  "src/commands/courses.ts"() {
2968
3099
  "use strict";
2969
- init_src();
3100
+ init_dist();
2970
3101
  init_courses();
2971
3102
  init_http();
2972
3103
  init_guards();
@@ -3002,7 +3133,7 @@ var init_courses2 = __esm({
3002
3133
  // src/workspace/resolve.ts
3003
3134
  import fs10 from "node:fs/promises";
3004
3135
  import path12 from "node:path";
3005
- import os9 from "node:os";
3136
+ import os6 from "node:os";
3006
3137
  function courseSlug(title) {
3007
3138
  return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
3008
3139
  }
@@ -3033,7 +3164,7 @@ async function resolveCwdWorkspacePath(cwd = process.cwd()) {
3033
3164
  return null;
3034
3165
  }
3035
3166
  }
3036
- async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os9.homedir()) {
3167
+ async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os6.homedir()) {
3037
3168
  const home = path12.resolve(homeDir);
3038
3169
  let current = path12.resolve(startCwd);
3039
3170
  while (true) {
@@ -3064,7 +3195,7 @@ async function findExistingVault(workspacePath, slug) {
3064
3195
  return null;
3065
3196
  }
3066
3197
  }
3067
- 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()) {
3068
3199
  const cwdWorkspace = await findCwdWorkspaceUpwards(cwd, homeDir);
3069
3200
  if (cwdWorkspace) {
3070
3201
  return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
@@ -3086,7 +3217,7 @@ var DEFAULT_BASE;
3086
3217
  var init_resolve = __esm({
3087
3218
  "src/workspace/resolve.ts"() {
3088
3219
  "use strict";
3089
- DEFAULT_BASE = path12.join(os9.homedir(), "study");
3220
+ DEFAULT_BASE = path12.join(os6.homedir(), "study");
3090
3221
  }
3091
3222
  });
3092
3223
 
@@ -3115,31 +3246,15 @@ var init_status = __esm({
3115
3246
  });
3116
3247
 
3117
3248
  // src/learner-brief/api.ts
3118
- async function apiFetch2(url2, token2, init) {
3119
- const response = await fetch(url2, {
3120
- method: init?.method ?? "GET",
3121
- body: init?.body,
3122
- headers: {
3123
- "Content-Type": "application/json",
3124
- Authorization: `Bearer ${token2}`,
3125
- ...init?.headers ?? {}
3126
- }
3127
- });
3128
- const body = await response.json();
3129
- if (!response.ok) {
3130
- throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
3131
- }
3132
- return body;
3133
- }
3134
3249
  async function fetchLearnerBrief(input2) {
3135
- const response = await apiFetch2(
3250
+ const response = await cliApiFetch(
3136
3251
  `${input2.apiUrl}/api/cli/student/learner-brief`,
3137
3252
  input2.token
3138
3253
  );
3139
3254
  return response.brief;
3140
3255
  }
3141
3256
  async function upsertLearnerBrief(input2) {
3142
- const response = await apiFetch2(
3257
+ const response = await cliApiFetch(
3143
3258
  `${input2.apiUrl}/api/cli/student/learner-brief`,
3144
3259
  input2.token,
3145
3260
  {
@@ -3250,32 +3365,17 @@ var init_api2 = __esm({
3250
3365
  });
3251
3366
 
3252
3367
  // src/onboarding/api.ts
3253
- async function apiFetch3(url2, token2, init) {
3254
- const response = await fetch(url2, {
3255
- ...init,
3256
- headers: {
3257
- "Content-Type": "application/json",
3258
- Authorization: `Bearer ${token2}`,
3259
- ...init?.headers
3260
- }
3261
- });
3262
- const body = await response.json();
3263
- if (!response.ok) {
3264
- throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
3265
- }
3266
- return body;
3267
- }
3268
3368
  async function getRemoteEnrollmentOnboarding(input2) {
3269
3369
  const url2 = new URL(`${input2.apiUrl}/api/cli/onboarding`);
3270
3370
  url2.searchParams.set("enrollmentId", input2.enrollmentId);
3271
- const response = await apiFetch3(
3371
+ const response = await cliApiFetch(
3272
3372
  url2.toString(),
3273
3373
  input2.token
3274
3374
  );
3275
3375
  return response.onboarding;
3276
3376
  }
3277
3377
  async function saveRemoteEnrollmentOnboarding(input2) {
3278
- const response = await apiFetch3(
3378
+ const response = await cliApiFetch(
3279
3379
  `${input2.apiUrl}/api/cli/onboarding`,
3280
3380
  input2.token,
3281
3381
  {
@@ -3450,7 +3550,7 @@ var logger5, defaultDeps2;
3450
3550
  var init_instruction_pipeline = __esm({
3451
3551
  "src/instruction-pipeline.ts"() {
3452
3552
  "use strict";
3453
- init_src();
3553
+ init_dist();
3454
3554
  init_workspace_marker();
3455
3555
  init_workspace_state();
3456
3556
  init_api();
@@ -3554,7 +3654,7 @@ var logger6;
3554
3654
  var init_pipeline_deps = __esm({
3555
3655
  "src/commands/_shared/pipeline-deps.ts"() {
3556
3656
  "use strict";
3557
- init_src();
3657
+ init_dist();
3558
3658
  init_courses();
3559
3659
  init_http();
3560
3660
  init_instruction_pipeline();
@@ -3565,18 +3665,19 @@ var init_pipeline_deps = __esm({
3565
3665
 
3566
3666
  // src/commands/select.ts
3567
3667
  import { Command as Command6 } from "commander";
3568
- import os10 from "node:os";
3668
+ import os7 from "node:os";
3569
3669
  var logger7, selectCommand;
3570
3670
  var init_select = __esm({
3571
3671
  "src/commands/select.ts"() {
3572
3672
  "use strict";
3573
- init_src();
3673
+ init_dist();
3574
3674
  init_courses();
3575
3675
  init_http();
3576
3676
  init_guards();
3577
3677
  init_workspace_marker();
3578
3678
  init_resolve();
3579
3679
  init_formatter();
3680
+ init_errors();
3580
3681
  init_status();
3581
3682
  init_instruction_pipeline();
3582
3683
  init_pipeline_deps();
@@ -3649,7 +3750,7 @@ var init_select = __esm({
3649
3750
  );
3650
3751
  } else {
3651
3752
  const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
3652
- const home = os10.homedir();
3753
+ const home = os7.homedir();
3653
3754
  const cwd = process.cwd();
3654
3755
  const namespacedPath = `${cwd}/.tostudy`;
3655
3756
  let wsLine;
@@ -3684,6 +3785,10 @@ var init_select = __esm({
3684
3785
  output(lines.join("\n"), { json: false });
3685
3786
  }
3686
3787
  } catch (err) {
3788
+ if (isEnrollmentNotEntitledError(err)) {
3789
+ process.stderr.write(getErrors().enrollmentNotEntitled);
3790
+ process.exit(1);
3791
+ }
3687
3792
  const msg = err instanceof Error ? err.message : String(err);
3688
3793
  error(msg);
3689
3794
  }
@@ -3697,12 +3802,13 @@ var logger8, progressCommand;
3697
3802
  var init_progress = __esm({
3698
3803
  "src/commands/progress.ts"() {
3699
3804
  "use strict";
3700
- init_src();
3805
+ init_dist();
3701
3806
  init_courses();
3702
3807
  init_http();
3703
3808
  init_guards();
3704
3809
  init_course_state();
3705
3810
  init_formatter();
3811
+ init_errors();
3706
3812
  logger8 = createLogger("cli:progress");
3707
3813
  progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
3708
3814
  try {
@@ -3719,6 +3825,10 @@ var init_progress = __esm({
3719
3825
  output(formatProgress(progress3), { json: false });
3720
3826
  }
3721
3827
  } catch (err) {
3828
+ if (isEnrollmentNotEntitledError(err)) {
3829
+ process.stderr.write(getErrors().enrollmentNotEntitled);
3830
+ process.exit(1);
3831
+ }
3722
3832
  const msg = err instanceof Error ? err.message : String(err);
3723
3833
  error(msg);
3724
3834
  }
@@ -5272,7 +5382,7 @@ var init_query_promise = __esm({
5272
5382
  function mapResultRow(columns, row, joinsNotNullableMap) {
5273
5383
  const nullifyMap = {};
5274
5384
  const result = columns.reduce(
5275
- (result2, { path: path22, field }, columnIndex) => {
5385
+ (result2, { path: path23, field }, columnIndex) => {
5276
5386
  let decoder;
5277
5387
  if (is(field, Column)) {
5278
5388
  decoder = field;
@@ -5284,8 +5394,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
5284
5394
  decoder = field.sql.decoder;
5285
5395
  }
5286
5396
  let node = result2;
5287
- for (const [pathChunkIndex, pathChunk] of path22.entries()) {
5288
- if (pathChunkIndex < path22.length - 1) {
5397
+ for (const [pathChunkIndex, pathChunk] of path23.entries()) {
5398
+ if (pathChunkIndex < path23.length - 1) {
5289
5399
  if (!(pathChunk in node)) {
5290
5400
  node[pathChunk] = {};
5291
5401
  }
@@ -5293,8 +5403,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
5293
5403
  } else {
5294
5404
  const rawValue = row[columnIndex];
5295
5405
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
5296
- if (joinsNotNullableMap && is(field, Column) && path22.length === 2) {
5297
- const objectName = path22[0];
5406
+ if (joinsNotNullableMap && is(field, Column) && path23.length === 2) {
5407
+ const objectName = path23[0];
5298
5408
  if (!(objectName in nullifyMap)) {
5299
5409
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
5300
5410
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -9241,13 +9351,13 @@ function Subscribe(postgres2, options) {
9241
9351
  }
9242
9352
  }
9243
9353
  function handle(a, b2) {
9244
- const path22 = b2.relation.schema + "." + b2.relation.table;
9354
+ const path23 = b2.relation.schema + "." + b2.relation.table;
9245
9355
  call("*", a, b2);
9246
- call("*:" + path22, a, b2);
9247
- 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);
9248
9358
  call(b2.command, a, b2);
9249
- call(b2.command + ":" + path22, a, b2);
9250
- 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);
9251
9361
  }
9252
9362
  function pong() {
9253
9363
  const x2 = Buffer.alloc(34);
@@ -9360,8 +9470,8 @@ function parseEvent(x) {
9360
9470
  const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
9361
9471
  if (!xs)
9362
9472
  throw new Error("Malformed subscribe pattern: " + x);
9363
- const [, command, path22, key] = xs;
9364
- 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 : "");
9365
9475
  }
9366
9476
  var noop2;
9367
9477
  var init_subscribe = __esm({
@@ -9442,7 +9552,7 @@ var init_large = __esm({
9442
9552
  });
9443
9553
 
9444
9554
  // ../../node_modules/postgres/src/index.js
9445
- import os11 from "os";
9555
+ import os8 from "os";
9446
9556
  import fs11 from "fs";
9447
9557
  function Postgres(a, b2) {
9448
9558
  const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
@@ -9499,10 +9609,10 @@ function Postgres(a, b2) {
9499
9609
  });
9500
9610
  return query;
9501
9611
  }
9502
- function file2(path22, args = [], options2 = {}) {
9612
+ function file2(path23, args = [], options2 = {}) {
9503
9613
  arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
9504
9614
  const query = new Query([], args, (query2) => {
9505
- fs11.readFile(path22, "utf8", (err, string4) => {
9615
+ fs11.readFile(path23, "utf8", (err, string4) => {
9506
9616
  if (err)
9507
9617
  return query2.reject(err);
9508
9618
  query2.strings = [string4];
@@ -9820,13 +9930,13 @@ function parseUrl(url2) {
9820
9930
  }
9821
9931
  function osUsername() {
9822
9932
  try {
9823
- return os11.userInfo().username;
9933
+ return os8.userInfo().username;
9824
9934
  } catch (_) {
9825
9935
  return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
9826
9936
  }
9827
9937
  }
9828
9938
  var src_default;
9829
- var init_src2 = __esm({
9939
+ var init_src = __esm({
9830
9940
  "../../node_modules/postgres/src/index.js"() {
9831
9941
  init_types();
9832
9942
  init_connection();
@@ -14081,12 +14191,12 @@ var init_session2 = __esm({
14081
14191
  init_tracing();
14082
14192
  init_utils();
14083
14193
  PostgresJsPreparedQuery = class extends PgPreparedQuery {
14084
- constructor(client, queryString, params, logger31, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14194
+ constructor(client, queryString, params, logger33, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14085
14195
  super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
14086
14196
  this.client = client;
14087
14197
  this.queryString = queryString;
14088
14198
  this.params = params;
14089
- this.logger = logger31;
14199
+ this.logger = logger33;
14090
14200
  this.fields = fields;
14091
14201
  this._isResponseInArrayMode = _isResponseInArrayMode;
14092
14202
  this.customResultMapper = customResultMapper;
@@ -14227,11 +14337,11 @@ function construct(client, config2 = {}) {
14227
14337
  client.options.serializers["114"] = transparentParser;
14228
14338
  client.options.serializers["3802"] = transparentParser;
14229
14339
  const dialect = new PgDialect({ casing: config2.casing });
14230
- let logger31;
14340
+ let logger33;
14231
14341
  if (config2.logger === true) {
14232
- logger31 = new DefaultLogger();
14342
+ logger33 = new DefaultLogger();
14233
14343
  } else if (config2.logger !== false) {
14234
- logger31 = config2.logger;
14344
+ logger33 = config2.logger;
14235
14345
  }
14236
14346
  let schema;
14237
14347
  if (config2.schema) {
@@ -14245,7 +14355,7 @@ function construct(client, config2 = {}) {
14245
14355
  tableNamesMap: tablesConfig.tableNamesMap
14246
14356
  };
14247
14357
  }
14248
- 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 });
14249
14359
  const db2 = new PostgresJsDatabase(dialect, session, schema);
14250
14360
  db2.$client = client;
14251
14361
  db2.$cache = config2.cache;
@@ -14275,7 +14385,7 @@ function drizzle(...params) {
14275
14385
  var PostgresJsDatabase;
14276
14386
  var init_driver = __esm({
14277
14387
  "../../node_modules/drizzle-orm/postgres-js/driver.js"() {
14278
- init_src2();
14388
+ init_src();
14279
14389
  init_entity();
14280
14390
  init_logger();
14281
14391
  init_db();
@@ -14560,10 +14670,10 @@ function mergeDefs(...defs) {
14560
14670
  function cloneDef(schema) {
14561
14671
  return mergeDefs(schema._zod.def);
14562
14672
  }
14563
- function getElementAtPath(obj, path22) {
14564
- if (!path22)
14673
+ function getElementAtPath(obj, path23) {
14674
+ if (!path23)
14565
14675
  return obj;
14566
- return path22.reduce((acc, key) => acc?.[key], obj);
14676
+ return path23.reduce((acc, key) => acc?.[key], obj);
14567
14677
  }
14568
14678
  function promiseAllObject(promisesObj) {
14569
14679
  const keys = Object.keys(promisesObj);
@@ -14875,11 +14985,11 @@ function aborted(x, startIndex = 0) {
14875
14985
  }
14876
14986
  return false;
14877
14987
  }
14878
- function prefixIssues(path22, issues) {
14988
+ function prefixIssues(path23, issues) {
14879
14989
  return issues.map((iss) => {
14880
14990
  var _a2;
14881
14991
  (_a2 = iss).path ?? (_a2.path = []);
14882
- iss.path.unshift(path22);
14992
+ iss.path.unshift(path23);
14883
14993
  return iss;
14884
14994
  });
14885
14995
  }
@@ -15121,7 +15231,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
15121
15231
  }
15122
15232
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
15123
15233
  const result = { errors: [] };
15124
- const processError = (error50, path22 = []) => {
15234
+ const processError = (error50, path23 = []) => {
15125
15235
  var _a2, _b;
15126
15236
  for (const issue2 of error50.issues) {
15127
15237
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -15131,7 +15241,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
15131
15241
  } else if (issue2.code === "invalid_element") {
15132
15242
  processError({ issues: issue2.issues }, issue2.path);
15133
15243
  } else {
15134
- const fullpath = [...path22, ...issue2.path];
15244
+ const fullpath = [...path23, ...issue2.path];
15135
15245
  if (fullpath.length === 0) {
15136
15246
  result.errors.push(mapper(issue2));
15137
15247
  continue;
@@ -15163,8 +15273,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
15163
15273
  }
15164
15274
  function toDotPath(_path) {
15165
15275
  const segs = [];
15166
- const path22 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15167
- for (const seg of path22) {
15276
+ const path23 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15277
+ for (const seg of path23) {
15168
15278
  if (typeof seg === "number")
15169
15279
  segs.push(`[${seg}]`);
15170
15280
  else if (typeof seg === "symbol")
@@ -27858,13 +27968,13 @@ function resolveRef(ref, ctx) {
27858
27968
  if (!ref.startsWith("#")) {
27859
27969
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
27860
27970
  }
27861
- const path22 = ref.slice(1).split("/").filter(Boolean);
27862
- if (path22.length === 0) {
27971
+ const path23 = ref.slice(1).split("/").filter(Boolean);
27972
+ if (path23.length === 0) {
27863
27973
  return ctx.rootSchema;
27864
27974
  }
27865
27975
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
27866
- if (path22[0] === defsKey) {
27867
- const key = path22[1];
27976
+ if (path23[0] === defsKey) {
27977
+ const key = path23[1];
27868
27978
  if (!key || !ctx.defs[key]) {
27869
27979
  throw new Error(`Reference not found: ${ref}`);
27870
27980
  }
@@ -28629,6 +28739,7 @@ var init_users = __esm({
28629
28739
  "../../packages/database/src/schema/users.ts"() {
28630
28740
  "use strict";
28631
28741
  init_pg_core();
28742
+ init_drizzle_orm();
28632
28743
  userRoleEnum = pgEnum("user_role", [
28633
28744
  "student",
28634
28745
  "creator_provisional",
@@ -28676,7 +28787,7 @@ var init_users = __esm({
28676
28787
  photoUrl: text("photo_url"),
28677
28788
  // Cloudflare R2 URL for custom uploaded photo
28678
28789
  studentRole: studentRoleEnum("student_role"),
28679
- // 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
28680
28791
  referralSource: varchar("referral_source", { length: 50 }),
28681
28792
  // How the user heard about tostudy (optional, for attribution)
28682
28793
  expertise: varchar("expertise", { length: 200 }),
@@ -28791,7 +28902,12 @@ var init_users = __esm({
28791
28902
  externalIdIdx: index("idx_users_external_id").on(
28792
28903
  table.provisioningSource,
28793
28904
  table.externalUserId
28794
- )
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`)
28795
28911
  })
28796
28912
  );
28797
28913
  }
@@ -28932,6 +29048,14 @@ var init_user_preferences = __esm({
28932
29048
  emailNewReplies: boolean("email_new_replies").default(true).notNull(),
28933
29049
  /** Whether user wants push notifications for mentorship (via MCP) */
28934
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(),
28935
29059
  // i18n nickname pilot column (dogfooding)
28936
29060
  nicknameI18n: jsonb("nickname_i18n").$type(),
28937
29061
  // Timestamps
@@ -29194,7 +29318,6 @@ var init_courses3 = __esm({
29194
29318
  tags: jsonb("tags").$type().default([]).notNull(),
29195
29319
  // Search tags: ['agent', 'mcp', 'automation', 'claude-code']
29196
29320
  categoryId: uuid("category_id").references(() => courseCategories.id, { onDelete: "set null" }),
29197
- enrollmentCount: integer("enrollment_count").default(0).notNull(),
29198
29321
  thumbnailUrl: varchar("thumbnail_url", { length: 500 }),
29199
29322
  videoIntroUrl: varchar("video_intro_url", { length: 500 }),
29200
29323
  // Story 10.8: Video upload integration
@@ -29299,8 +29422,6 @@ var init_courses3 = __esm({
29299
29422
  // B-tree index for essential course filter
29300
29423
  createdAtIdx: index("courses_created_at_idx").on(table.createdAt),
29301
29424
  // B-tree index for newest sort
29302
- enrollmentCountIdx: index("courses_enrollment_count_idx").on(table.enrollmentCount),
29303
- // B-tree for best-sellers sort
29304
29425
  // Story 11.1: Curadoria queue sorting and filtering indexes
29305
29426
  statusIdx: index("courses_status_idx").on(table.status),
29306
29427
  // B-tree for status filtering
@@ -29644,6 +29765,18 @@ var init_credit_wallets = __esm({
29644
29765
  walletCreatedAtIdx: index("cwt_wallet_created_at_idx").on(table.walletId, table.createdAt),
29645
29766
  walletTypeIdx: index("cwt_wallet_type_idx").on(table.walletId, table.type),
29646
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'`),
29647
29780
  referenceTypeCheck: check(
29648
29781
  "cwt_reference_type_check",
29649
29782
  sql`${table.referenceType} IS NULL OR ${table.referenceType} IN (
@@ -30187,7 +30320,7 @@ var init_errors6 = __esm({
30187
30320
  });
30188
30321
 
30189
30322
  // ../../packages/types/src/index.ts
30190
- var init_src3 = __esm({
30323
+ var init_src2 = __esm({
30191
30324
  "../../packages/types/src/index.ts"() {
30192
30325
  "use strict";
30193
30326
  init_user();
@@ -30209,7 +30342,7 @@ var init_course_reviews = __esm({
30209
30342
  init_pg_core();
30210
30343
  init_users();
30211
30344
  init_courses3();
30212
- init_src3();
30345
+ init_src2();
30213
30346
  courseReviewStatusEnum = pgEnum("course_review_status", [
30214
30347
  "pending",
30215
30348
  "approved",
@@ -30317,6 +30450,7 @@ var init_enrollments = __esm({
30317
30450
  "../../packages/database/src/schema/enrollments.ts"() {
30318
30451
  "use strict";
30319
30452
  init_pg_core();
30453
+ init_drizzle_orm();
30320
30454
  init_users();
30321
30455
  init_courses3();
30322
30456
  init_cohorts();
@@ -30397,7 +30531,14 @@ var init_enrollments = __esm({
30397
30531
  table.currentLevel
30398
30532
  ),
30399
30533
  // Paris Group integration: partial index for cohort lookups.
30400
- 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'`)
30401
30542
  })
30402
30543
  );
30403
30544
  }
@@ -33900,6 +34041,34 @@ var init_mentor_availability = __esm({
33900
34041
  }
33901
34042
  });
33902
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
+
33903
34072
  // ../../packages/database/src/schema/mentorship-sessions.ts
33904
34073
  var mentorshipSessions2, sessionParticipants2, sessionWaitlist2;
33905
34074
  var init_mentorship_sessions = __esm({
@@ -34163,14 +34332,16 @@ var init_mentorship_packages = __esm({
34163
34332
  function isCreditUsable(credit) {
34164
34333
  return credit.status === "active" && credit.creditsRemaining > 0 && new Date(credit.expiresAt) > /* @__PURE__ */ new Date();
34165
34334
  }
34166
- var creditStatusEnum, mentorshipCredits2, insertMentorshipCreditSchema;
34335
+ var creditStatusEnum, creditOriginEnum, mentorshipCredits2, insertMentorshipCreditSchema;
34167
34336
  var init_mentorship_credits = __esm({
34168
34337
  "../../packages/database/src/schema/mentorship-credits.ts"() {
34169
34338
  "use strict";
34170
34339
  init_pg_core();
34340
+ init_drizzle_orm();
34171
34341
  init_users();
34172
34342
  init_mentorship_packages();
34173
34343
  init_mentorship_services();
34344
+ init_enrollments();
34174
34345
  init_zod();
34175
34346
  creditStatusEnum = pgEnum("mentorship_credit_status", [
34176
34347
  "active",
@@ -34180,12 +34351,17 @@ var init_mentorship_credits = __esm({
34180
34351
  "expired"
34181
34352
  // Credits expired (not used before expiration date)
34182
34353
  ]);
34354
+ creditOriginEnum = pgEnum("mentorship_credit_origin", ["package", "course_purchase"]);
34183
34355
  mentorshipCredits2 = pgTable(
34184
34356
  "mentorship_credits",
34185
34357
  {
34186
34358
  id: uuid("id").defaultRandom().primaryKey(),
34187
34359
  userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
34188
- 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
+ }),
34189
34365
  mentorId: uuid("mentor_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
34190
34366
  serviceId: uuid("service_id").references(() => mentorshipServices2.id, { onDelete: "cascade" }).notNull(),
34191
34367
  // Credit balance
@@ -34195,6 +34371,12 @@ var init_mentorship_credits = __esm({
34195
34371
  // Current available credits
34196
34372
  // Status tracking
34197
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
+ }),
34198
34380
  // Payment reference
34199
34381
  stripePaymentIntentId: varchar("stripe_payment_intent_id", { length: 100 }),
34200
34382
  // Dates
@@ -34240,21 +34422,47 @@ var init_mentorship_credits = __esm({
34240
34422
  table.stripePaymentIntentId
34241
34423
  ),
34242
34424
  // ORDER BY purchasedAt DESC (credit transaction history)
34243
- 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
+ )
34244
34434
  })
34245
34435
  );
34246
34436
  insertMentorshipCreditSchema = external_exports.object({
34247
34437
  userId: external_exports.string().uuid(),
34248
- packageId: external_exports.string().uuid(),
34438
+ // FEAT-1288: optional — required iff origin === "package" (refinement below)
34439
+ packageId: external_exports.string().uuid().optional().nullable(),
34249
34440
  mentorId: external_exports.string().uuid(),
34250
34441
  serviceId: external_exports.string().uuid(),
34251
34442
  totalCredits: external_exports.number().int().positive(),
34252
34443
  creditsRemaining: external_exports.number().int().min(0),
34253
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(),
34254
34447
  stripePaymentIntentId: external_exports.string().max(100).optional().nullable(),
34255
34448
  expiresAt: external_exports.date(),
34256
34449
  reminderSent30d: external_exports.boolean().optional().default(false),
34257
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
+ }
34258
34466
  });
34259
34467
  }
34260
34468
  });
@@ -34453,6 +34661,55 @@ var init_mentorship_transactions = __esm({
34453
34661
  }
34454
34662
  });
34455
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
+
34456
34713
  // ../../packages/database/src/schema/session-reviews.ts
34457
34714
  var REVIEW_TAGS, sessionReviews2, sessionReviewVotes2;
34458
34715
  var init_session_reviews = __esm({
@@ -34537,11 +34794,13 @@ var init_mentorship_index = __esm({
34537
34794
  init_mentorship_templates();
34538
34795
  init_mentorship_services();
34539
34796
  init_mentor_availability();
34797
+ init_mentor_specializations();
34540
34798
  init_mentorship_sessions();
34541
34799
  init_session_resources();
34542
34800
  init_mentorship_transactions();
34543
34801
  init_mentorship_packages();
34544
34802
  init_mentorship_credits();
34803
+ init_course_mentoria_offers();
34545
34804
  init_session_reviews();
34546
34805
  }
34547
34806
  });
@@ -34617,6 +34876,7 @@ var init_transactions = __esm({
34617
34876
  init_pg_core();
34618
34877
  init_courses3();
34619
34878
  init_users();
34879
+ init_enrollments();
34620
34880
  transactions2 = pgTable(
34621
34881
  "transactions",
34622
34882
  {
@@ -34629,6 +34889,15 @@ var init_transactions = __esm({
34629
34889
  courseId: uuid("course_id").notNull().references(() => courses.id, { onDelete: "restrict" }),
34630
34890
  creatorId: uuid("creator_id").notNull().references(() => users.id, { onDelete: "restrict" }),
34631
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
+ }),
34632
34901
  // Financial details (all amounts in cents)
34633
34902
  amount: integer("amount").notNull(),
34634
34903
  // Total amount charged
@@ -34648,11 +34917,24 @@ var init_transactions = __esm({
34648
34917
  paymentMethod: varchar("payment_method", { length: 50 }),
34649
34918
  // 'card' or 'pix'
34650
34919
  status: varchar("status", { length: 20 }).notNull().default("pending"),
34651
- // 'pending', 'succeeded', 'failed', 'refunded'
34920
+ // 'pending', 'succeeded', 'failed', 'refunded', 'disputed' (GH #718 chargeback)
34652
34921
  // Timestamps
34653
34922
  createdAt: timestamp("created_at").defaultNow().notNull(),
34654
34923
  processedAt: timestamp("processed_at"),
34655
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)
34656
34938
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
34657
34939
  },
34658
34940
  (table) => ({
@@ -34739,6 +35021,56 @@ var init_payouts = __esm({
34739
35021
  }
34740
35022
  });
34741
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
+
34742
35074
  // ../../packages/database/src/schema/creator-applications.ts
34743
35075
  var creatorApplications2;
34744
35076
  var init_creator_applications = __esm({
@@ -35001,7 +35333,10 @@ var init_course_project_enums = __esm({
35001
35333
  });
35002
35334
 
35003
35335
  // ../../packages/database/src/schema/course-project-types.ts
35004
- var CANCEL_REASONS;
35336
+ function v3CancelReasonRedisKey(courseId) {
35337
+ return `v3:cancel-reason:${courseId}`;
35338
+ }
35339
+ var CANCEL_REASONS, V3_CANCEL_REASON_TTL_SECONDS;
35005
35340
  var init_course_project_types = __esm({
35006
35341
  "../../packages/database/src/schema/course-project-types.ts"() {
35007
35342
  "use strict";
@@ -35012,6 +35347,7 @@ var init_course_project_types = __esm({
35012
35347
  "incomplete_stream",
35013
35348
  "unknown"
35014
35349
  ];
35350
+ V3_CANCEL_REASON_TTL_SECONDS = 86400;
35015
35351
  }
35016
35352
  });
35017
35353
 
@@ -35051,6 +35387,7 @@ var init_course_projects = __esm({
35051
35387
  "../../packages/database/src/schema/course-projects.ts"() {
35052
35388
  "use strict";
35053
35389
  init_pg_core();
35390
+ init_drizzle_orm();
35054
35391
  init_users();
35055
35392
  init_brainstormings();
35056
35393
  init_courses3();
@@ -35169,7 +35506,9 @@ var init_course_projects = __esm({
35169
35506
  statusIdx: index("course_projects_status_idx").on(table.status),
35170
35507
  currentPhaseIdx: index("course_projects_current_phase_idx").on(table.currentPhase),
35171
35508
  sourceTypeIdx: index("course_projects_source_type_idx").on(table.sourceType),
35172
- 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`),
35173
35512
  categoryIdIdx: index("course_projects_category_id_idx").on(table.categoryId),
35174
35513
  // ORDER BY createdAt/updatedAt DESC (project listing + creator initial state)
35175
35514
  createdAtIdx: index("course_projects_created_at_idx").on(table.createdAt),
@@ -36988,6 +37327,12 @@ var init_security_audit_log = __esm({
36988
37327
  "account.role_change",
36989
37328
  "account.profile_update",
36990
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",
36991
37336
  // Admin events
36992
37337
  "admin.user_suspend",
36993
37338
  "admin.user_unsuspend",
@@ -37113,7 +37458,7 @@ var init_credit_movement_audit_log = __esm({
37113
37458
  });
37114
37459
 
37115
37460
  // ../../packages/database/src/schema/marketing.ts
37116
- var testimonialStatusEnum, marketingContentTypeEnum, creatorTestimonials2, marketingContent2, newsletterSubscribers, contactSubmissions;
37461
+ var testimonialStatusEnum, testimonialPlacementEnum, marketingContentTypeEnum, creatorTestimonials2, marketingContent2, newsletterSubscribers, contactSubmissions;
37117
37462
  var init_marketing = __esm({
37118
37463
  "../../packages/database/src/schema/marketing.ts"() {
37119
37464
  "use strict";
@@ -37124,6 +37469,11 @@ var init_marketing = __esm({
37124
37469
  "published",
37125
37470
  "archived"
37126
37471
  ]);
37472
+ testimonialPlacementEnum = pgEnum("testimonial_placement", [
37473
+ "homepage",
37474
+ "creators",
37475
+ "both"
37476
+ ]);
37127
37477
  marketingContentTypeEnum = pgEnum("marketing_content_type", [
37128
37478
  "text",
37129
37479
  // Simple text content
@@ -37158,6 +37508,7 @@ var init_marketing = __esm({
37158
37508
  featured: boolean("featured").default(false).notNull(),
37159
37509
  // Show in carousel
37160
37510
  displayOrder: integer("display_order").default(0).notNull(),
37511
+ placement: testimonialPlacementEnum("placement").default("creators").notNull(),
37161
37512
  // Localization support (null = applies to all locales)
37162
37513
  locale: varchar("locale", { length: 10 }),
37163
37514
  // Metadata
@@ -37170,7 +37521,8 @@ var init_marketing = __esm({
37170
37521
  statusIdx: index("creator_testimonials_status_idx").on(table.status),
37171
37522
  featuredIdx: index("creator_testimonials_featured_idx").on(table.featured),
37172
37523
  displayOrderIdx: index("creator_testimonials_display_order_idx").on(table.displayOrder),
37173
- localeIdx: index("creator_testimonials_locale_idx").on(table.locale)
37524
+ localeIdx: index("creator_testimonials_locale_idx").on(table.locale),
37525
+ placementIdx: index("creator_testimonials_placement_idx").on(table.placement)
37174
37526
  })
37175
37527
  );
37176
37528
  marketingContent2 = pgTable(
@@ -38023,12 +38375,14 @@ var init_system_config = __esm({
38023
38375
  "ai.context.compression_threshold": "ai.context.compression_threshold",
38024
38376
  // Business - Pricing
38025
38377
  "business.pricing.min_course_price_brl": "business.pricing.min_course_price_brl",
38026
- "business.pricing.mentorship_platform_fee_percent": "business.pricing.mentorship_platform_fee_percent",
38027
38378
  "business.pricing.course_mentoria_creator_share": "business.pricing.course_mentoria_creator_share",
38028
38379
  "business.pricing.course_mentoria_platform_share": "business.pricing.course_mentoria_platform_share",
38029
- "business.pricing.course_selfpaced_creator_share": "business.pricing.course_selfpaced_creator_share",
38030
- "business.pricing.course_selfpaced_platform_share": "business.pricing.course_selfpaced_platform_share",
38031
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",
38032
38386
  // Business - Monitoring Thresholds
38033
38387
  "business.monitoring.cache_success_threshold": "business.monitoring.cache_success_threshold",
38034
38388
  "business.monitoring.cache_warning_threshold": "business.monitoring.cache_warning_threshold",
@@ -38617,7 +38971,7 @@ var init_course_transfer_logs = __esm({
38617
38971
  "use strict";
38618
38972
  init_pg_core();
38619
38973
  init_users();
38620
- transferTypeEnum = pgEnum("transfer_type", ["export", "import"]);
38974
+ transferTypeEnum = pgEnum("transfer_type", ["export", "import", "reassign"]);
38621
38975
  transferStatusEnum = pgEnum("transfer_status", [
38622
38976
  "pending",
38623
38977
  "processing",
@@ -38632,6 +38986,8 @@ var init_course_transfer_logs = __esm({
38632
38986
  adminId: uuid("admin_id").references(() => users.id, { onDelete: "restrict" }).notNull(),
38633
38987
  type: transferTypeEnum("type").notNull(),
38634
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" }),
38635
38991
  courseName: text("course_name").notNull(),
38636
38992
  sourceEnvironment: text("source_environment"),
38637
38993
  status: transferStatusEnum("status").default("pending").notNull(),
@@ -38646,9 +39002,7 @@ var init_course_transfer_logs = __esm({
38646
39002
  adminIdIdx: index("course_transfer_logs_admin_id_idx").on(table.adminId),
38647
39003
  typeIdx: index("course_transfer_logs_type_idx").on(table.type),
38648
39004
  statusIdx: index("course_transfer_logs_status_idx").on(table.status),
38649
- createdAtIdx: index("course_transfer_logs_created_at_idx").on(
38650
- table.createdAt
38651
- )
39005
+ createdAtIdx: index("course_transfer_logs_created_at_idx").on(table.createdAt)
38652
39006
  })
38653
39007
  );
38654
39008
  }
@@ -40687,34 +41041,6 @@ var init_magic_link_tokens = __esm({
40687
41041
  }
40688
41042
  });
40689
41043
 
40690
- // ../../packages/database/src/schema/platform-fees.ts
40691
- var platformFeeTypeEnum, platformFees;
40692
- var init_platform_fees = __esm({
40693
- "../../packages/database/src/schema/platform-fees.ts"() {
40694
- "use strict";
40695
- init_pg_core();
40696
- platformFeeTypeEnum = pgEnum("platform_fee_type", ["course", "mentorship"]);
40697
- platformFees = pgTable(
40698
- "platform_fees",
40699
- {
40700
- id: uuid("id").defaultRandom().primaryKey(),
40701
- countryCode: varchar("country_code", { length: 2 }).notNull(),
40702
- feeType: platformFeeTypeEnum("fee_type").notNull(),
40703
- feePercent: numeric("fee_percent", { precision: 5, scale: 2 }).notNull(),
40704
- isActive: boolean("is_active").default(true),
40705
- createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
40706
- updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
40707
- },
40708
- (table) => ({
40709
- countryFeeTypeUniq: unique("platform_fees_country_fee_type_uniq").on(
40710
- table.countryCode,
40711
- table.feeType
40712
- )
40713
- })
40714
- );
40715
- }
40716
- });
40717
-
40718
41044
  // ../../packages/database/src/schema/payout-methods.ts
40719
41045
  var payoutMethods, payoutAccounts;
40720
41046
  var init_payout_methods = __esm({
@@ -40754,6 +41080,34 @@ var init_payout_methods = __esm({
40754
41080
  }
40755
41081
  });
40756
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
+
40757
41111
  // ../../packages/database/src/schema/media-assets.ts
40758
41112
  var mediaAssetStatusEnum, mediaAssetTypeEnum, mediaAssetDomainEnum, mediaAssets;
40759
41113
  var init_media_assets = __esm({
@@ -40835,7 +41189,7 @@ var init_push_tokens = __esm({
40835
41189
  });
40836
41190
 
40837
41191
  // ../../packages/database/src/schema/payout.ts
40838
- var payoutFeeConfig, payoutWithdrawals, payoutBankAccounts, payoutWithdrawalTransitions;
41192
+ var payoutWithdrawals, payoutBankAccounts, payoutWithdrawalTransitions;
40839
41193
  var init_payout = __esm({
40840
41194
  "../../packages/database/src/schema/payout.ts"() {
40841
41195
  "use strict";
@@ -40845,22 +41199,6 @@ var init_payout = __esm({
40845
41199
  init_credit_wallets();
40846
41200
  init_credit_wallets();
40847
41201
  init_payout_methods();
40848
- payoutFeeConfig = pgTable(
40849
- "payout_fee_config",
40850
- {
40851
- id: uuid("id").defaultRandom().primaryKey(),
40852
- mentorId: uuid("mentor_id").references(() => users.id, {
40853
- onDelete: "cascade"
40854
- }).unique(),
40855
- // null = global default
40856
- feePercent: decimal("fee_percent", { precision: 5, scale: 2 }).notNull(),
40857
- createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
40858
- updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
40859
- },
40860
- (table) => ({
40861
- feeRange: check("pfc_fee_range", sql`${table.feePercent} >= 0 AND ${table.feePercent} <= 100`)
40862
- })
40863
- );
40864
41202
  payoutWithdrawals = pgTable(
40865
41203
  "payout_withdrawals",
40866
41204
  {
@@ -40877,10 +41215,17 @@ var init_payout = __esm({
40877
41215
  precision: 10,
40878
41216
  scale: 4
40879
41217
  }).notNull(),
40880
- 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(),
40881
41222
  grossUsd: decimal("gross_usd", { precision: 10, scale: 4 }).notNull(),
40882
- feeUsd: decimal("fee_usd", { precision: 10, scale: 4 }).notNull(),
41223
+ taxUsd: decimal("tax_usd", { precision: 10, scale: 4 }).notNull(),
40883
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 }),
40884
41229
  exchangeRateBrl: decimal("exchange_rate_brl", {
40885
41230
  precision: 10,
40886
41231
  scale: 4
@@ -41079,6 +41424,7 @@ var init_course_allowlist = __esm({
41079
41424
  "../../packages/database/src/schema/course-allowlist.ts"() {
41080
41425
  "use strict";
41081
41426
  init_pg_core();
41427
+ init_drizzle_orm();
41082
41428
  init_courses3();
41083
41429
  init_users();
41084
41430
  courseAllowlist = pgTable(
@@ -41092,58 +41438,172 @@ var init_course_allowlist = __esm({
41092
41438
  onDelete: "set null"
41093
41439
  }),
41094
41440
  invitedAt: timestamp("invited_at", { withTimezone: true }).defaultNow().notNull(),
41095
- 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 })
41096
41446
  },
41097
41447
  (table) => ({
41098
41448
  courseEmailUnique: uniqueIndex("course_allowlist_course_email_unique").on(
41099
41449
  table.courseId,
41100
41450
  table.email
41101
41451
  ),
41102
- 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`)
41103
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
41104
41456
  })
41105
41457
  );
41106
41458
  }
41107
41459
  });
41108
41460
 
41109
- // ../../node_modules/@parisgroup-ai/pg-backend/dist/idempotency-schema.mjs
41461
+ // ../../packages/database/src/schema/idempotency.ts
41110
41462
  var pgBackendIdempotencyKeys;
41111
- var init_idempotency_schema = __esm({
41112
- "../../node_modules/@parisgroup-ai/pg-backend/dist/idempotency-schema.mjs"() {
41463
+ var init_idempotency = __esm({
41464
+ "../../packages/database/src/schema/idempotency.ts"() {
41465
+ "use strict";
41113
41466
  init_drizzle_orm();
41114
41467
  init_pg_core();
41115
- pgBackendIdempotencyKeys = pgTable("pg_backend_idempotency_keys", {
41116
- reservationId: uuid("reservation_id").primaryKey().defaultRandom(),
41117
- idempotencyKey: text("idempotency_key").notNull(),
41118
- resource: text("resource").notNull(),
41119
- operation: text("operation").notNull(),
41120
- fingerprint: text("fingerprint").notNull(),
41121
- scopeKey: text("scope_key"),
41122
- status: text("status").notNull(),
41123
- response: jsonb("response"),
41124
- reservedAt: timestamp("reserved_at", { withTimezone: true }).notNull().defaultNow(),
41125
- committedAt: timestamp("committed_at", { withTimezone: true }),
41126
- expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
41127
- }, (t) => ({
41128
- uniqueKey: uniqueIndex("pg_backend_idempotency_unique").on(t.idempotencyKey, t.resource, t.operation, sql`COALESCE(${t.scopeKey}, '')`),
41129
- expiresIdx: index("pg_backend_idempotency_expires").on(t.expiresAt),
41130
- pendingIdx: index("pg_backend_idempotency_pending").on(t.status, t.reservedAt)
41131
- }));
41468
+ pgBackendIdempotencyKeys = pgTable(
41469
+ "pg_backend_idempotency_keys",
41470
+ {
41471
+ reservationId: uuid("reservation_id").primaryKey().defaultRandom(),
41472
+ idempotencyKey: text("idempotency_key").notNull(),
41473
+ resource: text("resource").notNull(),
41474
+ operation: text("operation").notNull(),
41475
+ fingerprint: text("fingerprint").notNull(),
41476
+ scopeKey: text("scope_key"),
41477
+ status: text("status").notNull(),
41478
+ response: jsonb("response"),
41479
+ reservedAt: timestamp("reserved_at", { withTimezone: true }).notNull().defaultNow(),
41480
+ committedAt: timestamp("committed_at", { withTimezone: true }),
41481
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
41482
+ },
41483
+ (t) => [
41484
+ uniqueIndex("pg_backend_idempotency_unique").on(
41485
+ t.idempotencyKey,
41486
+ t.resource,
41487
+ t.operation,
41488
+ sql`COALESCE(${t.scopeKey}, '')`
41489
+ ),
41490
+ index("pg_backend_idempotency_expires").on(t.expiresAt),
41491
+ index("pg_backend_idempotency_pending").on(t.status, t.reservedAt)
41492
+ ]
41493
+ );
41132
41494
  }
41133
41495
  });
41134
41496
 
41135
- // ../../node_modules/@parisgroup-ai/pg-backend/dist/index.mjs
41136
- var init_dist2 = __esm({
41137
- "../../node_modules/@parisgroup-ai/pg-backend/dist/index.mjs"() {
41138
- init_idempotency_schema();
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
+ );
41139
41535
  }
41140
41536
  });
41141
41537
 
41142
- // ../../packages/database/src/schema/idempotency.ts
41143
- var init_idempotency = __esm({
41144
- "../../packages/database/src/schema/idempotency.ts"() {
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"() {
41145
41542
  "use strict";
41146
- init_dist2();
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
+ );
41147
41607
  }
41148
41608
  });
41149
41609
 
@@ -41161,6 +41621,7 @@ __export(schema_exports, {
41161
41621
  PACKAGE_VALIDITY_OPTIONS: () => PACKAGE_VALIDITY_OPTIONS,
41162
41622
  REVIEW_TAGS: () => REVIEW_TAGS,
41163
41623
  SUGGESTED_DISCOUNTS: () => SUGGESTED_DISCOUNTS,
41624
+ V3_CANCEL_REASON_TTL_SECONDS: () => V3_CANCEL_REASON_TTL_SECONDS,
41164
41625
  accounts: () => accounts,
41165
41626
  accountsRelations: () => accountsRelations2,
41166
41627
  actionTypeEnum: () => actionTypeEnum,
@@ -41189,6 +41650,8 @@ __export(schema_exports, {
41189
41650
  browserSessions: () => browserSessions,
41190
41651
  browserSessionsRelations: () => browserSessionsRelations,
41191
41652
  cancellationReasonEnum: () => cancellationReasonEnum,
41653
+ catalogSubscriptionCreatorSplits: () => catalogSubscriptionCreatorSplits,
41654
+ catalogSubscriptionRevenuePools: () => catalogSubscriptionRevenuePools,
41192
41655
  certificates: () => certificates,
41193
41656
  certificatesRelations: () => certificatesRelations2,
41194
41657
  challengeType: () => challengeType,
@@ -41234,6 +41697,7 @@ __export(schema_exports, {
41234
41697
  courseKnowledgeBases: () => courseKnowledgeBases,
41235
41698
  courseKnowledgeBasesRelations: () => courseKnowledgeBasesRelations,
41236
41699
  courseLevelEnum: () => courseLevelEnum,
41700
+ courseMentoriaOffers: () => courseMentoriaOffers,
41237
41701
  courseProjects: () => courseProjects,
41238
41702
  courseProjectsRelations: () => courseProjectsRelations,
41239
41703
  courseProposals: () => courseProposals2,
@@ -41274,6 +41738,7 @@ __export(schema_exports, {
41274
41738
  creatorVariationAxesRelations: () => creatorVariationAxesRelations,
41275
41739
  creditMovementAuditLog: () => creditMovementAuditLog,
41276
41740
  creditMovementAuditLogRelations: () => creditMovementAuditLogRelations,
41741
+ creditOriginEnum: () => creditOriginEnum,
41277
41742
  creditPackages: () => creditPackages,
41278
41743
  creditStatusEnum: () => creditStatusEnum,
41279
41744
  creditSubscriptionPlans: () => creditSubscriptionPlans,
@@ -41323,6 +41788,7 @@ __export(schema_exports, {
41323
41788
  helpArticleRatings: () => helpArticleRatings,
41324
41789
  ideas: () => ideas,
41325
41790
  ideasRelations: () => ideasRelations,
41791
+ insertCourseMentoriaOfferSchema: () => insertCourseMentoriaOfferSchema,
41326
41792
  insertMentorshipCreditSchema: () => insertMentorshipCreditSchema,
41327
41793
  insertMentorshipPackageSchema: () => insertMentorshipPackageSchema,
41328
41794
  insertPlatformSettingSchema: () => insertPlatformSettingSchema,
@@ -41372,6 +41838,7 @@ __export(schema_exports, {
41372
41838
  mentorAvailabilityExceptions: () => mentorAvailabilityExceptions2,
41373
41839
  mentorAvailabilityExceptionsRelations: () => mentorAvailabilityExceptionsRelations2,
41374
41840
  mentorAvailabilityRelations: () => mentorAvailabilityRelations2,
41841
+ mentorSpecializations: () => mentorSpecializations,
41375
41842
  mentorshipCredits: () => mentorshipCredits2,
41376
41843
  mentorshipCreditsRelations: () => mentorshipCreditsRelations2,
41377
41844
  mentorshipMessages: () => mentorshipMessages2,
@@ -41422,7 +41889,6 @@ __export(schema_exports, {
41422
41889
  pathStatusEnum: () => pathStatusEnum,
41423
41890
  payoutAccounts: () => payoutAccounts,
41424
41891
  payoutBankAccounts: () => payoutBankAccounts,
41425
- payoutFeeConfig: () => payoutFeeConfig,
41426
41892
  payoutMethods: () => payoutMethods,
41427
41893
  payoutStatusEnum: () => payoutStatusEnum,
41428
41894
  payoutTransactions: () => payoutTransactions2,
@@ -41435,8 +41901,6 @@ __export(schema_exports, {
41435
41901
  pgBackendIdempotencyKeys: () => pgBackendIdempotencyKeys,
41436
41902
  pixKeyTypeEnum: () => pixKeyTypeEnum,
41437
41903
  platformConfig: () => platformConfig,
41438
- platformFeeTypeEnum: () => platformFeeTypeEnum,
41439
- platformFees: () => platformFees,
41440
41904
  platformSettings: () => platformSettings2,
41441
41905
  playgroundEvalScores: () => playgroundEvalScores,
41442
41906
  playgroundEvalScoresRelations: () => playgroundEvalScoresRelations,
@@ -41454,6 +41918,7 @@ __export(schema_exports, {
41454
41918
  portfolioViews: () => portfolioViews,
41455
41919
  portfolioViewsRelations: () => portfolioViewsRelations,
41456
41920
  pricingTiers: () => pricingTiers,
41921
+ profileVisibilityEnum: () => profileVisibilityEnum,
41457
41922
  progress: () => progress,
41458
41923
  progressRelations: () => progressRelations2,
41459
41924
  progressStatusEnum: () => progressStatusEnum,
@@ -41559,11 +42024,14 @@ __export(schema_exports, {
41559
42024
  studyModuleSessionsRelations: () => studyModuleSessionsRelations,
41560
42025
  studyPhaseV2Values: () => studyPhaseV2Values,
41561
42026
  studyPhaseValues: () => studyPhaseValues,
42027
+ subscriptionPoolStatusEnum: () => subscriptionPoolStatusEnum,
42028
+ subscriptionSplitStatusEnum: () => subscriptionSplitStatusEnum,
41562
42029
  supportedCurrencies: () => supportedCurrencies,
41563
42030
  syncLogs: () => syncLogs,
41564
42031
  syncLogsRelations: () => syncLogsRelations2,
41565
42032
  systemConfigs: () => systemConfigs,
41566
42033
  systemConfigsRelations: () => systemConfigsRelations,
42034
+ testimonialPlacementEnum: () => testimonialPlacementEnum,
41567
42035
  testimonialStatusEnum: () => testimonialStatusEnum,
41568
42036
  threadStatusEnum: () => threadStatusEnum,
41569
42037
  toneEnum: () => toneEnum,
@@ -41583,12 +42051,17 @@ __export(schema_exports, {
41583
42051
  userBadgesRelations: () => userBadgesRelations2,
41584
42052
  userChallengeScores: () => userChallengeScores,
41585
42053
  userChallengeScoresRelations: () => userChallengeScoresRelations,
42054
+ userDeletionStatusEnum: () => userDeletionStatusEnum,
42055
+ userDeletions: () => userDeletions,
41586
42056
  userFollows: () => userFollows2,
41587
42057
  userFollowsRelations: () => userFollowsRelations2,
41588
42058
  userPathEnrollments: () => userPathEnrollments,
41589
42059
  userPathEnrollmentsRelations: () => userPathEnrollmentsRelations,
41590
42060
  userPreferences: () => userPreferences,
42061
+ userPreferencesAudits: () => userPreferencesAudits,
41591
42062
  userPreferencesRelations: () => userPreferencesRelations2,
42063
+ userProfileAudits: () => userProfileAudits,
42064
+ userProfiles: () => userProfiles,
41592
42065
  userRegionalSettings: () => userRegionalSettings,
41593
42066
  userRegionalSettingsRelations: () => userRegionalSettingsRelations,
41594
42067
  userRoleEnum: () => userRoleEnum,
@@ -41596,6 +42069,7 @@ __export(schema_exports, {
41596
42069
  userUsageQuotas: () => userUsageQuotas2,
41597
42070
  users: () => users,
41598
42071
  usersRelations: () => usersRelations2,
42072
+ v3CancelReasonRedisKey: () => v3CancelReasonRedisKey,
41599
42073
  validationAttempts: () => validationAttempts2,
41600
42074
  validationAttemptsRelations: () => validationAttemptsRelations2,
41601
42075
  validationLessonProgress: () => validationLessonProgress,
@@ -41616,7 +42090,8 @@ __export(schema_exports, {
41616
42090
  videoTemplateNameEnum: () => videoTemplateNameEnum,
41617
42091
  videoTypeEnum: () => videoTypeEnum,
41618
42092
  webhookEvents: () => webhookEvents2,
41619
- wishlists: () => wishlists
42093
+ wishlists: () => wishlists,
42094
+ withdrawalTaxes: () => withdrawalTaxes
41620
42095
  });
41621
42096
  var init_schema3 = __esm({
41622
42097
  "../../packages/database/src/schema/index.ts"() {
@@ -41632,6 +42107,7 @@ var init_schema3 = __esm({
41632
42107
  init_mentorship_index();
41633
42108
  init_reviews();
41634
42109
  init_payouts();
42110
+ init_subscription_pools();
41635
42111
  init_creator_index();
41636
42112
  init_webhook_events();
41637
42113
  init_transactions();
@@ -41707,8 +42183,8 @@ var init_schema3 = __esm({
41707
42183
  init_tutor_content_issues();
41708
42184
  init_magic_link_tokens();
41709
42185
  init_mobile_refresh_tokens();
41710
- init_platform_fees();
41711
42186
  init_payout_methods();
42187
+ init_withdrawal_taxes();
41712
42188
  init_media_assets();
41713
42189
  init_push_tokens();
41714
42190
  init_payout();
@@ -41718,6 +42194,9 @@ var init_schema3 = __esm({
41718
42194
  init_course_visibility();
41719
42195
  init_course_allowlist();
41720
42196
  init_idempotency();
42197
+ init_user_deletions();
42198
+ init_user_profiles();
42199
+ init_user_resource_audits();
41721
42200
  }
41722
42201
  });
41723
42202
 
@@ -41725,7 +42204,7 @@ var init_schema3 = __esm({
41725
42204
  var init_check_module_unlocked = __esm({
41726
42205
  "../../packages/database/src/utils/check-module-unlocked.ts"() {
41727
42206
  "use strict";
41728
- init_src4();
42207
+ init_src3();
41729
42208
  init_schema3();
41730
42209
  }
41731
42210
  });
@@ -41754,11 +42233,11 @@ var init_ids = __esm({
41754
42233
 
41755
42234
  // ../../packages/database/src/index.ts
41756
42235
  var envSchema, _db, createDb, getDb, db;
41757
- var init_src4 = __esm({
42236
+ var init_src3 = __esm({
41758
42237
  "../../packages/database/src/index.ts"() {
41759
42238
  "use strict";
41760
42239
  init_postgres_js();
41761
- init_src2();
42240
+ init_src();
41762
42241
  init_zod();
41763
42242
  init_schema3();
41764
42243
  init_schema3();
@@ -41855,7 +42334,7 @@ var init_formats = __esm({
41855
42334
  var init_parser = __esm({
41856
42335
  "../../packages/tostudy-core/src/exercises/parser.ts"() {
41857
42336
  "use strict";
41858
- init_src3();
42337
+ init_src2();
41859
42338
  init_formats();
41860
42339
  init_formats();
41861
42340
  }
@@ -41879,8 +42358,8 @@ var init_lesson_exercise_data = __esm({
41879
42358
  var init_sync_enrollment_progress = __esm({
41880
42359
  "../../packages/tostudy-core/src/learning/sync-enrollment-progress.ts"() {
41881
42360
  "use strict";
41882
- init_src4();
41883
- init_src();
42361
+ init_src3();
42362
+ init_dist();
41884
42363
  }
41885
42364
  });
41886
42365
 
@@ -41889,21 +42368,38 @@ var DEFAULT_MAX_SESSION_TIME_SECONDS;
41889
42368
  var init_study_state_sync = __esm({
41890
42369
  "../../packages/tostudy-core/src/learning/study-state-sync.ts"() {
41891
42370
  "use strict";
41892
- init_src4();
42371
+ init_src3();
41893
42372
  DEFAULT_MAX_SESSION_TIME_SECONDS = 2 * 60 * 60;
41894
42373
  }
41895
42374
  });
41896
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
+
41897
42391
  // ../../packages/tostudy-core/src/learning/start-module-full.ts
41898
42392
  var init_start_module_full = __esm({
41899
42393
  "../../packages/tostudy-core/src/learning/start-module-full.ts"() {
41900
42394
  "use strict";
41901
- init_src4();
42395
+ init_src3();
41902
42396
  init_parser();
41903
42397
  init_exercise_types();
41904
42398
  init_lesson_exercise_data();
41905
42399
  init_sync_enrollment_progress();
41906
42400
  init_study_state_sync();
42401
+ init_entitlement();
42402
+ init_archived_access();
41907
42403
  }
41908
42404
  });
41909
42405
 
@@ -41911,10 +42407,13 @@ var init_start_module_full = __esm({
41911
42407
  var init_next_lesson_full = __esm({
41912
42408
  "../../packages/tostudy-core/src/learning/next-lesson-full.ts"() {
41913
42409
  "use strict";
41914
- init_src4();
42410
+ init_src3();
42411
+ init_dist();
41915
42412
  init_parser();
41916
42413
  init_lesson_exercise_data();
41917
42414
  init_sync_enrollment_progress();
42415
+ init_entitlement();
42416
+ init_archived_access();
41918
42417
  init_study_state_sync();
41919
42418
  }
41920
42419
  });
@@ -41923,8 +42422,8 @@ var init_next_lesson_full = __esm({
41923
42422
  var init_student_memory = __esm({
41924
42423
  "../../packages/tostudy-core/src/memory/student-memory.ts"() {
41925
42424
  "use strict";
41926
- init_src4();
41927
- init_src();
42425
+ init_src3();
42426
+ init_dist();
41928
42427
  }
41929
42428
  });
41930
42429
 
@@ -41932,7 +42431,7 @@ var init_student_memory = __esm({
41932
42431
  var init_summarize_lesson = __esm({
41933
42432
  "../../packages/tostudy-core/src/learning/summarize-lesson.ts"() {
41934
42433
  "use strict";
41935
- init_src();
42434
+ init_dist();
41936
42435
  }
41937
42436
  });
41938
42437
 
@@ -41940,14 +42439,16 @@ var init_summarize_lesson = __esm({
41940
42439
  var init_validate_solution_full = __esm({
41941
42440
  "../../packages/tostudy-core/src/learning/validate-solution-full.ts"() {
41942
42441
  "use strict";
41943
- init_src4();
41944
- init_src();
42442
+ init_src3();
42443
+ init_dist();
41945
42444
  init_parser();
41946
42445
  init_extractors();
41947
42446
  init_exercise_types();
41948
42447
  init_study_state_sync();
41949
42448
  init_sync_enrollment_progress();
41950
42449
  init_student_memory();
42450
+ init_entitlement();
42451
+ init_archived_access();
41951
42452
  init_summarize_lesson();
41952
42453
  }
41953
42454
  });
@@ -41956,8 +42457,10 @@ var init_validate_solution_full = __esm({
41956
42457
  var init_go_to_lesson = __esm({
41957
42458
  "../../packages/tostudy-core/src/learning/go-to-lesson.ts"() {
41958
42459
  "use strict";
41959
- init_src4();
42460
+ init_src3();
41960
42461
  init_parser();
42462
+ init_entitlement();
42463
+ init_archived_access();
41961
42464
  }
41962
42465
  });
41963
42466
 
@@ -41965,8 +42468,9 @@ var init_go_to_lesson = __esm({
41965
42468
  var init_get_lesson_content_full = __esm({
41966
42469
  "../../packages/tostudy-core/src/learning/get-lesson-content-full.ts"() {
41967
42470
  "use strict";
41968
- init_src4();
42471
+ init_src3();
41969
42472
  init_parser();
42473
+ init_entitlement();
41970
42474
  }
41971
42475
  });
41972
42476
 
@@ -41982,9 +42486,11 @@ var init_formatter2 = __esm({
41982
42486
  var init_get_hint_full = __esm({
41983
42487
  "../../packages/tostudy-core/src/learning/get-hint-full.ts"() {
41984
42488
  "use strict";
41985
- init_src4();
42489
+ init_src3();
41986
42490
  init_parser();
41987
42491
  init_formatter2();
42492
+ init_entitlement();
42493
+ init_archived_access();
41988
42494
  }
41989
42495
  });
41990
42496
 
@@ -41992,8 +42498,8 @@ var init_get_hint_full = __esm({
41992
42498
  var init_explain_concept = __esm({
41993
42499
  "../../packages/tostudy-core/src/learning/explain-concept.ts"() {
41994
42500
  "use strict";
41995
- init_src4();
41996
- init_src();
42501
+ init_src3();
42502
+ init_dist();
41997
42503
  }
41998
42504
  });
41999
42505
 
@@ -42033,10 +42539,6 @@ async function runStart(opts, deps = defaultDeps3) {
42033
42539
  const session = await deps.requireSession();
42034
42540
  const activeCourse = await deps.requireActiveCourse();
42035
42541
  const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
42036
- if (!onboarding.initReady) {
42037
- deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
42038
- `);
42039
- }
42040
42542
  const driftWarning = await deps.checkCourseDrift();
42041
42543
  if (driftWarning) deps.stderrWrite(driftWarning + "\n");
42042
42544
  const data = deps.createHttpProvider(session.apiUrl, session.token);
@@ -42055,15 +42557,22 @@ async function runStart(opts, deps = defaultDeps3) {
42055
42557
  } else {
42056
42558
  deps.output(deps.formatModuleStart(moduleData), { json: false });
42057
42559
  }
42560
+ if (!onboarding.initReady) {
42561
+ deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
42562
+ `);
42563
+ }
42058
42564
  } catch (err) {
42059
42565
  if (err instanceof CliApiError && err.status === 403 && err.message === "CHANNEL_MISMATCH") {
42060
42566
  process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
42061
42567
  process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
42062
42568
  process.exit(1);
42063
42569
  }
42064
- if (err instanceof CliApiError && err.status === 403 && err.message === "COURSE_ARCHIVED") {
42065
- process.stderr.write("Este curso foi arquivado e est\xE1 dispon\xEDvel somente para leitura.\n");
42066
- process.stderr.write("Use `tostudy lesson` para revisar o conte\xFAdo j\xE1 estudado.\n");
42570
+ if (isEnrollmentNotEntitledError(err)) {
42571
+ deps.stderrWrite(getErrors().enrollmentNotEntitled);
42572
+ process.exit(1);
42573
+ }
42574
+ if (isCourseArchivedError(err)) {
42575
+ deps.stderrWrite(getErrors().courseArchived);
42067
42576
  process.exit(1);
42068
42577
  }
42069
42578
  const msg = err instanceof Error ? err.message : String(err);
@@ -42074,7 +42583,7 @@ var logger9, defaultDeps3, startCommand;
42074
42583
  var init_start = __esm({
42075
42584
  "src/commands/start.ts"() {
42076
42585
  "use strict";
42077
- init_src();
42586
+ init_dist();
42078
42587
  init_lessons2();
42079
42588
  init_http();
42080
42589
  init_guards();
@@ -42082,6 +42591,7 @@ var init_start = __esm({
42082
42591
  init_workspace_state();
42083
42592
  init_status();
42084
42593
  init_formatter();
42594
+ init_errors();
42085
42595
  logger9 = createLogger("cli:start");
42086
42596
  defaultDeps3 = {
42087
42597
  requireSession,
@@ -42110,13 +42620,14 @@ var logger10, startNextCommand;
42110
42620
  var init_start_next = __esm({
42111
42621
  "src/commands/start-next.ts"() {
42112
42622
  "use strict";
42113
- init_src();
42623
+ init_dist();
42114
42624
  init_lessons2();
42115
42625
  init_http();
42116
42626
  init_guards();
42117
42627
  init_course_state();
42118
42628
  init_workspace_state();
42119
42629
  init_formatter();
42630
+ init_errors();
42120
42631
  logger10 = createLogger("cli:start-next");
42121
42632
  startNextCommand = new Command9("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
42122
42633
  try {
@@ -42144,9 +42655,12 @@ var init_start_next = __esm({
42144
42655
  process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
42145
42656
  process.exit(0);
42146
42657
  }
42147
- if (err instanceof CliApiError && err.status === 403 && err.message === "COURSE_ARCHIVED") {
42148
- process.stderr.write("Este curso foi arquivado e est\xE1 dispon\xEDvel somente para leitura.\n");
42149
- process.stderr.write("Use `tostudy lesson` para revisar o conte\xFAdo j\xE1 estudado.\n");
42658
+ if (isEnrollmentNotEntitledError(err)) {
42659
+ process.stderr.write(getErrors().enrollmentNotEntitled);
42660
+ process.exit(1);
42661
+ }
42662
+ if (isCourseArchivedError(err)) {
42663
+ process.stderr.write(getErrors().courseArchived);
42150
42664
  process.exit(1);
42151
42665
  }
42152
42666
  const msg = err instanceof Error ? err.message : String(err);
@@ -42163,13 +42677,14 @@ var logger11, nextCommand;
42163
42677
  var init_next = __esm({
42164
42678
  "src/commands/next.ts"() {
42165
42679
  "use strict";
42166
- init_src();
42680
+ init_dist();
42167
42681
  init_lessons2();
42168
42682
  init_http();
42169
42683
  init_guards();
42170
42684
  init_course_state();
42171
42685
  init_workspace_state();
42172
42686
  init_formatter();
42687
+ init_errors();
42173
42688
  logger11 = createLogger("cli:next");
42174
42689
  nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
42175
42690
  try {
@@ -42195,6 +42710,18 @@ var init_next = __esm({
42195
42710
  output(formatLesson(lessonData), { json: false });
42196
42711
  }
42197
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
+ }
42719
+ if (isCourseArchivedError(err)) {
42720
+ const friendly = getErrors().courseArchived;
42721
+ if (opts.json) jsonError("course_archived", { message: friendly });
42722
+ process.stderr.write(friendly);
42723
+ process.exit(1);
42724
+ }
42198
42725
  if (err instanceof CliApiError) {
42199
42726
  if (err.status === 403 && err.message === "CHANNEL_MISMATCH") {
42200
42727
  process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
@@ -42227,6 +42754,7 @@ var init_next = __esm({
42227
42754
 
42228
42755
  // src/commands/lesson.ts
42229
42756
  import { Command as Command11 } from "commander";
42757
+ import path13 from "node:path";
42230
42758
  function adjustTimeEstimate(type, baseMinutes) {
42231
42759
  if (type === "exercise") return Math.max(baseMinutes, 30);
42232
42760
  return baseMinutes;
@@ -42234,15 +42762,29 @@ function adjustTimeEstimate(type, baseMinutes) {
42234
42762
  function isCheckpoint(title) {
42235
42763
  return /^checkpoint/i.test(title.trim());
42236
42764
  }
42237
- 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) {
42238
42772
  const time4 = adjustTimeEstimate(data.type, data.estimatedTimeMinutes);
42239
- const lines = [
42240
- `\u2501\u2501\u2501 Li\xE7\xE3o: ${data.title} \u2501\u2501\u2501`,
42241
- "",
42242
- `Tipo: ${data.type} | Dura\xE7\xE3o estimada: ~${time4} min`,
42243
- "",
42244
- data.content
42245
- ];
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);
42246
42788
  if (data.acceptanceCriteria) {
42247
42789
  lines.push("", "Crit\xE9rios de aceita\xE7\xE3o:", data.acceptanceCriteria);
42248
42790
  }
@@ -42269,8 +42811,9 @@ var logger12, lessonCommand;
42269
42811
  var init_lesson = __esm({
42270
42812
  "src/commands/lesson.ts"() {
42271
42813
  "use strict";
42272
- init_src();
42814
+ init_dist();
42273
42815
  init_lessons2();
42816
+ init_courses();
42274
42817
  init_http();
42275
42818
  init_guards();
42276
42819
  init_course_state();
@@ -42278,7 +42821,10 @@ var init_lesson = __esm({
42278
42821
  init_resolve();
42279
42822
  init_errors();
42280
42823
  logger12 = createLogger("cli:lesson");
42281
- lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
42824
+ lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").option(
42825
+ "--read <lessonId>",
42826
+ "Read a specific lesson by id (works on archived / read-only courses)"
42827
+ ).action(async (opts) => {
42282
42828
  try {
42283
42829
  const session = await requireSession();
42284
42830
  const activeCourse = await requireActiveCourse();
@@ -42286,30 +42832,68 @@ var init_lesson = __esm({
42286
42832
  if (driftWarning) process.stderr.write(driftWarning + "\n");
42287
42833
  const data = createHttpProvider(session.apiUrl, session.token);
42288
42834
  const deps = { data, logger: logger12 };
42289
- const lessonId = activeCourse.currentLessonId;
42835
+ let lessonId = opts.read ?? activeCourse.currentLessonId;
42290
42836
  if (!lessonId) {
42291
- if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
42292
- error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
42837
+ try {
42838
+ const prog = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
42839
+ lessonId = prog.currentLesson?.id || void 0;
42840
+ if (prog.isArchived) {
42841
+ process.stderr.write("\u2139 Curso arquivado \u2014 modo leitura.\n");
42842
+ }
42843
+ } catch {
42844
+ }
42845
+ }
42846
+ if (!lessonId) {
42847
+ if (opts.json)
42848
+ jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o encontrada para leitura" });
42849
+ error("Nenhuma li\xE7\xE3o encontrada para leitura. Rode: tostudy start ou tostudy next");
42293
42850
  }
42294
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
+ }
42295
42873
  if (opts.json) {
42296
42874
  output(content, { json: true });
42297
42875
  } else {
42298
- 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
+ );
42299
42884
  }
42300
- if (content.type === "exercise") {
42301
- const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
42302
- const ws = await resolveEffectiveWorkspace(
42303
- activeCourse.courseTitle,
42304
- 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"
42305
42888
  );
42306
- if (!ws.found) {
42307
- process.stderr.write(
42308
- "\n\u{1F4A1} Dica: rode `tostudy select` desta pasta para us\xE1-la como workspace, ou `tostudy workspace setup` para criar em ~/study/.\n"
42309
- );
42310
- }
42311
42889
  }
42312
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
+ }
42313
42897
  if (isInsufficientCreditsError(err)) {
42314
42898
  const friendly = getErrors().insufficientCredits;
42315
42899
  if (opts.json) jsonError("insufficient_credits", { message: friendly });
@@ -42330,7 +42914,7 @@ var logger13, hintCommand;
42330
42914
  var init_hint = __esm({
42331
42915
  "src/commands/hint.ts"() {
42332
42916
  "use strict";
42333
- init_src();
42917
+ init_dist();
42334
42918
  init_lessons2();
42335
42919
  init_http();
42336
42920
  init_guards();
@@ -42356,6 +42940,18 @@ var init_hint = __esm({
42356
42940
  output(formatHint(hint), { json: false });
42357
42941
  }
42358
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
+ }
42949
+ if (isCourseArchivedError(err)) {
42950
+ const friendly = getErrors().courseArchived;
42951
+ if (opts.json) jsonError("course_archived", { message: friendly });
42952
+ else process.stderr.write(friendly);
42953
+ process.exit(1);
42954
+ }
42359
42955
  if (isInsufficientCreditsError(err)) {
42360
42956
  const friendly = getErrors().insufficientCredits;
42361
42957
  if (opts.json) jsonError("insufficient_credits", { message: friendly });
@@ -42390,7 +42986,7 @@ var init_validate_solution = __esm({
42390
42986
  var init_level_validator = __esm({
42391
42987
  "../../packages/tostudy-core/src/exercises/level-validator.ts"() {
42392
42988
  "use strict";
42393
- init_src3();
42989
+ init_src2();
42394
42990
  }
42395
42991
  });
42396
42992
 
@@ -42685,14 +43281,15 @@ var init_init_template = __esm({
42685
43281
 
42686
43282
  // src/commands/validate.ts
42687
43283
  import fs12 from "node:fs";
42688
- import path13 from "node:path";
43284
+ import path14 from "node:path";
42689
43285
  import { Command as Command13 } from "commander";
42690
43286
  var logger14, validateCommand;
42691
43287
  var init_validate = __esm({
42692
43288
  "src/commands/validate.ts"() {
42693
43289
  "use strict";
42694
- init_src();
43290
+ init_dist();
42695
43291
  init_exercises();
43292
+ init_courses();
42696
43293
  init_http();
42697
43294
  init_guards();
42698
43295
  init_course_state();
@@ -42706,7 +43303,22 @@ var init_validate = __esm({
42706
43303
  const activeCourse = await requireActiveCourse();
42707
43304
  const driftWarning = await checkCourseDrift();
42708
43305
  if (driftWarning) process.stderr.write(driftWarning + "\n");
42709
- 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
+ }
42710
43322
  if (!lessonId) {
42711
43323
  if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
42712
43324
  error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
@@ -42727,7 +43339,7 @@ var init_validate = __esm({
42727
43339
  error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
42728
43340
  }
42729
43341
  if (file2 && activeCourse.courseTags?.length) {
42730
- const ext = path13.extname(file2).toLowerCase();
43342
+ const ext = path14.extname(file2).toLowerCase();
42731
43343
  const LANG_EXTENSIONS = {
42732
43344
  ".html": ["html", "html5"],
42733
43345
  ".css": ["css"],
@@ -42762,8 +43374,6 @@ var init_validate = __esm({
42762
43374
  }
42763
43375
  }
42764
43376
  }
42765
- const data = createHttpProvider(session.apiUrl, session.token);
42766
- const deps = { data, logger: logger14 };
42767
43377
  const result = await validateSolution(
42768
43378
  {
42769
43379
  lessonId,
@@ -42780,6 +43390,18 @@ var init_validate = __esm({
42780
43390
  }
42781
43391
  process.exit(result.passed ? 0 : 1);
42782
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
+ }
43399
+ if (isCourseArchivedError(err)) {
43400
+ const friendly = getErrors().courseArchived;
43401
+ if (opts.json) jsonError("course_archived", { message: friendly });
43402
+ else process.stderr.write(friendly);
43403
+ process.exit(1);
43404
+ }
42783
43405
  if (isInsufficientCreditsError(err)) {
42784
43406
  const friendly = getErrors().insufficientCredits;
42785
43407
  if (opts.json) jsonError("insufficient_credits", { message: friendly });
@@ -43135,7 +43757,7 @@ var logger15, defaultDeps4, initCommand;
43135
43757
  var init_init = __esm({
43136
43758
  "src/commands/init.ts"() {
43137
43759
  "use strict";
43138
- init_src();
43760
+ init_dist();
43139
43761
  init_courses();
43140
43762
  init_http();
43141
43763
  init_session_store();
@@ -43174,13 +43796,13 @@ var init_init = __esm({
43174
43796
 
43175
43797
  // ../../packages/tostudy-core/src/workspace/setup-workspace.ts
43176
43798
  import fs13 from "node:fs/promises";
43177
- import path14 from "node:path";
43799
+ import path15 from "node:path";
43178
43800
  async function setupWorkspace(input2) {
43179
- const workspacePath = path14.join(input2.basePath, input2.courseSlug);
43801
+ const workspacePath = path15.join(input2.basePath, input2.courseSlug);
43180
43802
  for (const dir of WORKSPACE_DIRS) {
43181
- await fs13.mkdir(path14.join(workspacePath, dir), { recursive: true });
43803
+ await fs13.mkdir(path15.join(workspacePath, dir), { recursive: true });
43182
43804
  }
43183
- const configPath = path14.join(workspacePath, ".ana-config.json");
43805
+ const configPath = path15.join(workspacePath, ".ana-config.json");
43184
43806
  const config2 = {
43185
43807
  courseId: input2.courseId,
43186
43808
  courseSlug: input2.courseSlug,
@@ -43215,7 +43837,7 @@ async function setupWorkspace(input2) {
43215
43837
  "tostudy vault sync # Sincronizar progresso",
43216
43838
  "```"
43217
43839
  ].join("\n");
43218
- await fs13.writeFile(path14.join(workspacePath, "README.md"), readme, "utf-8");
43840
+ await fs13.writeFile(path15.join(workspacePath, "README.md"), readme, "utf-8");
43219
43841
  return { workspacePath, directories: WORKSPACE_DIRS, configPath };
43220
43842
  }
43221
43843
  var WORKSPACE_DIRS;
@@ -43317,7 +43939,7 @@ var init_templates = __esm({
43317
43939
 
43318
43940
  // ../../packages/tostudy-core/src/workspace/extract-exercise.ts
43319
43941
  import fs14 from "node:fs/promises";
43320
- import path15 from "node:path";
43942
+ import path16 from "node:path";
43321
43943
  function padOrder(n) {
43322
43944
  return String(n).padStart(2, "0");
43323
43945
  }
@@ -43341,15 +43963,15 @@ async function extractExercise(input2) {
43341
43963
  const { lessonData, exerciseTier, workspacePath } = input2;
43342
43964
  const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
43343
43965
  const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
43344
- const exercisePath = path15.join(workspacePath, "exercises", moduleDir, lessonDir);
43966
+ const exercisePath = path16.join(workspacePath, "exercises", moduleDir, lessonDir);
43345
43967
  await fs14.mkdir(exercisePath, { recursive: true });
43346
43968
  const extractedFiles = [];
43347
43969
  let hasStarterCode = false;
43348
43970
  if (lessonData.sandpackConfig?.files) {
43349
43971
  for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
43350
43972
  const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
43351
- const fullPath = path15.join(exercisePath, cleanPath);
43352
- await fs14.mkdir(path15.dirname(fullPath), { recursive: true });
43973
+ const fullPath = path16.join(exercisePath, cleanPath);
43974
+ await fs14.mkdir(path16.dirname(fullPath), { recursive: true });
43353
43975
  await fs14.writeFile(fullPath, fileData.code, "utf-8");
43354
43976
  extractedFiles.push(cleanPath);
43355
43977
  hasStarterCode = true;
@@ -43358,13 +43980,13 @@ async function extractExercise(input2) {
43358
43980
  const tierData = getTierData(lessonData.structuredData, exerciseTier);
43359
43981
  const tierCode = tierData?.code;
43360
43982
  if (tierCode) {
43361
- await fs14.writeFile(path15.join(exercisePath, "exercise.js"), tierCode, "utf-8");
43983
+ await fs14.writeFile(path16.join(exercisePath, "exercise.js"), tierCode, "utf-8");
43362
43984
  extractedFiles.push("exercise.js");
43363
43985
  hasStarterCode = true;
43364
43986
  } else {
43365
43987
  const starter = getStarterCode(lessonData.structuredData);
43366
43988
  if (starter) {
43367
- await fs14.writeFile(path15.join(exercisePath, "exercise.js"), starter, "utf-8");
43989
+ await fs14.writeFile(path16.join(exercisePath, "exercise.js"), starter, "utf-8");
43368
43990
  extractedFiles.push("exercise.js");
43369
43991
  hasStarterCode = true;
43370
43992
  }
@@ -43383,7 +44005,7 @@ async function extractExercise(input2) {
43383
44005
  }
43384
44006
  };
43385
44007
  await fs14.writeFile(
43386
- path15.join(exercisePath, "package.json"),
44008
+ path16.join(exercisePath, "package.json"),
43387
44009
  JSON.stringify(pkgJson, null, 2),
43388
44010
  "utf-8"
43389
44011
  );
@@ -43395,19 +44017,19 @@ async function extractExercise(input2) {
43395
44017
  );
43396
44018
  for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
43397
44019
  if (!sandpackFileNames.has(configFile)) {
43398
- await fs14.writeFile(path15.join(exercisePath, configFile), configContent, "utf-8");
44020
+ await fs14.writeFile(path16.join(exercisePath, configFile), configContent, "utf-8");
43399
44021
  extractedFiles.push(configFile);
43400
44022
  }
43401
44023
  }
43402
44024
  const setupSh = `#!/bin/sh
43403
44025
  ${scaffold.setupScript}
43404
44026
  `;
43405
- await fs14.writeFile(path15.join(exercisePath, "setup.sh"), setupSh, "utf-8");
44027
+ await fs14.writeFile(path16.join(exercisePath, "setup.sh"), setupSh, "utf-8");
43406
44028
  extractedFiles.push("setup.sh");
43407
44029
  }
43408
44030
  }
43409
44031
  const readme = generateReadme(lessonData, exerciseTier);
43410
- const readmePath = path15.join(exercisePath, "README.md");
44032
+ const readmePath = path16.join(exercisePath, "README.md");
43411
44033
  await fs14.writeFile(readmePath, readme, "utf-8");
43412
44034
  extractedFiles.push("README.md");
43413
44035
  return {
@@ -43480,14 +44102,14 @@ var init_workspace = __esm({
43480
44102
 
43481
44103
  // src/commands/workspace.ts
43482
44104
  import { Command as Command16 } from "commander";
43483
- import path16 from "node:path";
43484
- import os12 from "node:os";
44105
+ import path17 from "node:path";
44106
+ import os9 from "node:os";
43485
44107
  import fs15 from "node:fs/promises";
43486
44108
  var logger16, workspaceCommand;
43487
44109
  var init_workspace2 = __esm({
43488
44110
  "src/commands/workspace.ts"() {
43489
44111
  "use strict";
43490
- init_src();
44112
+ init_dist();
43491
44113
  init_workspace();
43492
44114
  init_guards();
43493
44115
  init_course_state();
@@ -43506,13 +44128,13 @@ var init_workspace2 = __esm({
43506
44128
  let directories;
43507
44129
  if (!opts.path && cwdIsWorkspace) {
43508
44130
  const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
43509
- workspacePath = resolvedCwd ?? path16.join(process.cwd(), ".tostudy");
44131
+ workspacePath = resolvedCwd ?? path17.join(process.cwd(), ".tostudy");
43510
44132
  await fs15.mkdir(workspacePath, { recursive: true });
43511
44133
  directories = ["exercises", "generated", "notes", "diagrams"];
43512
44134
  for (const dir of directories) {
43513
- await fs15.mkdir(path16.join(workspacePath, dir), { recursive: true });
44135
+ await fs15.mkdir(path17.join(workspacePath, dir), { recursive: true });
43514
44136
  }
43515
- const configPath = path16.join(workspacePath, ".ana-config.json");
44137
+ const configPath = path17.join(workspacePath, ".ana-config.json");
43516
44138
  await fs15.writeFile(
43517
44139
  configPath,
43518
44140
  JSON.stringify(
@@ -43531,7 +44153,7 @@ var init_workspace2 = __esm({
43531
44153
  "utf-8"
43532
44154
  );
43533
44155
  } else {
43534
- const basePath = opts.path ?? path16.join(os12.homedir(), "study");
44156
+ const basePath = opts.path ?? path17.join(os9.homedir(), "study");
43535
44157
  const result2 = await setupWorkspace({
43536
44158
  courseId: activeCourse.courseId,
43537
44159
  courseSlug: courseSlug(activeCourse.courseTitle),
@@ -43565,7 +44187,7 @@ Pr\xF3ximo passo: tostudy export
43565
44187
  process.exit(1);
43566
44188
  }
43567
44189
  });
43568
- 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) => {
43569
44191
  try {
43570
44192
  const activeCourse = await requireActiveCourse();
43571
44193
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -43582,22 +44204,22 @@ Pr\xF3ximo passo: tostudy export
43582
44204
  const workspacePath = ws.workspacePath;
43583
44205
  let configData = null;
43584
44206
  try {
43585
- 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");
43586
44208
  configData = JSON.parse(raw);
43587
44209
  } catch {
43588
44210
  configData = null;
43589
44211
  }
43590
- const exercisesDir = path16.join(workspacePath, "exercises");
44212
+ const exercisesDir = path17.join(workspacePath, "exercises");
43591
44213
  let exerciseCount = 0;
43592
44214
  try {
43593
44215
  const moduleDirs = await fs15.readdir(exercisesDir);
43594
44216
  for (const modDir of moduleDirs) {
43595
- const modPath = path16.join(exercisesDir, modDir);
44217
+ const modPath = path17.join(exercisesDir, modDir);
43596
44218
  const stat = await fs15.stat(modPath);
43597
44219
  if (stat.isDirectory()) {
43598
44220
  const lessonDirs = await fs15.readdir(modPath);
43599
44221
  for (const lessonDir of lessonDirs) {
43600
- const lessonPath = path16.join(modPath, lessonDir);
44222
+ const lessonPath = path17.join(modPath, lessonDir);
43601
44223
  const lstat = await fs15.stat(lessonPath);
43602
44224
  if (lstat.isDirectory()) exerciseCount++;
43603
44225
  }
@@ -43605,14 +44227,14 @@ Pr\xF3ximo passo: tostudy export
43605
44227
  }
43606
44228
  } catch {
43607
44229
  }
43608
- const generatedDir = path16.join(workspacePath, "generated");
44230
+ const generatedDir = path17.join(workspacePath, "generated");
43609
44231
  let artifactCount = 0;
43610
44232
  try {
43611
44233
  const files = await fs15.readdir(generatedDir);
43612
44234
  artifactCount = files.length;
43613
44235
  } catch {
43614
44236
  }
43615
- const diagramsDir = path16.join(workspacePath, "diagrams");
44237
+ const diagramsDir = path17.join(workspacePath, "diagrams");
43616
44238
  let diagramCount = 0;
43617
44239
  try {
43618
44240
  const files = await fs15.readdir(diagramsDir);
@@ -43664,14 +44286,14 @@ Pr\xF3ximo passo: tostudy export
43664
44286
 
43665
44287
  // src/commands/export.ts
43666
44288
  import { Command as Command17 } from "commander";
43667
- import path17 from "node:path";
43668
- import os13 from "node:os";
44289
+ import path18 from "node:path";
44290
+ import os10 from "node:os";
43669
44291
  import fs16 from "node:fs/promises";
43670
44292
  var logger17, exportCommand;
43671
44293
  var init_export = __esm({
43672
44294
  "src/commands/export.ts"() {
43673
44295
  "use strict";
43674
- init_src();
44296
+ init_dist();
43675
44297
  init_workspace();
43676
44298
  init_http();
43677
44299
  init_guards();
@@ -43679,7 +44301,7 @@ var init_export = __esm({
43679
44301
  init_resolve();
43680
44302
  init_errors();
43681
44303
  logger17 = createLogger("cli:export");
43682
- 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) => {
43683
44305
  try {
43684
44306
  const session = await requireSession();
43685
44307
  const activeCourse = await requireActiveCourse();
@@ -43697,7 +44319,7 @@ var init_export = __esm({
43697
44319
  opts.path
43698
44320
  );
43699
44321
  if (ws.found && ws.source === "cwd" && ws.workspacePath) {
43700
- const configPath = path17.join(ws.workspacePath, ".ana-config.json");
44322
+ const configPath = path18.join(ws.workspacePath, ".ana-config.json");
43701
44323
  let hasConfig = false;
43702
44324
  try {
43703
44325
  await fs16.access(configPath);
@@ -43709,7 +44331,7 @@ var init_export = __esm({
43709
44331
  logger17.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
43710
44332
  await fs16.mkdir(ws.workspacePath, { recursive: true });
43711
44333
  for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
43712
- await fs16.mkdir(path17.join(ws.workspacePath, dir), { recursive: true });
44334
+ await fs16.mkdir(path18.join(ws.workspacePath, dir), { recursive: true });
43713
44335
  }
43714
44336
  await fs16.writeFile(
43715
44337
  configPath,
@@ -43729,7 +44351,7 @@ var init_export = __esm({
43729
44351
  "utf-8"
43730
44352
  );
43731
44353
  await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
43732
- const isNamespaced = ws.workspacePath === path17.join(process.cwd(), ".tostudy");
44354
+ const isNamespaced = ws.workspacePath === path18.join(process.cwd(), ".tostudy");
43733
44355
  process.stderr.write(
43734
44356
  isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
43735
44357
  ` : `\u2728 Workspace inicializado nesta pasta.
@@ -43780,19 +44402,19 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
43780
44402
  // src/commands/open.ts
43781
44403
  import { Command as Command18 } from "commander";
43782
44404
  import { execFile as execFile3 } from "node:child_process";
43783
- import path18 from "node:path";
43784
- import os14 from "node:os";
44405
+ import path19 from "node:path";
44406
+ import os11 from "node:os";
43785
44407
  var logger18, openCommand;
43786
44408
  var init_open = __esm({
43787
44409
  "src/commands/open.ts"() {
43788
44410
  "use strict";
43789
- init_src();
44411
+ init_dist();
43790
44412
  init_guards();
43791
44413
  init_course_state();
43792
44414
  init_resolve();
43793
44415
  init_errors();
43794
44416
  logger18 = createLogger("cli:open");
43795
- 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) => {
43796
44418
  try {
43797
44419
  const activeCourse = await requireActiveCourse();
43798
44420
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -43846,14 +44468,14 @@ var init_types2 = __esm({
43846
44468
 
43847
44469
  // ../../packages/tostudy-core/src/vault/write-vault.ts
43848
44470
  import fs17 from "node:fs/promises";
43849
- import path19 from "node:path";
44471
+ import path20 from "node:path";
43850
44472
  async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
43851
44473
  for (const file2 of files) {
43852
- const fullPath = path19.join(outputPath, file2.relativePath);
43853
- 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 });
43854
44476
  await fs17.writeFile(fullPath, file2.content, "utf-8");
43855
44477
  }
43856
- const vaultPath = path19.join(outputPath, courseSlug2);
44478
+ const vaultPath = path20.join(outputPath, courseSlug2);
43857
44479
  const marker = {
43858
44480
  courseId,
43859
44481
  courseSlug: courseSlug2,
@@ -43862,7 +44484,7 @@ async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
43862
44484
  };
43863
44485
  await fs17.mkdir(vaultPath, { recursive: true });
43864
44486
  await fs17.writeFile(
43865
- path19.join(vaultPath, VAULT_MARKER_FILENAME),
44487
+ path20.join(vaultPath, VAULT_MARKER_FILENAME),
43866
44488
  JSON.stringify(marker, null, 2),
43867
44489
  "utf-8"
43868
44490
  );
@@ -43887,14 +44509,14 @@ var init_vault = __esm({
43887
44509
 
43888
44510
  // src/commands/vault.ts
43889
44511
  import { Command as Command19 } from "commander";
43890
- import path20 from "node:path";
43891
- import os15 from "node:os";
44512
+ import path21 from "node:path";
44513
+ import os12 from "node:os";
43892
44514
  import fs18 from "node:fs/promises";
43893
44515
  var logger19, vaultCommand;
43894
44516
  var init_vault2 = __esm({
43895
44517
  "src/commands/vault.ts"() {
43896
44518
  "use strict";
43897
- init_src();
44519
+ init_dist();
43898
44520
  init_vault();
43899
44521
  init_courses();
43900
44522
  init_http();
@@ -43904,7 +44526,7 @@ var init_vault2 = __esm({
43904
44526
  init_errors();
43905
44527
  logger19 = createLogger("cli:vault");
43906
44528
  vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
43907
- 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) => {
43908
44530
  try {
43909
44531
  const session = await requireSession();
43910
44532
  const activeCourse = await requireActiveCourse();
@@ -43986,7 +44608,7 @@ Para visualizar:
43986
44608
  process.exit(1);
43987
44609
  }
43988
44610
  });
43989
- 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) => {
43990
44612
  try {
43991
44613
  const session = await requireSession();
43992
44614
  const activeCourse = await requireActiveCourse();
@@ -44012,7 +44634,7 @@ Para visualizar:
44012
44634
  const data = createHttpProvider(session.apiUrl, session.token);
44013
44635
  const deps = { data, logger: logger19 };
44014
44636
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
44015
- const markerPath = path20.join(vaultPath, ".ana-vault.json");
44637
+ const markerPath = path21.join(vaultPath, ".ana-vault.json");
44016
44638
  const markerRaw = await fs18.readFile(markerPath, "utf-8");
44017
44639
  const marker = JSON.parse(markerRaw);
44018
44640
  marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -44022,7 +44644,7 @@ Para visualizar:
44022
44644
  currentLesson: progress3.currentLesson.title
44023
44645
  };
44024
44646
  await fs18.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
44025
- const courseIndexPath = path20.join(vaultPath, slug, "index.md");
44647
+ const courseIndexPath = path21.join(vaultPath, slug, "index.md");
44026
44648
  try {
44027
44649
  let indexContent = await fs18.readFile(courseIndexPath, "utf-8");
44028
44650
  indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
@@ -44148,7 +44770,7 @@ var logger20, syncCommand;
44148
44770
  var init_sync = __esm({
44149
44771
  "src/commands/sync.ts"() {
44150
44772
  "use strict";
44151
- init_src();
44773
+ init_dist();
44152
44774
  init_guards();
44153
44775
  init_formatter();
44154
44776
  init_instruction_pipeline();
@@ -44205,7 +44827,7 @@ var logger21, briefCommand;
44205
44827
  var init_brief = __esm({
44206
44828
  "src/commands/brief.ts"() {
44207
44829
  "use strict";
44208
- init_src();
44830
+ init_dist();
44209
44831
  init_guards();
44210
44832
  init_cache();
44211
44833
  init_api();
@@ -44257,7 +44879,7 @@ var logger22, briefCreateCommand;
44257
44879
  var init_brief_create = __esm({
44258
44880
  "src/commands/brief-create.ts"() {
44259
44881
  "use strict";
44260
- init_src();
44882
+ init_dist();
44261
44883
  init_guards();
44262
44884
  init_bootstrap();
44263
44885
  init_api();
@@ -44335,15 +44957,15 @@ var init_brief_open = __esm({
44335
44957
 
44336
44958
  // src/sessions/storage.ts
44337
44959
  import fs19 from "node:fs";
44338
- import path21 from "node:path";
44960
+ import path22 from "node:path";
44339
44961
  function sessionsDir(workspacePath) {
44340
- return path21.join(workspacePath, ".tostudy", "sessions");
44962
+ return path22.join(workspacePath, ".tostudy", "sessions");
44341
44963
  }
44342
44964
  async function saveModuleSummary(workspacePath, input2) {
44343
44965
  const dir = sessionsDir(workspacePath);
44344
44966
  fs19.mkdirSync(dir, { recursive: true });
44345
44967
  const filename = `module-${input2.moduleId}-summary.md`;
44346
- const filePath = path21.join(dir, filename);
44968
+ const filePath = path22.join(dir, filename);
44347
44969
  const header = [
44348
44970
  "---",
44349
44971
  `moduleId: ${input2.moduleId}`,
@@ -44362,7 +44984,7 @@ async function loadSessionContext(workspacePath) {
44362
44984
  const files = fs19.readdirSync(dir).filter((f) => f.startsWith("module-") && f.endsWith("-summary.md")).sort();
44363
44985
  const summaries = [];
44364
44986
  for (const file2 of files) {
44365
- const content = fs19.readFileSync(path21.join(dir, file2), "utf-8");
44987
+ const content = fs19.readFileSync(path22.join(dir, file2), "utf-8");
44366
44988
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
44367
44989
  if (!frontmatterMatch) continue;
44368
44990
  const meta3 = frontmatterMatch[1];
@@ -44378,7 +45000,7 @@ var logger23;
44378
45000
  var init_storage = __esm({
44379
45001
  "src/sessions/storage.ts"() {
44380
45002
  "use strict";
44381
- init_src();
45003
+ init_dist();
44382
45004
  logger23 = createLogger("cli:sessions");
44383
45005
  }
44384
45006
  });
@@ -44390,7 +45012,7 @@ var logger24, compactCommand;
44390
45012
  var init_compact = __esm({
44391
45013
  "src/commands/compact.ts"() {
44392
45014
  "use strict";
44393
- init_src();
45015
+ init_dist();
44394
45016
  init_guards();
44395
45017
  init_workspace_state();
44396
45018
  init_storage();
@@ -44439,7 +45061,7 @@ var logger25, contextCommand;
44439
45061
  var init_context = __esm({
44440
45062
  "src/commands/context.ts"() {
44441
45063
  "use strict";
44442
- init_src();
45064
+ init_dist();
44443
45065
  init_workspace_state();
44444
45066
  init_storage();
44445
45067
  init_formatter();
@@ -44493,21 +45115,125 @@ var init_context = __esm({
44493
45115
  }
44494
45116
  });
44495
45117
 
44496
- // src/commands/level.ts
45118
+ // src/commands/memory.ts
44497
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";
44498
45224
  function isExerciseLevel2(value) {
44499
45225
  return ALL_LEVELS.includes(value);
44500
45226
  }
44501
- var logger26, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
45227
+ var logger28, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
44502
45228
  var init_level = __esm({
44503
45229
  "src/commands/level.ts"() {
44504
45230
  "use strict";
44505
- init_src();
45231
+ init_dist();
44506
45232
  init_http();
44507
45233
  init_exercises();
44508
45234
  init_guards();
44509
45235
  init_formatter();
44510
- logger26 = createLogger("cli:level");
45236
+ logger28 = createLogger("cli:level");
44511
45237
  ALL_LEVELS = ["L0", "L1", "L2", "L3", "L4"];
44512
45238
  LEVEL_LABELS2 = {
44513
45239
  L0: "Pr\xE9-check (perguntas conceituais)",
@@ -44516,12 +45242,12 @@ var init_level = __esm({
44516
45242
  L3: "Guiado (passo a passo + checkpoints)",
44517
45243
  L4: "Desafio livre (folha em branco)"
44518
45244
  };
44519
- 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) => {
44520
45246
  try {
44521
45247
  const session = await requireSession();
44522
45248
  const activeCourse = await requireActiveCourse();
44523
45249
  const data = createHttpProvider(session.apiUrl, session.token);
44524
- const deps = { data, logger: logger26 };
45250
+ const deps = { data, logger: logger28 };
44525
45251
  if (!rawLevel) {
44526
45252
  const levels = await getEnrollmentLevels({ enrollmentId: activeCourse.enrollmentId }, deps);
44527
45253
  if (opts.json) {
@@ -44579,18 +45305,18 @@ var init_level = __esm({
44579
45305
  });
44580
45306
 
44581
45307
  // src/commands/theory.ts
44582
- import { Command as Command28 } from "commander";
44583
- var logger27, EXERCISE_LEVELS2, theoryCommand;
45308
+ import { Command as Command30 } from "commander";
45309
+ var logger29, EXERCISE_LEVELS2, theoryCommand;
44584
45310
  var init_theory = __esm({
44585
45311
  "src/commands/theory.ts"() {
44586
45312
  "use strict";
44587
- init_src();
45313
+ init_dist();
44588
45314
  init_guards();
44589
45315
  init_formatter();
44590
45316
  init_errors();
44591
- logger27 = createLogger("cli:theory");
45317
+ logger29 = createLogger("cli:theory");
44592
45318
  EXERCISE_LEVELS2 = ["L0", "L1", "L2", "L3", "L4"];
44593
- 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) => {
44594
45320
  try {
44595
45321
  const session = await requireSession();
44596
45322
  const active = await requireActiveCourse();
@@ -44627,7 +45353,7 @@ var init_theory = __esm({
44627
45353
  currentLevel = levels.currentLevel;
44628
45354
  }
44629
45355
  const requestUrl = `${session.apiUrl}/api/cli/exercises/more-theory`;
44630
- logger27.debug("Requesting more theory", { lessonId, currentLevel });
45356
+ logger29.debug("Requesting more theory", { lessonId, currentLevel });
44631
45357
  const res = await fetch(requestUrl, {
44632
45358
  method: "POST",
44633
45359
  headers: {
@@ -44945,7 +45671,7 @@ var init_resolve_enrollment = __esm({
44945
45671
  });
44946
45672
 
44947
45673
  // src/commands/export-grades.ts
44948
- import { Command as Command29 } from "commander";
45674
+ import { Command as Command31 } from "commander";
44949
45675
  function isFormat(value) {
44950
45676
  return SUPPORTED_FORMATS.includes(value);
44951
45677
  }
@@ -45032,15 +45758,15 @@ async function runExportGrades(opts, deps = defaultDeps5) {
45032
45758
  if (!rendered.endsWith("\n")) rendered += "\n";
45033
45759
  deps.stdoutWrite(rendered);
45034
45760
  }
45035
- var logger28, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
45761
+ var logger30, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
45036
45762
  var init_export_grades = __esm({
45037
45763
  "src/commands/export-grades.ts"() {
45038
45764
  "use strict";
45039
- init_src();
45765
+ init_dist();
45040
45766
  init_guards();
45041
45767
  init_grades();
45042
45768
  init_resolve_enrollment();
45043
- logger28 = createLogger("cli:export-grades");
45769
+ logger30 = createLogger("cli:export-grades");
45044
45770
  SUPPORTED_FORMATS = ["json", "csv", "md"];
45045
45771
  defaultDeps5 = {
45046
45772
  requireSession,
@@ -45050,10 +45776,10 @@ var init_export_grades = __esm({
45050
45776
  stderrWrite: (message) => process.stderr.write(message),
45051
45777
  // process.exit is typed as `never` — wrap so the cast is local to one line.
45052
45778
  exit: (code) => process.exit(code),
45053
- logger: logger28,
45779
+ logger: logger30,
45054
45780
  resolveBySlug: resolveEnrollmentBySlug
45055
45781
  };
45056
- 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(
45057
45783
  "--course <slug>",
45058
45784
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, exporta o curso ativo."
45059
45785
  ).option("--format <json|csv|md>", "Formato de sa\xEDda (default: json)", "json").addHelpText(
@@ -45078,7 +45804,7 @@ C\xF3digos de sa\xEDda:
45078
45804
  });
45079
45805
 
45080
45806
  // src/commands/grades.ts
45081
- import { Command as Command30 } from "commander";
45807
+ import { Command as Command32 } from "commander";
45082
45808
  async function fetchAttempts(session, enrollmentId, deps) {
45083
45809
  const url2 = `${session.apiUrl}/api/cli/validations/by-enrollment?enrollmentId=${encodeURIComponent(
45084
45810
  enrollmentId
@@ -45219,29 +45945,29 @@ async function runGrades(opts, deps = defaultDeps6) {
45219
45945
  );
45220
45946
  deps.stdoutWrite(formatGradesAllEnrollmentsTable(summaries) + "\n");
45221
45947
  }
45222
- var logger29, defaultDeps6, gradesCommand;
45948
+ var logger31, defaultDeps6, gradesCommand;
45223
45949
  var init_grades2 = __esm({
45224
45950
  "src/commands/grades.ts"() {
45225
45951
  "use strict";
45226
- init_src();
45952
+ init_dist();
45227
45953
  init_http();
45228
45954
  init_courses();
45229
45955
  init_guards();
45230
45956
  init_grades();
45231
45957
  init_resolve_enrollment();
45232
- logger29 = createLogger("cli:grades");
45958
+ logger31 = createLogger("cli:grades");
45233
45959
  defaultDeps6 = {
45234
45960
  requireSession,
45235
45961
  fetch: globalThis.fetch.bind(globalThis),
45236
45962
  stdoutWrite: (message) => process.stdout.write(message),
45237
45963
  stderrWrite: (message) => process.stderr.write(message),
45238
45964
  exit: (code) => process.exit(code),
45239
- logger: logger29,
45965
+ logger: logger31,
45240
45966
  resolveBySlug: resolveEnrollmentBySlug,
45241
45967
  buildDataProvider: createHttpProvider,
45242
45968
  listCoursesFn: listCourses
45243
45969
  };
45244
- 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(
45245
45971
  "--course <slug>",
45246
45972
  "Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, lista todos os cursos."
45247
45973
  ).addHelpText(
@@ -45265,7 +45991,7 @@ C\xF3digos de sa\xEDda:
45265
45991
  });
45266
45992
 
45267
45993
  // src/commands/review.ts
45268
- import { Command as Command31 } from "commander";
45994
+ import { Command as Command33 } from "commander";
45269
45995
  import { createInterface } from "node:readline/promises";
45270
45996
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
45271
45997
  async function defaultPromptLessonChoice(lessonIds) {
@@ -45401,26 +46127,26 @@ async function runReview(slug, opts, deps = defaultDeps7) {
45401
46127
  }) + "\n"
45402
46128
  );
45403
46129
  }
45404
- var logger30, defaultDeps7, reviewCommand;
46130
+ var logger32, defaultDeps7, reviewCommand;
45405
46131
  var init_review2 = __esm({
45406
46132
  "src/commands/review.ts"() {
45407
46133
  "use strict";
45408
- init_src();
46134
+ init_dist();
45409
46135
  init_guards();
45410
46136
  init_grades();
45411
46137
  init_resolve_enrollment();
45412
- logger30 = createLogger("cli:review");
46138
+ logger32 = createLogger("cli:review");
45413
46139
  defaultDeps7 = {
45414
46140
  requireSession,
45415
46141
  fetch: globalThis.fetch.bind(globalThis),
45416
46142
  stdoutWrite: (message) => process.stdout.write(message),
45417
46143
  stderrWrite: (message) => process.stderr.write(message),
45418
46144
  exit: (code) => process.exit(code),
45419
- logger: logger30,
46145
+ logger: logger32,
45420
46146
  resolveBySlug: resolveEnrollmentBySlug,
45421
46147
  promptLessonChoice: defaultPromptLessonChoice
45422
46148
  };
45423
- 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(
45424
46150
  "after",
45425
46151
  `
45426
46152
  Exemplos:
@@ -45449,9 +46175,9 @@ __export(cli_exports, {
45449
46175
  CLI_VERSION: () => CLI_VERSION,
45450
46176
  createProgram: () => createProgram
45451
46177
  });
45452
- import { Command as Command32 } from "commander";
46178
+ import { Command as Command34 } from "commander";
45453
46179
  function createProgram() {
45454
- const program2 = new Command32();
46180
+ const program2 = new Command34();
45455
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");
45456
46182
  program2.addCommand(setupCommand);
45457
46183
  program2.addCommand(doctorCommand);
@@ -45484,6 +46210,8 @@ function createProgram() {
45484
46210
  program2.addCommand(vaultCommand);
45485
46211
  program2.addCommand(compactCommand);
45486
46212
  program2.addCommand(contextCommand);
46213
+ program2.addCommand(memoryCommand);
46214
+ program2.addCommand(insightCommand);
45487
46215
  return program2;
45488
46216
  }
45489
46217
  var init_cli = __esm({
@@ -45515,6 +46243,8 @@ var init_cli = __esm({
45515
46243
  init_brief_open();
45516
46244
  init_compact();
45517
46245
  init_context();
46246
+ init_memory();
46247
+ init_insight();
45518
46248
  init_level();
45519
46249
  init_theory();
45520
46250
  init_export_grades();