@tostudy-ai/cli 0.14.0 → 0.15.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
@@ -41,137 +41,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
41
41
  mod
42
42
  ));
43
43
 
44
- // ../../node_modules/picocolors/picocolors.js
45
- var require_picocolors = __commonJS({
46
- "../../node_modules/picocolors/picocolors.js"(exports, module) {
47
- var p = process || {};
48
- var argv = p.argv || [];
49
- var env = p.env || {};
50
- var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
51
- var formatter = (open, close, replace = open) => (input2) => {
52
- let string4 = "" + input2, index2 = string4.indexOf(close, open.length);
53
- return ~index2 ? open + replaceClose(string4, close, replace, index2) + close : open + string4 + close;
54
- };
55
- var replaceClose = (string4, close, replace, index2) => {
56
- let result = "", cursor = 0;
57
- do {
58
- result += string4.substring(cursor, index2) + replace;
59
- cursor = index2 + close.length;
60
- index2 = string4.indexOf(close, cursor);
61
- } while (~index2);
62
- return result + string4.substring(cursor);
63
- };
64
- var createColors = (enabled = isColorSupported) => {
65
- let f = enabled ? formatter : () => String;
66
- return {
67
- isColorSupported: enabled,
68
- reset: f("\x1B[0m", "\x1B[0m"),
69
- bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
70
- dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
71
- italic: f("\x1B[3m", "\x1B[23m"),
72
- underline: f("\x1B[4m", "\x1B[24m"),
73
- inverse: f("\x1B[7m", "\x1B[27m"),
74
- hidden: f("\x1B[8m", "\x1B[28m"),
75
- strikethrough: f("\x1B[9m", "\x1B[29m"),
76
- black: f("\x1B[30m", "\x1B[39m"),
77
- red: f("\x1B[31m", "\x1B[39m"),
78
- green: f("\x1B[32m", "\x1B[39m"),
79
- yellow: f("\x1B[33m", "\x1B[39m"),
80
- blue: f("\x1B[34m", "\x1B[39m"),
81
- magenta: f("\x1B[35m", "\x1B[39m"),
82
- cyan: f("\x1B[36m", "\x1B[39m"),
83
- white: f("\x1B[37m", "\x1B[39m"),
84
- gray: f("\x1B[90m", "\x1B[39m"),
85
- bgBlack: f("\x1B[40m", "\x1B[49m"),
86
- bgRed: f("\x1B[41m", "\x1B[49m"),
87
- bgGreen: f("\x1B[42m", "\x1B[49m"),
88
- bgYellow: f("\x1B[43m", "\x1B[49m"),
89
- bgBlue: f("\x1B[44m", "\x1B[49m"),
90
- bgMagenta: f("\x1B[45m", "\x1B[49m"),
91
- bgCyan: f("\x1B[46m", "\x1B[49m"),
92
- bgWhite: f("\x1B[47m", "\x1B[49m"),
93
- blackBright: f("\x1B[90m", "\x1B[39m"),
94
- redBright: f("\x1B[91m", "\x1B[39m"),
95
- greenBright: f("\x1B[92m", "\x1B[39m"),
96
- yellowBright: f("\x1B[93m", "\x1B[39m"),
97
- blueBright: f("\x1B[94m", "\x1B[39m"),
98
- magentaBright: f("\x1B[95m", "\x1B[39m"),
99
- cyanBright: f("\x1B[96m", "\x1B[39m"),
100
- whiteBright: f("\x1B[97m", "\x1B[39m"),
101
- bgBlackBright: f("\x1B[100m", "\x1B[49m"),
102
- bgRedBright: f("\x1B[101m", "\x1B[49m"),
103
- bgGreenBright: f("\x1B[102m", "\x1B[49m"),
104
- bgYellowBright: f("\x1B[103m", "\x1B[49m"),
105
- bgBlueBright: f("\x1B[104m", "\x1B[49m"),
106
- bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
107
- bgCyanBright: f("\x1B[106m", "\x1B[49m"),
108
- bgWhiteBright: f("\x1B[107m", "\x1B[49m")
109
- };
110
- };
111
- module.exports = createColors();
112
- module.exports.createColors = createColors;
113
- }
114
- });
115
-
116
- // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-DJVL2Rjl.mjs
117
- function formatJson(entry) {
118
- const { timestamp: timestamp2, level, message, service, environment, context, data, error: error49 } = entry;
119
- const output3 = {
120
- timestamp: timestamp2,
121
- level,
122
- message,
123
- service,
124
- environment,
125
- ...context,
126
- ...data
127
- };
128
- if (error49) output3.error = error49;
129
- return JSON.stringify(output3);
130
- }
131
- function formatTime(timestamp2) {
132
- const date6 = new Date(timestamp2);
133
- return `${String(date6.getUTCHours()).padStart(2, "0")}:${String(date6.getUTCMinutes()).padStart(2, "0")}:${String(date6.getUTCSeconds()).padStart(2, "0")}`;
134
- }
135
- function formatLevel(level) {
136
- const colorFn = LEVEL_COLORS[level];
137
- return colorFn(level.toUpperCase().padEnd(LEVEL_WIDTH));
138
- }
139
- function formatData(data) {
140
- const entries = Object.entries(data);
141
- if (entries.length === 0) return "";
142
- return "\n" + entries.map(([key, value]) => {
143
- const formatted = typeof value === "object" ? JSON.stringify(value) : String(value);
144
- return ` ${import_picocolors.default.dim(key + ":")} ${formatted}`;
145
- }).join("\n");
146
- }
147
- function formatPretty(entry) {
148
- const { timestamp: timestamp2, level, message, service, context, data, error: error49 } = entry;
149
- const time4 = formatTime(timestamp2);
150
- const lvl = formatLevel(level);
151
- const mod = context.module ? import_picocolors.default.dim(`[${context.module}]`) + " " : "";
152
- const svc = import_picocolors.default.dim(`[${service}]`) + " ";
153
- let output3 = `${import_picocolors.default.dim(time4)} ${lvl} ${svc}${mod}${message}`;
154
- const traceInfo = {};
155
- if (context.traceId) traceInfo.traceId = context.traceId;
156
- if (context.requestId && context.requestId !== context.traceId) traceInfo.requestId = context.requestId;
157
- if (context.caller) traceInfo.caller = context.caller;
158
- if (context.duration !== void 0) traceInfo.duration = `${context.duration}ms`;
159
- const allData = {
160
- ...traceInfo,
161
- ...data
162
- };
163
- if (Object.keys(allData).length > 0) output3 += formatData(allData);
164
- if (error49) {
165
- output3 += "\n" + import_picocolors.default.red(` ${error49.name}: ${error49.message}`);
166
- if (error49.code) output3 += import_picocolors.default.dim(` (${error49.code})`);
167
- if (error49.stack) {
168
- const stackLines = error49.stack.split("\n").slice(1, 4);
169
- output3 += "\n" + stackLines.map((l) => import_picocolors.default.dim(` ${l.trim()}`)).join("\n");
170
- }
171
- if (error49.cause) output3 += "\n" + import_picocolors.default.dim(` Caused by: ${error49.cause.name}: ${error49.cause.message}`);
172
- }
173
- return output3;
174
- }
44
+ // ../../node_modules/@parisgroup-ai/logger/dist/sanitize-u1LaSkAO.mjs
175
45
  function email(value) {
176
46
  if (!value || typeof value !== "string") return "[INVALID_EMAIL]";
177
47
  const atIndex = value.indexOf("@");
@@ -296,42 +166,9 @@ function headers(hdrs) {
296
166
  for (const key of Object.keys(sanitized)) if (sensitiveHeaders.some((h) => key.toLowerCase() === h)) sanitized[key] = "[REDACTED]";
297
167
  return sanitized;
298
168
  }
299
- function extractErrorInfo(error49, includeStack, visited = /* @__PURE__ */ new WeakSet()) {
300
- const info = {
301
- name: error49.name,
302
- message: error49.message
303
- };
304
- if (includeStack && error49.stack) info.stack = error49.stack;
305
- if ("code" in error49 && typeof error49.code === "string") info.code = error49.code;
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);
312
- return info;
313
- }
314
- var import_picocolors, LOG_LEVELS, LEVEL_COLORS, LEVEL_WIDTH, SENSITIVE_FIELDS, sanitize, BaseLogger;
315
- var init_logger_base_DJVL2Rjl = __esm({
316
- "../../node_modules/@parisgroup-ai/logger/dist/logger.base-DJVL2Rjl.mjs"() {
317
- import_picocolors = __toESM(require_picocolors(), 1);
318
- LOG_LEVELS = {
319
- trace: 0,
320
- debug: 1,
321
- info: 2,
322
- warn: 3,
323
- error: 4,
324
- fatal: 5
325
- };
326
- LEVEL_COLORS = {
327
- trace: import_picocolors.default.gray,
328
- debug: import_picocolors.default.cyan,
329
- info: import_picocolors.default.green,
330
- warn: import_picocolors.default.yellow,
331
- error: import_picocolors.default.red,
332
- fatal: (s) => import_picocolors.default.bgRed(import_picocolors.default.white(s))
333
- };
334
- LEVEL_WIDTH = 5;
169
+ var SENSITIVE_FIELDS, sanitize;
170
+ var init_sanitize_u1LaSkAO = __esm({
171
+ "../../node_modules/@parisgroup-ai/logger/dist/sanitize-u1LaSkAO.mjs"() {
335
172
  SENSITIVE_FIELDS = [
336
173
  "password",
337
174
  "senha",
@@ -398,6 +235,177 @@ var init_logger_base_DJVL2Rjl = __esm({
398
235
  headers,
399
236
  isSensitiveKey
400
237
  };
238
+ }
239
+ });
240
+
241
+ // ../../node_modules/picocolors/picocolors.js
242
+ var require_picocolors = __commonJS({
243
+ "../../node_modules/picocolors/picocolors.js"(exports, module) {
244
+ var p = process || {};
245
+ var argv = p.argv || [];
246
+ var env = p.env || {};
247
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
248
+ var formatter = (open, close, replace = open) => (input2) => {
249
+ let string4 = "" + input2, index2 = string4.indexOf(close, open.length);
250
+ return ~index2 ? open + replaceClose(string4, close, replace, index2) + close : open + string4 + close;
251
+ };
252
+ var replaceClose = (string4, close, replace, index2) => {
253
+ let result = "", cursor = 0;
254
+ do {
255
+ result += string4.substring(cursor, index2) + replace;
256
+ cursor = index2 + close.length;
257
+ index2 = string4.indexOf(close, cursor);
258
+ } while (~index2);
259
+ return result + string4.substring(cursor);
260
+ };
261
+ var createColors = (enabled = isColorSupported) => {
262
+ let f = enabled ? formatter : () => String;
263
+ return {
264
+ isColorSupported: enabled,
265
+ reset: f("\x1B[0m", "\x1B[0m"),
266
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
267
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
268
+ italic: f("\x1B[3m", "\x1B[23m"),
269
+ underline: f("\x1B[4m", "\x1B[24m"),
270
+ inverse: f("\x1B[7m", "\x1B[27m"),
271
+ hidden: f("\x1B[8m", "\x1B[28m"),
272
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
273
+ black: f("\x1B[30m", "\x1B[39m"),
274
+ red: f("\x1B[31m", "\x1B[39m"),
275
+ green: f("\x1B[32m", "\x1B[39m"),
276
+ yellow: f("\x1B[33m", "\x1B[39m"),
277
+ blue: f("\x1B[34m", "\x1B[39m"),
278
+ magenta: f("\x1B[35m", "\x1B[39m"),
279
+ cyan: f("\x1B[36m", "\x1B[39m"),
280
+ white: f("\x1B[37m", "\x1B[39m"),
281
+ gray: f("\x1B[90m", "\x1B[39m"),
282
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
283
+ bgRed: f("\x1B[41m", "\x1B[49m"),
284
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
285
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
286
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
287
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
288
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
289
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
290
+ blackBright: f("\x1B[90m", "\x1B[39m"),
291
+ redBright: f("\x1B[91m", "\x1B[39m"),
292
+ greenBright: f("\x1B[92m", "\x1B[39m"),
293
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
294
+ blueBright: f("\x1B[94m", "\x1B[39m"),
295
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
296
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
297
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
298
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
299
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
300
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
301
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
302
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
303
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
304
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
305
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
306
+ };
307
+ };
308
+ module.exports = createColors();
309
+ module.exports.createColors = createColors;
310
+ }
311
+ });
312
+
313
+ // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-W8CRO9aE.mjs
314
+ function formatJson(entry) {
315
+ const { timestamp: timestamp2, level, message, service, environment, context, data, error: error49 } = entry;
316
+ const output3 = {
317
+ timestamp: timestamp2,
318
+ level,
319
+ message,
320
+ service,
321
+ environment,
322
+ ...context,
323
+ ...data
324
+ };
325
+ if (error49) output3.error = error49;
326
+ return JSON.stringify(output3);
327
+ }
328
+ function formatTime(timestamp2) {
329
+ const date6 = new Date(timestamp2);
330
+ return `${String(date6.getUTCHours()).padStart(2, "0")}:${String(date6.getUTCMinutes()).padStart(2, "0")}:${String(date6.getUTCSeconds()).padStart(2, "0")}`;
331
+ }
332
+ function formatLevel(level) {
333
+ const colorFn = LEVEL_COLORS[level];
334
+ return colorFn(level.toUpperCase().padEnd(LEVEL_WIDTH));
335
+ }
336
+ function formatData(data) {
337
+ const entries = Object.entries(data);
338
+ if (entries.length === 0) return "";
339
+ return "\n" + entries.map(([key, value]) => {
340
+ const formatted = typeof value === "object" ? JSON.stringify(value) : String(value);
341
+ return ` ${import_picocolors.default.dim(key + ":")} ${formatted}`;
342
+ }).join("\n");
343
+ }
344
+ function formatPretty(entry) {
345
+ const { timestamp: timestamp2, level, message, service, context, data, error: error49 } = entry;
346
+ const time4 = formatTime(timestamp2);
347
+ const lvl = formatLevel(level);
348
+ const mod = context.module ? import_picocolors.default.dim(`[${context.module}]`) + " " : "";
349
+ const svc = import_picocolors.default.dim(`[${service}]`) + " ";
350
+ let output3 = `${import_picocolors.default.dim(time4)} ${lvl} ${svc}${mod}${message}`;
351
+ const traceInfo = {};
352
+ if (context.traceId) traceInfo.traceId = context.traceId;
353
+ if (context.requestId && context.requestId !== context.traceId) traceInfo.requestId = context.requestId;
354
+ if (context.caller) traceInfo.caller = context.caller;
355
+ if (context.duration !== void 0) traceInfo.duration = `${context.duration}ms`;
356
+ const allData = {
357
+ ...traceInfo,
358
+ ...data
359
+ };
360
+ if (Object.keys(allData).length > 0) output3 += formatData(allData);
361
+ if (error49) {
362
+ output3 += "\n" + import_picocolors.default.red(` ${error49.name}: ${error49.message}`);
363
+ if (error49.code) output3 += import_picocolors.default.dim(` (${error49.code})`);
364
+ if (error49.stack) {
365
+ const stackLines = error49.stack.split("\n").slice(1, 4);
366
+ output3 += "\n" + stackLines.map((l) => import_picocolors.default.dim(` ${l.trim()}`)).join("\n");
367
+ }
368
+ if (error49.cause) output3 += "\n" + import_picocolors.default.dim(` Caused by: ${error49.cause.name}: ${error49.cause.message}`);
369
+ }
370
+ return output3;
371
+ }
372
+ function extractErrorInfo(error49, includeStack, visited = /* @__PURE__ */ new WeakSet()) {
373
+ const info = {
374
+ name: error49.name,
375
+ message: error49.message
376
+ };
377
+ if (includeStack && error49.stack) info.stack = error49.stack;
378
+ if ("code" in error49 && typeof error49.code === "string") info.code = error49.code;
379
+ visited.add(error49);
380
+ if (error49.cause instanceof Error) if (visited.has(error49.cause)) info.cause = {
381
+ name: error49.cause.name,
382
+ message: "[Circular]"
383
+ };
384
+ else info.cause = extractErrorInfo(error49.cause, includeStack, visited);
385
+ return info;
386
+ }
387
+ var import_picocolors, LOG_LEVELS, LEVEL_COLORS, LEVEL_WIDTH, BaseLogger;
388
+ var init_logger_base_W8CRO9aE = __esm({
389
+ "../../node_modules/@parisgroup-ai/logger/dist/logger.base-W8CRO9aE.mjs"() {
390
+ init_sanitize_u1LaSkAO();
391
+ import_picocolors = __toESM(require_picocolors(), 1);
392
+ LOG_LEVELS = {
393
+ trace: 0,
394
+ debug: 1,
395
+ info: 2,
396
+ warn: 3,
397
+ error: 4,
398
+ fatal: 5
399
+ };
400
+ LEVEL_COLORS = {
401
+ trace: import_picocolors.default.gray,
402
+ debug: import_picocolors.default.cyan,
403
+ info: import_picocolors.default.green,
404
+ warn: import_picocolors.default.yellow,
405
+ error: import_picocolors.default.red,
406
+ fatal: (s) => import_picocolors.default.bgRed(import_picocolors.default.white(s))
407
+ };
408
+ LEVEL_WIDTH = 5;
401
409
  BaseLogger = class {
402
410
  level;
403
411
  format;
@@ -639,7 +647,8 @@ function createLogger(module, options) {
639
647
  var asyncLocalStorage, ConsoleTransport, FileTransport, HttpTransport, SINGLETON_KEY, Logger, logger;
640
648
  var init_dist = __esm({
641
649
  "../../node_modules/@parisgroup-ai/logger/dist/index.mjs"() {
642
- init_logger_base_DJVL2Rjl();
650
+ init_logger_base_W8CRO9aE();
651
+ init_sanitize_u1LaSkAO();
643
652
  asyncLocalStorage = new AsyncLocalStorage();
644
653
  ConsoleTransport = class {
645
654
  log(entry, formatted) {
@@ -1575,13 +1584,27 @@ function detectIDEs(home = os3.homedir()) {
1575
1584
  detected: checkExists(path4.join(home, ".codex")),
1576
1585
  configPath: path4.join(home, ".codex", "config.toml")
1577
1586
  },
1578
- // OpenCode: ~/.opencode
1587
+ // OpenCode: ~/.opencode or XDG ~/.config/opencode
1579
1588
  {
1580
1589
  name: "OpenCode",
1581
1590
  id: "opencode",
1582
- detected: checkExists(path4.join(home, ".opencode")),
1591
+ detected: checkExists(path4.join(home, ".opencode")) || checkExists(path4.join(home, ".config", "opencode")),
1583
1592
  configPath: path4.join(home, ".opencode", "opencode.json")
1584
1593
  },
1594
+ // Grok CLI: ~/.grok
1595
+ {
1596
+ name: "Grok CLI",
1597
+ id: "grok",
1598
+ detected: checkExists(path4.join(home, ".grok")),
1599
+ configPath: path4.join(home, ".grok", "config.toml")
1600
+ },
1601
+ // Antigravity: ~/.gemini/antigravity-cli
1602
+ {
1603
+ name: "Antigravity",
1604
+ id: "antigravity",
1605
+ detected: checkExists(path4.join(home, ".gemini", "antigravity-cli")),
1606
+ configPath: path4.join(home, ".gemini", "antigravity-cli", "mcp_config.json")
1607
+ },
1585
1608
  // Manual (always available as fallback)
1586
1609
  {
1587
1610
  name: "Manual",
@@ -1598,6 +1621,19 @@ function detectIDEs(home = os3.homedir()) {
1598
1621
  } catch {
1599
1622
  }
1600
1623
  }
1624
+ for (const [id, bin] of [
1625
+ ["grok", "grok"],
1626
+ ["antigravity", "agy"]
1627
+ ]) {
1628
+ const ide = ides.find((i) => i.id === id);
1629
+ if (ide && !ide.detected) {
1630
+ try {
1631
+ execFileSync("which", [bin], { encoding: "utf-8" });
1632
+ ide.detected = true;
1633
+ } catch {
1634
+ }
1635
+ }
1636
+ }
1601
1637
  return ides;
1602
1638
  }
1603
1639
  var init_ide_detector = __esm({
@@ -1906,64 +1942,51 @@ var init_instruction_template_v3 = __esm({
1906
1942
  }
1907
1943
  });
1908
1944
 
1909
- // src/workspace/instruction-files.ts
1910
- var instruction_files_exports = {};
1911
- __export(instruction_files_exports, {
1912
- installUniversalCommand: () => installUniversalCommand,
1913
- slugify: () => slugify,
1914
- upsertRootAgentsBlock: () => upsertRootAgentsBlock,
1915
- writeInstructionFiles: () => writeInstructionFiles
1945
+ // src/workspace/runtime-registry.ts
1946
+ var runtime_registry_exports = {};
1947
+ __export(runtime_registry_exports, {
1948
+ RUNTIMES: () => RUNTIMES,
1949
+ detectedSlashHints: () => detectedSlashHints,
1950
+ upsertRootAgentsBlock: () => upsertRootAgentsBlock
1916
1951
  });
1952
+ import { execFileSync as execFileSync2 } from "node:child_process";
1917
1953
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
1918
- import { homedir } from "node:os";
1919
1954
  import { join as join2 } from "node:path";
1920
- function slugify(title) {
1921
- return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
1955
+ function hasBinary(name) {
1956
+ try {
1957
+ execFileSync2("which", [name], { encoding: "utf-8", stdio: "pipe" });
1958
+ return true;
1959
+ } catch {
1960
+ return false;
1961
+ }
1922
1962
  }
1923
- function writeInstructionFiles(cwd, ctx, content) {
1924
- const slug = slugify(ctx.courseTitle);
1925
- const written = [];
1926
- const claudeDir = join2(cwd, ".claude", "commands");
1927
- if (!existsSync2(claudeDir)) {
1928
- mkdirSync2(claudeDir, { recursive: true });
1929
- }
1930
- const claudeFile = `tostudy-${slug}.md`;
1931
- writeFileSync2(join2(claudeDir, claudeFile), content);
1932
- written.push(`.claude/commands/${claudeFile}`);
1933
- const cursorDir = join2(cwd, ".cursor", "rules");
1934
- if (existsSync2(join2(cwd, ".cursor")) || existsSync2(cursorDir)) {
1935
- if (!existsSync2(cursorDir)) {
1936
- mkdirSync2(cursorDir, { recursive: true });
1937
- }
1938
- const mdcContent = `---
1939
- description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
1963
+ function writeFile2(absDir, fileName, body) {
1964
+ mkdirSync2(absDir, { recursive: true });
1965
+ writeFileSync2(join2(absDir, fileName), body);
1966
+ }
1967
+ function cursorMdc(title, content) {
1968
+ return `---
1969
+ description: ${title} \u2014 ToStudy Course Guide
1940
1970
  globs: ["**/*"]
1941
1971
  alwaysApply: true
1942
1972
  ---
1943
1973
 
1944
1974
  ${content}`;
1945
- const cursorFile = `tostudy-${slug}.mdc`;
1946
- writeFileSync2(join2(cursorDir, cursorFile), mdcContent);
1947
- written.push(`.cursor/rules/${cursorFile}`);
1948
- }
1949
- const courseDir = join2(cwd, ".tostudy", "courses", slug);
1950
- if (!existsSync2(courseDir)) {
1951
- mkdirSync2(courseDir, { recursive: true });
1952
- }
1953
- writeFileSync2(join2(courseDir, "AGENTS.md"), content);
1954
- written.push(`.tostudy/courses/${slug}/AGENTS.md`);
1955
- const rootAgents = upsertRootAgentsBlock(cwd, ctx);
1956
- if (rootAgents) {
1957
- written.push(rootAgents);
1958
- }
1959
- logger2.info("Generated instruction files (v3)", {
1960
- slug,
1961
- files: written
1962
- });
1963
- return written;
1975
+ }
1976
+ function antigravitySkillMd(name, description, content) {
1977
+ return `---
1978
+ name: ${name}
1979
+ description: >-
1980
+ ${description}
1981
+ ---
1982
+
1983
+ ${content}`;
1984
+ }
1985
+ function detectedSlashHints(slug, home, cwd) {
1986
+ return RUNTIMES.filter((r) => r.slashHint && r.detect(home, cwd)).map((r) => r.slashHint(slug));
1964
1987
  }
1965
1988
  function upsertRootAgentsBlock(cwd, ctx) {
1966
- const slug = slugify(ctx.courseTitle);
1989
+ const slug = slugifyTitle(ctx.courseTitle);
1967
1990
  const block = [
1968
1991
  "<!-- tostudy:start -->",
1969
1992
  "",
@@ -2007,67 +2030,198 @@ function upsertRootAgentsBlock(cwd, ctx) {
2007
2030
  return null;
2008
2031
  }
2009
2032
  }
2010
- function installUniversalCommand(platform2, cwd = process.cwd(), homeDir = homedir()) {
2011
- const content = renderUniversalInstruction();
2012
- const written = [];
2013
- if (platform2 === "claude") {
2014
- const dir = join2(cwd, ".claude", "commands");
2015
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2016
- writeFileSync2(join2(dir, "tostudy.md"), content);
2017
- written.push(".claude/commands/tostudy.md");
2018
- }
2019
- if (platform2 === "codex") {
2020
- const dir = join2(homeDir, ".codex", "prompts");
2021
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2022
- const file2 = join2(dir, "tostudy.md");
2023
- writeFileSync2(file2, content);
2024
- written.push(file2);
2025
- }
2026
- if (platform2 === "opencode") {
2027
- const dir = join2(homeDir, ".config", "opencode", "command");
2028
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2029
- const file2 = join2(dir, "tostudy.md");
2030
- writeFileSync2(
2031
- file2,
2032
- `---
2033
- description: ToStudy \u2014 AI Tutor Guide
2033
+ function slugifyTitle(title) {
2034
+ return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
2035
+ }
2036
+ var logger2, detectOpencode, detectGrok, detectAntigravity, RUNTIMES, TOSTUDY_BLOCK_PATTERN, TOSTUDY_BLOCK_PATTERN_GLOBAL;
2037
+ var init_runtime_registry = __esm({
2038
+ "src/workspace/runtime-registry.ts"() {
2039
+ "use strict";
2040
+ init_dist();
2041
+ logger2 = createLogger("cli:runtime-registry");
2042
+ detectOpencode = (home) => existsSync2(join2(home, ".opencode")) || existsSync2(join2(home, ".config", "opencode"));
2043
+ detectGrok = (home) => existsSync2(join2(home, ".grok")) || hasBinary("grok");
2044
+ detectAntigravity = (home) => existsSync2(join2(home, ".gemini", "antigravity-cli")) || hasBinary("agy");
2045
+ RUNTIMES = [
2046
+ {
2047
+ id: "claude",
2048
+ name: "Claude Code",
2049
+ detect: () => hasBinary("claude"),
2050
+ writePerCourse({ cwd, slug, content }) {
2051
+ writeFile2(join2(cwd, ".claude", "commands"), `tostudy-${slug}.md`, content);
2052
+ return [`.claude/commands/tostudy-${slug}.md`];
2053
+ },
2054
+ writeUniversal({ cwd, content }) {
2055
+ writeFile2(join2(cwd, ".claude", "commands"), "tostudy.md", content);
2056
+ return [".claude/commands/tostudy.md"];
2057
+ },
2058
+ slashHint: (slug) => `No Claude Code, digite: /tostudy-${slug}`
2059
+ },
2060
+ {
2061
+ id: "cursor",
2062
+ name: "Cursor",
2063
+ detect: (home, cwd) => existsSync2(join2(home, ".cursor")) || existsSync2(join2(cwd, ".cursor")),
2064
+ writePerCourse({ cwd, slug, ctx, content, force }) {
2065
+ if (!force && !existsSync2(join2(cwd, ".cursor"))) return [];
2066
+ writeFile2(
2067
+ join2(cwd, ".cursor", "rules"),
2068
+ `tostudy-${slug}.mdc`,
2069
+ cursorMdc(ctx.courseTitle, content)
2070
+ );
2071
+ return [`.cursor/rules/tostudy-${slug}.mdc`];
2072
+ },
2073
+ writeUniversal({ cwd, content }) {
2074
+ writeFile2(join2(cwd, ".cursor", "rules"), "tostudy.mdc", cursorMdc("ToStudy", content));
2075
+ return [".cursor/rules/tostudy.mdc"];
2076
+ }
2077
+ },
2078
+ {
2079
+ id: "codex",
2080
+ name: "Codex",
2081
+ detect: (home) => existsSync2(join2(home, ".codex")),
2082
+ // No writePerCourse: Codex has no project-level command channel — the
2083
+ // root AGENTS.md managed block (generic entry) is its per-course path.
2084
+ writeUniversal({ home, content }) {
2085
+ const file2 = join2(home, ".codex", "prompts", "tostudy.md");
2086
+ writeFile2(join2(home, ".codex", "prompts"), "tostudy.md", content);
2087
+ return [file2];
2088
+ }
2089
+ },
2090
+ {
2091
+ id: "opencode",
2092
+ name: "OpenCode",
2093
+ detect: (home) => detectOpencode(home),
2094
+ writePerCourse({ cwd, home, slug, ctx, content, force }) {
2095
+ if (!force && !detectOpencode(home)) return [];
2096
+ const body = `---
2097
+ description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
2034
2098
  ---
2035
2099
 
2036
- ${content}`
2037
- );
2038
- written.push(file2);
2039
- }
2040
- if (platform2 === "cursor") {
2041
- const dir = join2(cwd, ".cursor", "rules");
2042
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2043
- const mdcContent = `---
2100
+ ${content}`;
2101
+ writeFile2(join2(cwd, ".opencode", "command"), `tostudy-${slug}.md`, body);
2102
+ return [`.opencode/command/tostudy-${slug}.md`];
2103
+ },
2104
+ writeUniversal({ home, content }) {
2105
+ const dir = join2(home, ".config", "opencode", "command");
2106
+ const body = `---
2044
2107
  description: ToStudy \u2014 AI Tutor Guide
2045
- globs: ["**/*"]
2046
- alwaysApply: true
2047
2108
  ---
2048
2109
 
2049
2110
  ${content}`;
2050
- writeFileSync2(join2(dir, "tostudy.mdc"), mdcContent);
2051
- written.push(".cursor/rules/tostudy.mdc");
2052
- }
2053
- if (platform2 === "generic") {
2054
- const dir = join2(cwd, ".tostudy");
2055
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
2056
- writeFileSync2(join2(dir, "AGENTS.md"), content);
2057
- written.push(".tostudy/AGENTS.md");
2111
+ writeFile2(dir, "tostudy.md", body);
2112
+ return [join2(dir, "tostudy.md")];
2113
+ },
2114
+ slashHint: (slug) => `No OpenCode, digite: /tostudy-${slug}`
2115
+ },
2116
+ {
2117
+ id: "grok",
2118
+ name: "Grok CLI",
2119
+ detect: (home) => detectGrok(home),
2120
+ writePerCourse({ cwd, home, slug, content, force }) {
2121
+ if (!force && !detectGrok(home)) return [];
2122
+ writeFile2(join2(cwd, ".grok", "commands"), `tostudy-${slug}.md`, content);
2123
+ return [`.grok/commands/tostudy-${slug}.md`];
2124
+ },
2125
+ writeUniversal({ home, content }) {
2126
+ const dir = join2(home, ".grok", "commands");
2127
+ writeFile2(dir, "tostudy.md", content);
2128
+ return [join2(dir, "tostudy.md")];
2129
+ },
2130
+ slashHint: (slug) => `No Grok, digite: /tostudy-${slug}`
2131
+ },
2132
+ {
2133
+ id: "antigravity",
2134
+ name: "Antigravity",
2135
+ detect: (home) => detectAntigravity(home),
2136
+ writePerCourse({ cwd, home, slug, ctx, content, force }) {
2137
+ if (!force && !detectAntigravity(home)) return [];
2138
+ writeFile2(
2139
+ join2(cwd, ".agents", "skills", `tostudy-${slug}`),
2140
+ "SKILL.md",
2141
+ antigravitySkillMd(
2142
+ `tostudy-${slug}`,
2143
+ `${ctx.courseTitle} \u2014 ToStudy Course Guide. Use when the student asks to study, continue the course, or runs tostudy commands.`,
2144
+ content
2145
+ )
2146
+ );
2147
+ return [`.agents/skills/tostudy-${slug}/SKILL.md`];
2148
+ },
2149
+ writeUniversal({ cwd, content }) {
2150
+ writeFile2(
2151
+ join2(cwd, ".agents", "skills", "tostudy"),
2152
+ "SKILL.md",
2153
+ antigravitySkillMd(
2154
+ "tostudy",
2155
+ "ToStudy \u2014 AI Tutor Guide. Use when the student asks to study or runs tostudy commands.",
2156
+ content
2157
+ )
2158
+ );
2159
+ return [".agents/skills/tostudy/SKILL.md"];
2160
+ },
2161
+ slashHint: (slug) => `No Antigravity, a skill tostudy-${slug} ativa automaticamente`
2162
+ },
2163
+ {
2164
+ id: "generic",
2165
+ name: "Generic (AGENTS.md)",
2166
+ // Windsurf is the remaining consumer of the generic universal install.
2167
+ detect: (home) => existsSync2(join2(home, ".codeium", "windsurf")),
2168
+ writePerCourse({ cwd, slug, ctx, content }) {
2169
+ writeFile2(join2(cwd, ".tostudy", "courses", slug), "AGENTS.md", content);
2170
+ const written = [`.tostudy/courses/${slug}/AGENTS.md`];
2171
+ const rootAgents = upsertRootAgentsBlock(cwd, ctx);
2172
+ if (rootAgents) written.push(rootAgents);
2173
+ return written;
2174
+ },
2175
+ writeUniversal({ cwd, content }) {
2176
+ writeFile2(join2(cwd, ".tostudy"), "AGENTS.md", content);
2177
+ return [".tostudy/AGENTS.md"];
2178
+ }
2179
+ }
2180
+ ];
2181
+ TOSTUDY_BLOCK_PATTERN = /<!-- tostudy:start -->[\s\S]*?<!-- tostudy:end -->/;
2182
+ TOSTUDY_BLOCK_PATTERN_GLOBAL = /<!-- tostudy:start -->[\s\S]*?<!-- tostudy:end -->/g;
2058
2183
  }
2059
- logger2.info("Installed universal /tostudy command", { platform: platform2, files: written });
2184
+ });
2185
+
2186
+ // src/workspace/instruction-files.ts
2187
+ var instruction_files_exports = {};
2188
+ __export(instruction_files_exports, {
2189
+ installUniversalCommand: () => installUniversalCommand,
2190
+ slugify: () => slugify,
2191
+ upsertRootAgentsBlock: () => upsertRootAgentsBlock,
2192
+ writeInstructionFiles: () => writeInstructionFiles
2193
+ });
2194
+ import { homedir } from "node:os";
2195
+ function slugify(title) {
2196
+ return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
2197
+ }
2198
+ function writeInstructionFiles(cwd, ctx, content, opts = {}) {
2199
+ const home = opts.home ?? homedir();
2200
+ const force = opts.allRuntimes ?? false;
2201
+ const slug = slugify(ctx.courseTitle);
2202
+ const written = RUNTIMES.flatMap(
2203
+ (r) => r.writePerCourse?.({ cwd, home, slug, ctx, content, force }) ?? []
2204
+ );
2205
+ logger3.info("Generated instruction files (v3)", { slug, files: written });
2060
2206
  return written;
2061
2207
  }
2062
- var logger2, TOSTUDY_BLOCK_PATTERN, TOSTUDY_BLOCK_PATTERN_GLOBAL;
2208
+ function installUniversalCommand(platform2, cwd = process.cwd(), homeDir = homedir()) {
2209
+ const entry = RUNTIMES.find((r) => r.id === platform2);
2210
+ if (!entry?.writeUniversal) return [];
2211
+ const content = renderUniversalInstruction();
2212
+ const written = entry.writeUniversal({ cwd, home: homeDir, content });
2213
+ logger3.info("Installed universal /tostudy command", { platform: platform2, files: written });
2214
+ return written;
2215
+ }
2216
+ var logger3;
2063
2217
  var init_instruction_files = __esm({
2064
2218
  "src/workspace/instruction-files.ts"() {
2065
2219
  "use strict";
2066
2220
  init_dist();
2067
2221
  init_instruction_template_v3();
2068
- logger2 = createLogger("cli:instruction-files");
2069
- TOSTUDY_BLOCK_PATTERN = /<!-- tostudy:start -->[\s\S]*?<!-- tostudy:end -->/;
2070
- TOSTUDY_BLOCK_PATTERN_GLOBAL = /<!-- tostudy:start -->[\s\S]*?<!-- tostudy:end -->/g;
2222
+ init_runtime_registry();
2223
+ init_runtime_registry();
2224
+ logger3 = createLogger("cli:instruction-files");
2071
2225
  }
2072
2226
  });
2073
2227
 
@@ -2140,16 +2294,7 @@ async function loginWithMagicLink(magicLinkUrl, apiUrl) {
2140
2294
  await persistSession(data, apiUrl);
2141
2295
  return data;
2142
2296
  }
2143
- function ideToPlatform(ideName) {
2144
- const lower = ideName.toLowerCase();
2145
- if (lower.includes("claude")) return "claude";
2146
- if (lower.includes("codex")) return "codex";
2147
- if (lower.includes("cursor")) return "cursor";
2148
- if (lower.includes("opencode")) return "opencode";
2149
- if (lower.includes("windsurf") || lower.includes("antigravity")) return "generic";
2150
- return null;
2151
- }
2152
- var logger3, DEFAULT_API_URL, PORT, loginCommand;
2297
+ var logger4, DEFAULT_API_URL, PORT, loginCommand;
2153
2298
  var init_login = __esm({
2154
2299
  "src/commands/login.ts"() {
2155
2300
  "use strict";
@@ -2159,7 +2304,7 @@ var init_login = __esm({
2159
2304
  init_oauth_server();
2160
2305
  init_session_store();
2161
2306
  init_formatter();
2162
- logger3 = createLogger("cli:login");
2307
+ logger4 = createLogger("cli:login");
2163
2308
  DEFAULT_API_URL = "https://tostudy.ai";
2164
2309
  PORT = 9876;
2165
2310
  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) => {
@@ -2224,6 +2369,8 @@ var init_login = __esm({
2224
2369
  try {
2225
2370
  const { detectIDEs: detectIDEs2 } = await Promise.resolve().then(() => (init_ide_detector(), ide_detector_exports));
2226
2371
  const { installUniversalCommand: installUniversalCommand2 } = await Promise.resolve().then(() => (init_instruction_files(), instruction_files_exports));
2372
+ const { RUNTIMES: RUNTIMES2 } = await Promise.resolve().then(() => (init_runtime_registry(), runtime_registry_exports));
2373
+ const { homedir: homedir2 } = await import("node:os");
2227
2374
  const ides = detectIDEs2();
2228
2375
  const detected = ides.filter((ide) => ide.detected);
2229
2376
  if (detected.length > 0) {
@@ -2231,27 +2378,26 @@ var init_login = __esm({
2231
2378
  for (const ide of detected) {
2232
2379
  console.log(` \u2713 ${ide.name} detectado`);
2233
2380
  }
2234
- const cwd = process.cwd();
2235
- const installedFiles = [];
2236
- for (const ide of detected) {
2237
- const platform2 = ideToPlatform(ide.name);
2238
- if (platform2) {
2239
- const files = installUniversalCommand2(platform2, cwd);
2240
- installedFiles.push(...files);
2241
- }
2381
+ }
2382
+ const cwd = process.cwd();
2383
+ const home = homedir2();
2384
+ const installedFiles = [];
2385
+ for (const runtime of RUNTIMES2) {
2386
+ if (runtime.writeUniversal && runtime.detect(home, cwd)) {
2387
+ installedFiles.push(...installUniversalCommand2(runtime.id, cwd));
2242
2388
  }
2243
- if (installedFiles.length > 0) {
2244
- console.log("");
2245
- for (const file2 of [...new Set(installedFiles)]) {
2246
- console.log(` \u2713 Instalado: ${file2}`);
2247
- }
2389
+ }
2390
+ if (installedFiles.length > 0) {
2391
+ console.log("");
2392
+ for (const file2 of [...new Set(installedFiles)]) {
2393
+ console.log(` \u2713 Instalado: ${file2}`);
2248
2394
  }
2249
2395
  }
2250
2396
  } catch {
2251
2397
  }
2252
2398
  try {
2253
2399
  const data = createHttpProvider(apiUrl, token2);
2254
- const courses3 = await listCourses({ userId }, { data, logger: logger3 });
2400
+ const courses3 = await listCourses({ userId }, { data, logger: logger4 });
2255
2401
  if (courses3.length > 0) {
2256
2402
  console.log(`
2257
2403
  \u2713 ${courses3.length} curso(s) matriculado(s)`);
@@ -2775,16 +2921,16 @@ var init_setup = __esm({
2775
2921
  });
2776
2922
 
2777
2923
  // src/installer/node-detector.ts
2778
- import { execFileSync as execFileSync2 } from "node:child_process";
2924
+ import { execFileSync as execFileSync3 } from "node:child_process";
2779
2925
  function detectNode() {
2780
2926
  try {
2781
- const version3 = execFileSync2("node", ["--version"], {
2927
+ const version3 = execFileSync3("node", ["--version"], {
2782
2928
  encoding: "utf-8"
2783
2929
  }).trim();
2784
2930
  let nodePath = null;
2785
2931
  try {
2786
2932
  const whichCmd = process.platform === "win32" ? "where" : "which";
2787
- nodePath = execFileSync2(whichCmd, ["node"], { encoding: "utf-8" }).trim();
2933
+ nodePath = execFileSync3(whichCmd, ["node"], { encoding: "utf-8" }).trim();
2788
2934
  } catch {
2789
2935
  }
2790
2936
  const major = parseInt(version3.replace("v", ""), 10);
@@ -2804,7 +2950,7 @@ var CLI_VERSION;
2804
2950
  var init_version = __esm({
2805
2951
  "src/version.ts"() {
2806
2952
  "use strict";
2807
- CLI_VERSION = true ? "0.14.0" : "0.7.1";
2953
+ CLI_VERSION = true ? "0.15.0" : "0.7.1";
2808
2954
  }
2809
2955
  });
2810
2956
 
@@ -2995,14 +3141,14 @@ var init_doctor = __esm({
2995
3141
  git: null
2996
3142
  };
2997
3143
  try {
2998
- const { execFileSync: execFileSync3 } = await import("node:child_process");
2999
- envChecks["pnpm"] = execFileSync3("pnpm", ["--version"], { encoding: "utf-8" }).trim();
3144
+ const { execFileSync: execFileSync4 } = await import("node:child_process");
3145
+ envChecks["pnpm"] = execFileSync4("pnpm", ["--version"], { encoding: "utf-8" }).trim();
3000
3146
  } catch {
3001
3147
  envChecks["pnpm"] = null;
3002
3148
  }
3003
3149
  try {
3004
- const { execFileSync: execFileSync3 } = await import("node:child_process");
3005
- envChecks["git"] = execFileSync3("git", ["--version"], { encoding: "utf-8" }).trim().replace("git version ", "");
3150
+ const { execFileSync: execFileSync4 } = await import("node:child_process");
3151
+ envChecks["git"] = execFileSync4("git", ["--version"], { encoding: "utf-8" }).trim().replace("git version ", "");
3006
3152
  } catch {
3007
3153
  envChecks["git"] = null;
3008
3154
  }
@@ -3181,7 +3327,7 @@ var init_doctor = __esm({
3181
3327
 
3182
3328
  // src/commands/courses.ts
3183
3329
  import { Command as Command5 } from "commander";
3184
- var logger4, coursesCommand;
3330
+ var logger5, coursesCommand;
3185
3331
  var init_courses2 = __esm({
3186
3332
  "src/commands/courses.ts"() {
3187
3333
  "use strict";
@@ -3190,12 +3336,12 @@ var init_courses2 = __esm({
3190
3336
  init_http();
3191
3337
  init_guards();
3192
3338
  init_formatter();
3193
- logger4 = createLogger("cli:courses");
3339
+ logger5 = createLogger("cli:courses");
3194
3340
  coursesCommand = new Command5("courses").description("List your enrolled courses with progress").option("--json", "Output structured JSON").option("--mine", "List only your own courses, including drafts").action(async (opts) => {
3195
3341
  try {
3196
3342
  const session = await requireSession();
3197
3343
  const data = createHttpProvider(session.apiUrl, session.token);
3198
- const deps = { data, logger: logger4 };
3344
+ const deps = { data, logger: logger5 };
3199
3345
  let coursesToList;
3200
3346
  if (opts.mine) {
3201
3347
  coursesToList = await data.courses.listCreatorCourses({
@@ -3527,7 +3673,7 @@ async function resolveAndGenerate(input2, options = {}, context) {
3527
3673
  });
3528
3674
  }
3529
3675
  } catch (err) {
3530
- logger5.warn("T0 fetch failed \u2014 rendering without persona", { err });
3676
+ logger6.warn("T0 fetch failed \u2014 rendering without persona", { err });
3531
3677
  tutorPersona = null;
3532
3678
  }
3533
3679
  }
@@ -3551,7 +3697,7 @@ async function resolveAndGenerate(input2, options = {}, context) {
3551
3697
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
3552
3698
  });
3553
3699
  } catch (err) {
3554
- logger5.warn("T1 fetch failed \u2014 rendering without brief", { err });
3700
+ logger6.warn("T1 fetch failed \u2014 rendering without brief", { err });
3555
3701
  }
3556
3702
  }
3557
3703
  if (!studentBrief && options.collectT1Interactive) {
@@ -3571,7 +3717,7 @@ async function resolveAndGenerate(input2, options = {}, context) {
3571
3717
  });
3572
3718
  bootstrapped = "t1";
3573
3719
  } catch (err) {
3574
- logger5.warn("T1 bootstrap failed", { err });
3720
+ logger6.warn("T1 bootstrap failed", { err });
3575
3721
  }
3576
3722
  }
3577
3723
  let enrollmentProfile = null;
@@ -3585,7 +3731,7 @@ async function resolveAndGenerate(input2, options = {}, context) {
3585
3731
  enrollmentProfile = onboarding.learnerProfile;
3586
3732
  }
3587
3733
  } catch (err) {
3588
- logger5.warn("T2 fetch failed \u2014 rendering without enrollment profile", { err });
3734
+ logger6.warn("T2 fetch failed \u2014 rendering without enrollment profile", { err });
3589
3735
  }
3590
3736
  const content = renderCourseInstruction({
3591
3737
  course: {
@@ -3609,7 +3755,7 @@ async function resolveAndGenerate(input2, options = {}, context) {
3609
3755
  courseTitle: courseMeta.courseTitle
3610
3756
  });
3611
3757
  } catch (err) {
3612
- logger5.warn("Failed to write workspace marker", { err });
3758
+ logger6.warn("Failed to write workspace marker", { err });
3613
3759
  }
3614
3760
  if (markerPath) {
3615
3761
  try {
@@ -3619,7 +3765,7 @@ async function resolveAndGenerate(input2, options = {}, context) {
3619
3765
  lastInitCourseId: input2.courseId
3620
3766
  });
3621
3767
  } catch (err) {
3622
- logger5.warn("Failed to update workspace state v2 fields", {
3768
+ logger6.warn("Failed to update workspace state v2 fields", {
3623
3769
  error: err instanceof Error ? err.message : String(err)
3624
3770
  });
3625
3771
  }
@@ -3634,7 +3780,7 @@ async function resolveAndGenerate(input2, options = {}, context) {
3634
3780
  renderedInstruction: content
3635
3781
  };
3636
3782
  }
3637
- var logger5, defaultDeps2;
3783
+ var logger6, defaultDeps2;
3638
3784
  var init_instruction_pipeline = __esm({
3639
3785
  "src/instruction-pipeline.ts"() {
3640
3786
  "use strict";
@@ -3649,7 +3795,7 @@ var init_instruction_pipeline = __esm({
3649
3795
  init_api3();
3650
3796
  init_session_store();
3651
3797
  init_instruction_template_v3();
3652
- logger5 = createLogger("cli:instruction-pipeline");
3798
+ logger6 = createLogger("cli:instruction-pipeline");
3653
3799
  defaultDeps2 = {
3654
3800
  fetchTutorPersona: fetchTutorPersonality,
3655
3801
  fetchLearnerBrief,
@@ -3672,7 +3818,7 @@ var init_instruction_pipeline = __esm({
3672
3818
  async function fetchCourseMetaViaHttp(input2) {
3673
3819
  const { session, courseId, enrollmentId } = input2;
3674
3820
  const data = createHttpProvider(session.apiUrl, session.token);
3675
- const coreDeps = { data, logger: logger6 };
3821
+ const coreDeps = { data, logger: logger7 };
3676
3822
  const detail = await selectCourse({ userId: session.userId, courseId }, coreDeps);
3677
3823
  let currentModuleTitle;
3678
3824
  let currentLessonTitle;
@@ -3681,7 +3827,7 @@ async function fetchCourseMetaViaHttp(input2) {
3681
3827
  currentModuleTitle = progress3.currentModule?.title;
3682
3828
  currentLessonTitle = progress3.currentLesson?.title;
3683
3829
  } catch (err) {
3684
- logger6.debug("getProgress failed \u2014 falling back to basic course meta", {
3830
+ logger7.debug("getProgress failed \u2014 falling back to basic course meta", {
3685
3831
  error: err instanceof Error ? err.message : String(err)
3686
3832
  });
3687
3833
  }
@@ -3695,7 +3841,7 @@ async function fetchCourseMetaViaHttp(input2) {
3695
3841
  courseLevel = match.level;
3696
3842
  }
3697
3843
  } catch (err) {
3698
- logger6.debug("listCourses failed \u2014 skipping tags/level", {
3844
+ logger7.debug("listCourses failed \u2014 skipping tags/level", {
3699
3845
  error: err instanceof Error ? err.message : String(err)
3700
3846
  });
3701
3847
  }
@@ -3715,7 +3861,7 @@ async function fetchCourseMetaViaHttp(input2) {
3715
3861
  }
3716
3862
  };
3717
3863
  }
3718
- async function writeFilesAdapter(cwd, courseMeta, content) {
3864
+ async function writeFilesAdapter(cwd, courseMeta, content, opts = {}) {
3719
3865
  return writeInstructionFiles(
3720
3866
  cwd,
3721
3867
  {
@@ -3728,17 +3874,18 @@ async function writeFilesAdapter(cwd, courseMeta, content) {
3728
3874
  currentLessonTitle: courseMeta.progress.currentLessonTitle,
3729
3875
  courseDescription: courseMeta.courseDescription
3730
3876
  },
3731
- content
3877
+ content,
3878
+ { allRuntimes: opts.allRuntimes }
3732
3879
  );
3733
3880
  }
3734
- function buildPipelineDeps() {
3881
+ function buildPipelineDeps(opts = {}) {
3735
3882
  return {
3736
3883
  ...defaultDeps2,
3737
3884
  fetchCourseMeta: fetchCourseMetaViaHttp,
3738
- writeFiles: writeFilesAdapter
3885
+ writeFiles: (cwd, courseMeta, content) => writeFilesAdapter(cwd, courseMeta, content, opts)
3739
3886
  };
3740
3887
  }
3741
- var logger6;
3888
+ var logger7;
3742
3889
  var init_pipeline_deps = __esm({
3743
3890
  "src/commands/_shared/pipeline-deps.ts"() {
3744
3891
  "use strict";
@@ -3747,14 +3894,14 @@ var init_pipeline_deps = __esm({
3747
3894
  init_http();
3748
3895
  init_instruction_pipeline();
3749
3896
  init_instruction_files();
3750
- logger6 = createLogger("cli:pipeline-deps");
3897
+ logger7 = createLogger("cli:pipeline-deps");
3751
3898
  }
3752
3899
  });
3753
3900
 
3754
3901
  // src/commands/select.ts
3755
3902
  import { Command as Command6 } from "commander";
3756
3903
  import os7 from "node:os";
3757
- var logger7, selectCommand;
3904
+ var logger8, selectCommand;
3758
3905
  var init_select = __esm({
3759
3906
  "src/commands/select.ts"() {
3760
3907
  "use strict";
@@ -3764,17 +3911,18 @@ var init_select = __esm({
3764
3911
  init_guards();
3765
3912
  init_workspace_marker();
3766
3913
  init_resolve();
3914
+ init_runtime_registry();
3767
3915
  init_formatter();
3768
3916
  init_errors();
3769
3917
  init_status();
3770
3918
  init_instruction_pipeline();
3771
3919
  init_pipeline_deps();
3772
- logger7 = createLogger("cli:select");
3920
+ logger8 = createLogger("cli:select");
3773
3921
  selectCommand = new Command6("select").description("Activate a course by ID or list index number").argument("<course>", "Course ID (UUID) or index number from `tostudy courses`").option("--json", "Output structured JSON").action(async (course, opts) => {
3774
3922
  try {
3775
3923
  const session = await requireSession();
3776
3924
  const data = createHttpProvider(session.apiUrl, session.token);
3777
- const deps = { data, logger: logger7 };
3925
+ const deps = { data, logger: logger8 };
3778
3926
  const courses3 = await listCourses({ userId: session.userId }, deps);
3779
3927
  let courseId = course;
3780
3928
  let enrollmentId = "";
@@ -3813,7 +3961,7 @@ var init_select = __esm({
3813
3961
  { cwd: process.cwd(), deps: buildPipelineDeps() }
3814
3962
  );
3815
3963
  } catch (err) {
3816
- logger7.warn("Failed to run instruction pipeline", {
3964
+ logger8.warn("Failed to run instruction pipeline", {
3817
3965
  error: err instanceof Error ? err.message : String(err)
3818
3966
  });
3819
3967
  }
@@ -3864,12 +4012,18 @@ var init_select = __esm({
3864
4012
  if (pipelineResult && !pipelineResult.usedT1) {
3865
4013
  lines.push(" (sem brief base \u2014 rode `tostudy init` para configurar)");
3866
4014
  }
3867
- lines.push(
3868
- "",
3869
- "\u2192 Abra sua plataforma (claude, codex, cursor...)",
3870
- `\u2192 No Claude Code, digite: ${slashCmd}`,
3871
- "\u2192 Em outras plataformas: tostudy init"
3872
- );
4015
+ lines.push("", "\u2192 Abra sua plataforma (claude, codex, cursor...)");
4016
+ if (courseSlug2) {
4017
+ const hints = detectedSlashHints(courseSlug2, home, cwd);
4018
+ if (hints.length > 0) {
4019
+ lines.push(...hints.map((h) => `\u2192 ${h}`));
4020
+ } else {
4021
+ lines.push(`\u2192 No Claude Code, digite: ${slashCmd}`);
4022
+ }
4023
+ } else {
4024
+ lines.push(`\u2192 No Claude Code, digite: ${slashCmd}`);
4025
+ }
4026
+ lines.push("\u2192 Em outras plataformas: tostudy init");
3873
4027
  output(lines.join("\n"), { json: false });
3874
4028
  }
3875
4029
  } catch (err) {
@@ -3886,7 +4040,7 @@ var init_select = __esm({
3886
4040
 
3887
4041
  // src/commands/progress.ts
3888
4042
  import { Command as Command7 } from "commander";
3889
- var logger8, progressCommand;
4043
+ var logger9, progressCommand;
3890
4044
  var init_progress = __esm({
3891
4045
  "src/commands/progress.ts"() {
3892
4046
  "use strict";
@@ -3897,7 +4051,7 @@ var init_progress = __esm({
3897
4051
  init_course_state();
3898
4052
  init_formatter();
3899
4053
  init_errors();
3900
- logger8 = createLogger("cli:progress");
4054
+ logger9 = createLogger("cli:progress");
3901
4055
  progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
3902
4056
  try {
3903
4057
  const session = await requireSession();
@@ -3905,7 +4059,7 @@ var init_progress = __esm({
3905
4059
  const driftWarning = await checkCourseDrift();
3906
4060
  if (driftWarning) process.stderr.write(driftWarning + "\n");
3907
4061
  const data = createHttpProvider(session.apiUrl, session.token);
3908
- const deps = { data, logger: logger8 };
4062
+ const deps = { data, logger: logger9 };
3909
4063
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
3910
4064
  if (opts.json) {
3911
4065
  output(progress3, { json: true });
@@ -14279,12 +14433,12 @@ var init_session2 = __esm({
14279
14433
  init_tracing();
14280
14434
  init_utils();
14281
14435
  PostgresJsPreparedQuery = class extends PgPreparedQuery {
14282
- constructor(client, queryString, params, logger34, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14436
+ constructor(client, queryString, params, logger35, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14283
14437
  super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
14284
14438
  this.client = client;
14285
14439
  this.queryString = queryString;
14286
14440
  this.params = params;
14287
- this.logger = logger34;
14441
+ this.logger = logger35;
14288
14442
  this.fields = fields;
14289
14443
  this._isResponseInArrayMode = _isResponseInArrayMode;
14290
14444
  this.customResultMapper = customResultMapper;
@@ -14425,11 +14579,11 @@ function construct(client, config2 = {}) {
14425
14579
  client.options.serializers["114"] = transparentParser;
14426
14580
  client.options.serializers["3802"] = transparentParser;
14427
14581
  const dialect = new PgDialect({ casing: config2.casing });
14428
- let logger34;
14582
+ let logger35;
14429
14583
  if (config2.logger === true) {
14430
- logger34 = new DefaultLogger();
14584
+ logger35 = new DefaultLogger();
14431
14585
  } else if (config2.logger !== false) {
14432
- logger34 = config2.logger;
14586
+ logger35 = config2.logger;
14433
14587
  }
14434
14588
  let schema;
14435
14589
  if (config2.schema) {
@@ -14443,7 +14597,7 @@ function construct(client, config2 = {}) {
14443
14597
  tableNamesMap: tablesConfig.tableNamesMap
14444
14598
  };
14445
14599
  }
14446
- const session = new PostgresJsSession(client, dialect, schema, { logger: logger34, cache: config2.cache });
14600
+ const session = new PostgresJsSession(client, dialect, schema, { logger: logger35, cache: config2.cache });
14447
14601
  const db2 = new PostgresJsDatabase(dialect, session, schema);
14448
14602
  db2.$client = client;
14449
14603
  db2.$cache = config2.cache;
@@ -29169,7 +29323,7 @@ var init_user_preferences = __esm({
29169
29323
  });
29170
29324
 
29171
29325
  // ../../packages/database/src/schema/mcp.ts
29172
- var mcpSessions, syncLogs, platformConfig;
29326
+ var mcpSessions, syncLogs;
29173
29327
  var init_mcp = __esm({
29174
29328
  "../../packages/database/src/schema/mcp.ts"() {
29175
29329
  "use strict";
@@ -29232,11 +29386,6 @@ var init_mcp = __esm({
29232
29386
  syncedAtIdx: index("idx_sync_logs_synced_at").on(table.syncedAt)
29233
29387
  })
29234
29388
  );
29235
- platformConfig = pgTable("platform_config", {
29236
- key: varchar("key", { length: 50 }).primaryKey(),
29237
- value: text("value").notNull(),
29238
- updatedAt: timestamp("updated_at").defaultNow().notNull()
29239
- });
29240
29389
  }
29241
29390
  });
29242
29391
 
@@ -29570,6 +29719,13 @@ var init_tutor_feedback = __esm({
29570
29719
  }
29571
29720
  });
29572
29721
 
29722
+ // ../../packages/types/src/lesson-review.ts
29723
+ var init_lesson_review = __esm({
29724
+ "../../packages/types/src/lesson-review.ts"() {
29725
+ "use strict";
29726
+ }
29727
+ });
29728
+
29573
29729
  // ../../packages/types/src/errors.ts
29574
29730
  var init_errors6 = __esm({
29575
29731
  "../../packages/types/src/errors.ts"() {
@@ -29601,7 +29757,8 @@ var init_credit_payment_enums = __esm({
29601
29757
  // course_sale_credit (legacy Stripe 20/85) so reconciliation/clawback never
29602
29758
  // conflate the two funding models.
29603
29759
  "course_enrollment_debit",
29604
- "course_enrollment_credit"
29760
+ "course_enrollment_credit",
29761
+ "referral_bonus"
29605
29762
  ];
29606
29763
  CREDIT_SUBSCRIPTION_STATUS_VALUES = [
29607
29764
  "active",
@@ -29621,6 +29778,13 @@ var init_credit_payment_enums = __esm({
29621
29778
  }
29622
29779
  });
29623
29780
 
29781
+ // ../../packages/types/src/referral.ts
29782
+ var init_referral = __esm({
29783
+ "../../packages/types/src/referral.ts"() {
29784
+ "use strict";
29785
+ }
29786
+ });
29787
+
29624
29788
  // ../../packages/types/src/index.ts
29625
29789
  var init_src2 = __esm({
29626
29790
  "../../packages/types/src/index.ts"() {
@@ -29634,8 +29798,10 @@ var init_src2 = __esm({
29634
29798
  init_exercise_type();
29635
29799
  init_lesson_contract();
29636
29800
  init_tutor_feedback();
29801
+ init_lesson_review();
29637
29802
  init_errors6();
29638
29803
  init_credit_payment_enums();
29804
+ init_referral();
29639
29805
  }
29640
29806
  });
29641
29807
 
@@ -30018,7 +30184,7 @@ var init_llm_analytics = __esm({
30018
30184
  // Operation type
30019
30185
  operationType: varchar("operation_type", { length: 50 }).notNull(),
30020
30186
  // Provider (references llm_provider enum from llm-pricing.ts)
30021
- provider: llmProviderEnum("provider").notNull().default("anthropic"),
30187
+ provider: llmProviderEnum("provider").notNull().default("other"),
30022
30188
  // Tracing
30023
30189
  correlationId: varchar("correlation_id", { length: 64 }),
30024
30190
  // Relationships
@@ -30038,6 +30204,11 @@ var init_llm_analytics = __esm({
30038
30204
  responseTokens: integer("response_tokens"),
30039
30205
  // Cost & Revenue
30040
30206
  costUsd: decimal("cost_usd", { precision: 10, scale: 6 }),
30207
+ // spec 2026-07-10 phase 3.3: where cost_usd came from - 'table' (pricing
30208
+ // table rate), 'gateway' (LiteLLM-reported cost fallback), 'unpriced'
30209
+ // (no rate anywhere: cost_usd=0, alertable). Nullable: rows predating the
30210
+ // marker stay null.
30211
+ costSource: varchar("cost_source", { length: 16 }),
30041
30212
  revenueUsd: decimal("revenue_usd", { precision: 10, scale: 6 }).default("0"),
30042
30213
  // Cache metrics (prompt caching)
30043
30214
  cacheCreationTokens: integer("cache_creation_tokens").default(0),
@@ -30064,6 +30235,12 @@ var init_llm_analytics = __esm({
30064
30235
  "llm_interactions_status_check",
30065
30236
  sql`${table.status} IN ('pending', 'success', 'error', 'timeout', 'abandoned')`
30066
30237
  ),
30238
+ // spec 2026-07-10 phase 3.3: cost_source vocabulary — NULL allowed for
30239
+ // rows predating the marker.
30240
+ costSourceVocabularyCheck: check(
30241
+ "llm_interactions_cost_source_check",
30242
+ sql`${table.costSource} IS NULL OR ${table.costSource} IN ('table', 'gateway', 'unpriced')`
30243
+ ),
30067
30244
  creatorIdx: index("idx_llm_interactions_creator").on(table.creatorId, table.createdAt),
30068
30245
  operationIdx: index("idx_llm_interactions_operation").on(table.operationType, table.createdAt),
30069
30246
  correlationIdx: index("idx_llm_interactions_correlation").on(table.correlationId),
@@ -30150,7 +30327,9 @@ var init_credit_wallets = __esm({
30150
30327
  // Credits-first (WS3/WS4): student debit (-), creator full-price credit (+).
30151
30328
  course_enrollment_debit: "negative",
30152
30329
  course_enrollment_credit: "positive",
30153
- adjustment: "either"
30330
+ adjustment: "either",
30331
+ // Referral program (spec 2026-07-17): both-sides fixed grant on first purchase.
30332
+ referral_bonus: "positive"
30154
30333
  };
30155
30334
  WITHDRAWABLE_LEDGER_TYPES = [
30156
30335
  "course_sale_credit",
@@ -30196,6 +30375,7 @@ var init_credit_wallets = __esm({
30196
30375
  "subscription",
30197
30376
  "package",
30198
30377
  "withdrawal",
30378
+ "referral",
30199
30379
  "backfill",
30200
30380
  "credit_transaction_migration"
30201
30381
  ];
@@ -30250,6 +30430,12 @@ var init_credit_wallets = __esm({
30250
30430
  // the PK). Raw `sql` literal (not eq()) to dodge the drizzle-kit `$N`
30251
30431
  // placeholder DDL bug — see packages/database/AGENTS.md.
30252
30432
  courseSaleCreditEnrollmentUnique: uniqueIndex("cwt_course_sale_credit_enrollment_unique").on(table.referenceId, table.referenceType, table.type).where(sql`${table.type} = 'course_sale_credit' AND ${table.referenceType} = 'enrollment'`),
30433
+ cwtPurchasePaymentIntentUnique: uniqueIndex("cwt_purchase_payment_intent_unique").on(sql`(${table.metadata}->>'stripe_payment_intent_id')`).where(sql`${table.type} = 'purchase'`),
30434
+ // Idempotency guard for subscription reloads via stripe invoice.
30435
+ // Closes the double-reload window when Stripe invoice webhook fires concurrently
30436
+ // or re-delivers. Raw `sql` literal (not eq()) to dodge the drizzle-kit `$N`
30437
+ // placeholder DDL bug — see packages/database/AGENTS.md.
30438
+ cwtSubscriptionReloadInvoiceUnique: uniqueIndex("cwt_subscription_reload_invoice_unique").on(sql`(${table.metadata}->>'stripe_invoice_id')`).where(sql`${table.type} = 'subscription_reload'`),
30253
30439
  // Idempotency guard for the AI-consumption reconciliation sweep (CHORE-credit-recon).
30254
30440
  // The sweep must not double-charge a debit the inline path already committed;
30255
30441
  // this index makes the DB the final arbiter — a second ai_consumption insert
@@ -30259,23 +30445,34 @@ var init_credit_wallets = __esm({
30259
30445
  // Raw `sql` literal (not eq()) to dodge the drizzle-kit `$N` placeholder DDL
30260
30446
  // bug (packages/database/AGENTS.md).
30261
30447
  aiConsumptionInteractionUniq: uniqueIndex("cwt_ai_consumption_interaction_uniq").on(table.interactionId).where(sql`${table.type} = 'ai_consumption' AND ${table.interactionId} IS NOT NULL`),
30262
- // Credits-first (WS3/WS4): NO partial-unique DB index for course_enrollment_debit
30263
- // / course_enrollment_credit. A partial index whose predicate references a
30264
- // freshly-added enum value cannot be created in this repo's one-transaction
30265
- // `pnpm db:migrate` (AGENTS.md): the enum literal trips check_safe_enum_use
30266
- // (enum.c) and a `::text` cast is non-IMMUTABLE so it trips CheckPredicate.
30267
- // Unlike the webhook-driven legacy paths (course_sale_credit / refund_debit),
30268
- // which need a DB race-guard against concurrent Stripe deliveries, the
30269
- // credit-debit enrollment path is a SYNCHRONOUS user mutation — idempotency is
30270
- // enforced at the application layer (EnrollWithCreditsUseCase: find-existing
30271
- // enrollment + atomic debit/credit/enrollment in one tx). If post-launch
30272
- // reconciliation surfaces a need, add the partial index in a standalone
30273
- // migration once these enum values are universally committed.
30448
+ // Credits-first ramp gate (credits-ssot-ux WS1): partial UNIQUE closes the
30449
+ // double-debit window in EnrollWithCreditsUseCase before the
30450
+ // `feature.credit_debit_enrollment_enabled` ramp. The advisory lock + re-check
30451
+ // covers the double-click case, but the DB is the final arbiter against any
30452
+ // path that bypasses the lock (retry storms, future callers); the use-case
30453
+ // catches 23505 as AlreadyEnrolled. This is the "standalone migration once
30454
+ // these enum values are universally committed" the previous note here
30455
+ // anticipated: the enum values landed in 0044 (13 migrations back, deployed
30456
+ // to prod), and a cold-boot single-transaction `pnpm db:migrate` is safe
30457
+ // because the TYPE itself is created in that same transaction (enum.c's
30458
+ // check_safe_enum_use only restricts values added to a PRE-EXISTING type).
30459
+ // Scoped to type='course_enrollment_debit' AND reference_type='enrollment' —
30460
+ // predicate-disjoint from every other partial index on this table, so the
30461
+ // `undefined`-constraint branches of the sibling 23505 detectors stay safe.
30462
+ // course_enrollment_credit needs no index of its own: it is written in the
30463
+ // SAME transaction as the debit, so the debit index aborts both. Raw `sql`
30464
+ // literal (not eq()) to dodge the drizzle-kit `$N` placeholder DDL bug —
30465
+ // see packages/database/AGENTS.md.
30466
+ courseEnrollmentDebitEnrollmentUnique: uniqueIndex(
30467
+ "cwt_course_enrollment_debit_enrollment_unique"
30468
+ ).on(table.referenceId, table.referenceType, table.type).where(
30469
+ sql`${table.type} = 'course_enrollment_debit' AND ${table.referenceType} = 'enrollment'`
30470
+ ),
30274
30471
  referenceTypeCheck: check(
30275
30472
  "cwt_reference_type_check",
30276
30473
  sql`${table.referenceType} IS NULL OR ${table.referenceType} IN (
30277
30474
  'ai_operation', 'enrollment', 'booking',
30278
- 'subscription', 'package', 'withdrawal',
30475
+ 'subscription', 'package', 'withdrawal', 'referral',
30279
30476
  'backfill', 'credit_transaction_migration'
30280
30477
  )`
30281
30478
  )
@@ -30374,7 +30571,13 @@ var init_credit_packages = __esm({
30374
30571
  (table) => ({
30375
30572
  userIdx: index("cs_user_idx").on(table.userId),
30376
30573
  stripeSubIdx: index("cs_stripe_sub_idx").on(table.stripeSubscriptionId),
30377
- lastInvoiceIdx: index("cs_last_invoice_idx").on(table.lastInvoiceId)
30574
+ lastInvoiceIdx: index("cs_last_invoice_idx").on(table.lastInvoiceId),
30575
+ // #793: one live billable row per user. createSubscription's active/past_due
30576
+ // guard checks-then-inserts without a lock; this closes the double-submit
30577
+ // race (deferred "while latent" in credit-wallet.router.ts — the rm-030
30578
+ // acquisition UI un-latents it). Raw sql in .where() — drizzle-kit
30579
+ // serializes eq() as a $N placeholder in partial-index DDL (42P02).
30580
+ activeUserUnique: uniqueIndex("cs_active_user_unique").on(table.userId).where(sql`status = 'active'`)
30378
30581
  })
30379
30582
  );
30380
30583
  }
@@ -31037,7 +31240,7 @@ var init_enrollment_channel_changes = __esm({
31037
31240
  });
31038
31241
 
31039
31242
  // ../../packages/database/migrations/schema.ts
31040
- var applicableTo, badgeCriteriaType, badgeTier, badgeType, brainstormStatus, cancellationReason, challengeType, communityPostType, courseReviewStatus, courseStatus, discountType, enrollmentStatus, funnelEventType, knowledgeProcessingStatus, knowledgeResourceType, knowledgeUrlMode, marketingContentType, mentorshipCreditStatus, mentorshipTransactionStatus, mentorshipTransactionType, moderationAction, moderationTarget, participantStatus, progressStatus, proposalStatus, refundReason, refundType, reportReason, reportStatus, resourceType, serviceType, sessionCancelledReason, sessionStatus, slaStatus, studentRole, testimonialStatus, threadStatus, transactionType, userRole, userStatus, goalType, skillLevel, pathStatus, milestoneStatus, tsvector, slashCommandAnalytics, notificationLogs, couponUsages, validationAttempts, users2, accounts2, platformConfig2, concepts2, mentorshipMessages, courseReviews2, webhookEvents, sessions2, courses2, badges2, userBadges2, challenges, userChallengeScores, leaderboardPreferences, onboardingAnalytics, creatorApplications, reviews, certificates2, userPreferences2, progress2, mentorshipTemplates, notifications, lessons2, syncLogs2, mentorshipThreads, courseReports2, mcpSessions2, transactions, enrollments2, reviewHelpfulVotes, modules2, coupons, funnelEvents, moderationLog, mentorshipServices, mentorAvailability, mentorAvailabilityExceptions, sessionWaitlist, sessionParticipants, mentorshipTransactions, mentorshipSessions, sessionResources, sessionReviews, mentorshipPackages, mentorshipCredits, refundAuditLog, sessionReviewVotes, communityPosts, communityComments, communityLikes, userFollows, sparkChatSessions, platformSettings, generationState, creatorTestimonials, marketingContent, studentNotes, userCredits, userUsageQuotas, usageTierLimits, brainstormSessions, creditTransactions, courseProposals, brainstormMessages, knowledgeResources, knowledgeChunks, verificationTokens2, studentTutorContext, tutorInteractionHistory, studentLearningPreferences, learningStreaks, dailyGoals, dailyGoalProgress, learningPaths, pathCourses, pathMilestones;
31243
+ var applicableTo, badgeCriteriaType, badgeTier, badgeType, brainstormStatus, cancellationReason, challengeType, communityPostType, courseReviewStatus, courseStatus, discountType, enrollmentStatus, funnelEventType, knowledgeProcessingStatus, knowledgeResourceType, knowledgeUrlMode, marketingContentType, mentorshipCreditStatus, mentorshipTransactionStatus, mentorshipTransactionType, moderationAction, moderationTarget, participantStatus, progressStatus, proposalStatus, refundReason, refundType, reportReason, reportStatus, resourceType, serviceType, sessionCancelledReason, sessionStatus, slaStatus, studentRole, testimonialStatus, threadStatus, transactionType, userRole, userStatus, goalType, skillLevel, pathStatus, milestoneStatus, tsvector, slashCommandAnalytics, notificationLogs, couponUsages, validationAttempts, users2, accounts2, platformConfig, concepts2, mentorshipMessages, courseReviews2, webhookEvents, sessions2, courses2, badges2, userBadges2, challenges, userChallengeScores, leaderboardPreferences, onboardingAnalytics, creatorApplications, reviews, certificates2, userPreferences2, progress2, mentorshipTemplates, notifications, lessons2, syncLogs2, mentorshipThreads, courseReports2, mcpSessions2, transactions, enrollments2, reviewHelpfulVotes, modules2, coupons, funnelEvents, moderationLog, mentorshipServices, mentorAvailability, mentorAvailabilityExceptions, sessionWaitlist, sessionParticipants, mentorshipTransactions, mentorshipSessions, sessionResources, sessionReviews, mentorshipPackages, mentorshipCredits, refundAuditLog, sessionReviewVotes, communityPosts, communityComments, communityLikes, userFollows, sparkChatSessions, platformSettings, generationState, creatorTestimonials, marketingContent, studentNotes, userCredits, userUsageQuotas, usageTierLimits, brainstormSessions, creditTransactions, courseProposals, brainstormMessages, knowledgeResources, knowledgeChunks, verificationTokens2, studentTutorContext, tutorInteractionHistory, studentLearningPreferences, learningStreaks, dailyGoals, dailyGoalProgress, learningPaths, pathCourses, pathMilestones;
31041
31244
  var init_schema2 = __esm({
31042
31245
  "../../packages/database/migrations/schema.ts"() {
31043
31246
  "use strict";
@@ -31500,7 +31703,7 @@ var init_schema2 = __esm({
31500
31703
  }).onDelete("cascade")
31501
31704
  ]
31502
31705
  );
31503
- platformConfig2 = pgTable("platform_config", {
31706
+ platformConfig = pgTable("platform_config", {
31504
31707
  key: varchar({ length: 50 }).primaryKey().notNull(),
31505
31708
  value: text().notNull(),
31506
31709
  updatedAt: timestamp("updated_at", { mode: "string" }).defaultNow().notNull()
@@ -35896,13 +36099,17 @@ var init_mentorship_credits = __esm({
35896
36099
  // Package lookup
35897
36100
  packageIdIdx: index("mentorship_credits_package_id_idx").on(table.packageId),
35898
36101
  // Stripe payment intent lookup (for webhook idempotency)
35899
- stripePaymentIntentIdIdx: index("mentorship_credits_stripe_payment_intent_idx").on(
35900
- table.stripePaymentIntentId
35901
- ),
36102
+ stripePaymentIntentIdUnique: uniqueIndex("mentorship_credits_stripe_payment_intent_unique").on(table.stripePaymentIntentId).where(sql`${table.stripePaymentIntentId} IS NOT NULL`),
35902
36103
  // ORDER BY purchasedAt DESC (credit transaction history)
35903
36104
  purchasedAtIdx: index("mentorship_credits_purchased_at_idx").on(table.purchasedAt),
35904
36105
  // FEAT-1288: refund revoke looks up course-granted credits by enrollment
35905
36106
  enrollmentIdIdx: index("mentorship_credits_enrollment_id_idx").on(table.enrollmentId),
36107
+ // GH #905 F5: one course-purchase grant per enrollment. The check-then-write
36108
+ // in GrantCourseMentoriaCreditsUseCase races on Stripe webhook re-delivery
36109
+ // (stripe_payment_intent_id is null for these rows, so the existing intent
36110
+ // unique index doesn't cover them). This partial unique index is the DB-side
36111
+ // idempotency guard; the use case catches 23505 as a no-op.
36112
+ courseEnrollmentUnique: uniqueIndex("mentorship_credits_course_enrollment_unique").on(table.enrollmentId).where(sql`${table.origin} = 'course_purchase' AND ${table.enrollmentId} IS NOT NULL`),
35906
36113
  // FEAT-1288: each origin carries its tracing FK — package rows keep a
35907
36114
  // package_id, course-purchase rows keep an enrollment_id.
35908
36115
  originConsistencyCheck: check(
@@ -35996,6 +36203,7 @@ var init_mentorship_transactions = __esm({
35996
36203
  "../../packages/database/src/schema/mentorship-transactions.ts"() {
35997
36204
  "use strict";
35998
36205
  init_pg_core();
36206
+ init_drizzle_orm();
35999
36207
  init_users();
36000
36208
  init_mentorship_sessions();
36001
36209
  init_mentorship_packages();
@@ -36096,9 +36304,9 @@ var init_mentorship_transactions = __esm({
36096
36304
  studentIdIdx: index("mentorship_transactions_student_id_idx").on(table.studentId),
36097
36305
  mentorIdIdx: index("mentorship_transactions_mentor_id_idx").on(table.mentorId),
36098
36306
  statusIdx: index("mentorship_transactions_status_idx").on(table.status),
36099
- stripePaymentIntentIdIdx: index("mentorship_transactions_stripe_payment_intent_id_idx").on(
36100
- table.stripePaymentIntentId
36101
- ),
36307
+ stripePaymentIntentIdUnique: uniqueIndex(
36308
+ "mentorship_transactions_stripe_payment_intent_id_unique"
36309
+ ).on(table.stripePaymentIntentId).where(sql`${table.stripePaymentIntentId} IS NOT NULL`),
36102
36310
  createdAtIdx: index("mentorship_transactions_created_at_idx").on(table.createdAt),
36103
36311
  // Story 19.3: Additional indexes for split and aggregation
36104
36312
  typeIdx: index("mentorship_transactions_type_idx").on(table.transactionType),
@@ -37526,6 +37734,7 @@ var init_transactions = __esm({
37526
37734
  "../../packages/database/src/schema/transactions.ts"() {
37527
37735
  "use strict";
37528
37736
  init_pg_core();
37737
+ init_drizzle_orm();
37529
37738
  init_courses3();
37530
37739
  init_users();
37531
37740
  init_enrollments();
@@ -37534,9 +37743,13 @@ var init_transactions = __esm({
37534
37743
  {
37535
37744
  id: uuid("id").defaultRandom().primaryKey(),
37536
37745
  // Stripe references
37537
- stripePaymentIntentId: varchar("stripe_payment_intent_id", { length: 255 }).notNull().unique(),
37746
+ stripePaymentIntentId: varchar("stripe_payment_intent_id", { length: 255 }).unique(),
37538
37747
  stripeTransferId: varchar("stripe_transfer_id", { length: 255 }),
37539
37748
  // Stripe Transfer ID (if separate transfer)
37749
+ // spec 2026-07-31-pix-modobank: second inbound provider. 'stripe' rows keep
37750
+ // stripe_payment_intent_id; 'modobank' rows carry the e2eid here instead.
37751
+ provider: varchar("provider", { length: 20 }).notNull().default("stripe"),
37752
+ providerPaymentId: varchar("provider_payment_id", { length: 255 }),
37540
37753
  // Foreign keys
37541
37754
  courseId: uuid("course_id").notNull().references(() => courses.id, { onDelete: "restrict" }),
37542
37755
  creatorId: uuid("creator_id").notNull().references(() => users.id, { onDelete: "restrict" }),
@@ -37608,6 +37821,56 @@ var init_transactions = __esm({
37608
37821
  table.studentId,
37609
37822
  table.status,
37610
37823
  table.createdAt
37824
+ ),
37825
+ // spec 2026-07-31-pix-modobank: dedupes per-provider payment refs; NULL
37826
+ // provider_payment_id (legacy Stripe rows, pre-webhook-write) is excluded.
37827
+ providerPaymentIdUnique: uniqueIndex("transactions_provider_payment_id_unique").on(table.provider, table.providerPaymentId).where(sql`${table.providerPaymentId} IS NOT NULL`)
37828
+ })
37829
+ );
37830
+ }
37831
+ });
37832
+
37833
+ // ../../packages/database/src/schema/pix-charges.ts
37834
+ var PIX_CHARGE_STATUSES, PIX_CHARGE_PURPOSES, pixCharges;
37835
+ var init_pix_charges = __esm({
37836
+ "../../packages/database/src/schema/pix-charges.ts"() {
37837
+ "use strict";
37838
+ init_drizzle_orm();
37839
+ init_pg_core();
37840
+ init_users();
37841
+ PIX_CHARGE_STATUSES = ["pending", "paid", "fulfilled", "expired", "refunded"];
37842
+ PIX_CHARGE_PURPOSES = ["course_sale", "credit_topup"];
37843
+ pixCharges = pgTable(
37844
+ "pix_charges",
37845
+ {
37846
+ id: uuid("id").defaultRandom().primaryKey(),
37847
+ txid: varchar("txid", { length: 35 }).notNull(),
37848
+ e2eid: varchar("e2eid", { length: 64 }),
37849
+ status: varchar("status", { length: 20 }).notNull().default("pending"),
37850
+ purpose: varchar("purpose", { length: 20 }).notNull(),
37851
+ userId: uuid("user_id").references(() => users.id, { onDelete: "restrict" }).notNull(),
37852
+ /** courseId (course_sale) or credit package id (credit_topup). */
37853
+ referenceId: uuid("reference_id"),
37854
+ amountBrlCents: integer("amount_brl_cents").notNull(),
37855
+ pixCopiaECola: text("pix_copia_e_cola").notNull(),
37856
+ /** course_sale: money breakdown replayed at fulfillment (creatorId, fees, couponId...). */
37857
+ metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
37858
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
37859
+ paidAt: timestamp("paid_at", { withTimezone: true }),
37860
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
37861
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
37862
+ },
37863
+ (table) => ({
37864
+ txidUnique: uniqueIndex("pix_charges_txid_unique").on(table.txid),
37865
+ userIdIdx: index("pix_charges_user_id_idx").on(table.userId),
37866
+ sweepIdx: index("pix_charges_sweep_idx").on(table.status, table.expiresAt),
37867
+ statusCheck: check(
37868
+ "pxc_status_check",
37869
+ sql`${table.status} IN ('pending', 'paid', 'fulfilled', 'expired', 'refunded')`
37870
+ ),
37871
+ purposeCheck: check(
37872
+ "pxc_purpose_check",
37873
+ sql`${table.purpose} IN ('course_sale', 'credit_topup')`
37611
37874
  )
37612
37875
  })
37613
37876
  );
@@ -38527,140 +38790,6 @@ var init_study_index = __esm({
38527
38790
  }
38528
38791
  });
38529
38792
 
38530
- // ../../packages/database/src/schema/platform-settings.ts
38531
- function parseSettingValue(value, valueType) {
38532
- switch (valueType) {
38533
- case "boolean":
38534
- return value === "true";
38535
- case "number":
38536
- return Number(value);
38537
- case "json":
38538
- try {
38539
- return JSON.parse(value);
38540
- } catch {
38541
- return null;
38542
- }
38543
- default:
38544
- return value;
38545
- }
38546
- }
38547
- function stringifySettingValue(value, valueType) {
38548
- switch (valueType) {
38549
- case "boolean":
38550
- return value ? "true" : "false";
38551
- case "number":
38552
- return String(value);
38553
- case "json":
38554
- return JSON.stringify(value);
38555
- default:
38556
- return String(value);
38557
- }
38558
- }
38559
- var settingValueTypes, settingCategories, platformSettings2, insertPlatformSettingSchema, selectPlatformSettingSchema, settingKeys;
38560
- var init_platform_settings = __esm({
38561
- "../../packages/database/src/schema/platform-settings.ts"() {
38562
- "use strict";
38563
- init_pg_core();
38564
- init_zod();
38565
- settingValueTypes = ["string", "boolean", "number", "json"];
38566
- settingCategories = [
38567
- "platform",
38568
- "notifications",
38569
- "security",
38570
- "email",
38571
- "financial"
38572
- ];
38573
- platformSettings2 = pgTable(
38574
- "platform_settings",
38575
- {
38576
- key: varchar("key", { length: 100 }).primaryKey(),
38577
- value: text("value").notNull(),
38578
- valueType: varchar("value_type", { length: 20 }).notNull().default("string"),
38579
- category: varchar("category", { length: 50 }).notNull().default("general"),
38580
- description: text("description"),
38581
- updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
38582
- updatedBy: varchar("updated_by", { length: 255 })
38583
- },
38584
- (table) => [index("idx_platform_settings_category").on(table.category)]
38585
- );
38586
- insertPlatformSettingSchema = external_exports.object({
38587
- key: external_exports.string().min(1).max(100),
38588
- value: external_exports.string(),
38589
- valueType: external_exports.enum(settingValueTypes).default("string"),
38590
- category: external_exports.enum(settingCategories).default("platform"),
38591
- description: external_exports.string().nullable().optional(),
38592
- updatedBy: external_exports.string().nullable().optional()
38593
- });
38594
- selectPlatformSettingSchema = external_exports.object({
38595
- key: external_exports.string(),
38596
- value: external_exports.string(),
38597
- valueType: external_exports.string(),
38598
- category: external_exports.string(),
38599
- description: external_exports.string().nullable(),
38600
- updatedAt: external_exports.date().nullable(),
38601
- updatedBy: external_exports.string().nullable()
38602
- });
38603
- settingKeys = {
38604
- // Platform
38605
- platformName: "platform_name",
38606
- platformUrl: "platform_url",
38607
- platformLogoUrl: "platform_logo_url",
38608
- platformDescription: "platform_description",
38609
- maintenanceMode: "maintenance_mode",
38610
- // Notifications
38611
- emailWelcome: "email_welcome",
38612
- emailCourseApproved: "email_course_approved",
38613
- emailNewEnrollment: "email_new_enrollment",
38614
- adminSystemAlerts: "admin_system_alerts",
38615
- // Security
38616
- mfaRequiredAdmins: "mfa_required_admins",
38617
- mfaRequiredCreators: "mfa_required_creators",
38618
- sessionTimeoutMinutes: "session_timeout_minutes",
38619
- maxLoginAttempts: "max_login_attempts",
38620
- // Email
38621
- emailFrom: "email_from",
38622
- emailFromName: "email_from_name"
38623
- };
38624
- }
38625
- });
38626
-
38627
- // ../../packages/database/src/schema/admin-settings-audit.ts
38628
- var adminSettingsAudit, adminSettingsAuditRelations;
38629
- var init_admin_settings_audit = __esm({
38630
- "../../packages/database/src/schema/admin-settings-audit.ts"() {
38631
- "use strict";
38632
- init_pg_core();
38633
- init_drizzle_orm();
38634
- init_users();
38635
- adminSettingsAudit = pgTable(
38636
- "admin_settings_audit",
38637
- {
38638
- id: uuid("id").defaultRandom().primaryKey(),
38639
- // What changed
38640
- settingKey: varchar("setting_key", { length: 100 }).notNull(),
38641
- previousValue: jsonb("previous_value"),
38642
- // nullable for new settings
38643
- newValue: jsonb("new_value").notNull(),
38644
- // Who changed it
38645
- adminId: uuid("admin_id").references(() => users.id, { onDelete: "set null" }),
38646
- // When
38647
- createdAt: timestamp("created_at").defaultNow().notNull()
38648
- },
38649
- (table) => ({
38650
- settingKeyIdx: index("admin_settings_audit_setting_key_idx").on(table.settingKey),
38651
- adminIdIdx: index("admin_settings_audit_admin_id_idx").on(table.adminId),
38652
- createdAtIdx: index("admin_settings_audit_created_at_idx").on(table.createdAt)
38653
- })
38654
- );
38655
- adminSettingsAuditRelations = relations(adminSettingsAudit, ({ one }) => ({
38656
- admin: one(users, {
38657
- fields: [adminSettingsAudit.adminId],
38658
- references: [users.id]
38659
- })
38660
- }));
38661
- }
38662
- });
38663
-
38664
38793
  // ../../packages/database/src/schema/admin-activity-feed.ts
38665
38794
  var adminActivityFeed, adminActivityFeedRelations;
38666
38795
  var init_admin_activity_feed = __esm({
@@ -39870,26 +39999,19 @@ var init_system_config = __esm({
39870
39999
  }));
39871
40000
  configKeys = {
39872
40001
  // AI - Generation
39873
- "ai.generation.model": "ai.generation.model",
40002
+ // GH #942: ai.*.model keys (generation/chat/study) retired — model comes from
40003
+ // the routing rule + gateway tier (GH #799 2b); DB keeps inference params only.
39874
40004
  "ai.generation.max_tokens": "ai.generation.max_tokens",
39875
40005
  "ai.generation.temperature": "ai.generation.temperature",
39876
40006
  // AI - Chat
39877
- "ai.chat.model": "ai.chat.model",
39878
40007
  "ai.chat.max_tokens": "ai.chat.max_tokens",
39879
40008
  "ai.chat.temperature": "ai.chat.temperature",
39880
40009
  // AI - Study
39881
- "ai.study.chat_model": "ai.study.chat_model",
39882
- "ai.study.hint_model": "ai.study.hint_model",
39883
40010
  "ai.study.max_tokens": "ai.study.max_tokens",
39884
40011
  "ai.study.temperature": "ai.study.temperature",
39885
- // AI - Costs
40012
+ // AI - Costs (GH #947: cache_optimization_enabled and ai.context.* purged;
40013
+ // GH #974: requests_per_minute purged — reader never mounted)
39886
40014
  "ai.costs.daily_limit_usd": "ai.costs.daily_limit_usd",
39887
- "ai.costs.requests_per_minute": "ai.costs.requests_per_minute",
39888
- "ai.costs.cache_optimization_enabled": "ai.costs.cache_optimization_enabled",
39889
- // AI - Context Window Management
39890
- "ai.context.window_size": "ai.context.window_size",
39891
- "ai.context.output_reserve": "ai.context.output_reserve",
39892
- "ai.context.compression_threshold": "ai.context.compression_threshold",
39893
40015
  // Business - Pricing
39894
40016
  "business.pricing.min_course_price_brl": "business.pricing.min_course_price_brl",
39895
40017
  "business.pricing.course_mentoria_creator_share": "business.pricing.course_mentoria_creator_share",
@@ -39906,6 +40028,8 @@ var init_system_config = __esm({
39906
40028
  "business.monitoring.prompt_caching_discount": "business.monitoring.prompt_caching_discount",
39907
40029
  "business.monitoring.llm_cost_warning_usd": "business.monitoring.llm_cost_warning_usd",
39908
40030
  "business.monitoring.llm_cost_critical_usd": "business.monitoring.llm_cost_critical_usd",
40031
+ // GH #948: margin tolerance for the daily credit-reconciliation job
40032
+ "business.monitoring.margin_tolerance_percent": "business.monitoring.margin_tolerance_percent",
39909
40033
  // Business - Limits
39910
40034
  "business.limits.max_courses_per_creator": "business.limits.max_courses_per_creator",
39911
40035
  "business.limits.max_modules_per_course": "business.limits.max_modules_per_course",
@@ -39918,21 +40042,11 @@ var init_system_config = __esm({
39918
40042
  "feature.community_enabled": "feature.community_enabled",
39919
40043
  "feature.certificates_enabled": "feature.certificates_enabled",
39920
40044
  "feature.maintenance_mode": "feature.maintenance_mode",
39921
- "feature.new_user_registration": "feature.new_user_registration",
39922
40045
  "feature.require_creator_mfa": "feature.require_creator_mfa",
39923
40046
  "feature.require_admin_mfa": "feature.require_admin_mfa",
39924
- "feature.security.session_timeout_minutes": "feature.security.session_timeout_minutes",
39925
- "feature.security.max_login_attempts": "feature.security.max_login_attempts",
39926
- // Platform Identity
39927
- "platform.identity.name": "platform.identity.name",
39928
- "platform.identity.url": "platform.identity.url",
39929
- "platform.identity.logo_url": "platform.identity.logo_url",
39930
- "platform.identity.description": "platform.identity.description",
40047
+ // GH #947: feature.security.*, platform.identity.* and the four
40048
+ // notification.email.* toggles purged — zero functional readers.
39931
40049
  // Notification Settings
39932
- "notification.email.welcome_enabled": "notification.email.welcome_enabled",
39933
- "notification.email.course_approved_enabled": "notification.email.course_approved_enabled",
39934
- "notification.email.new_enrollment_enabled": "notification.email.new_enrollment_enabled",
39935
- "notification.email.admin_system_alerts": "notification.email.admin_system_alerts",
39936
40050
  "notification.sender.from_address": "notification.sender.from_address",
39937
40051
  "notification.sender.from_name": "notification.sender.from_name"
39938
40052
  };
@@ -41203,7 +41317,6 @@ var init_relations_auth = __esm({
41203
41317
  init_user_regional_settings();
41204
41318
  init_users();
41205
41319
  init_validation_attempts();
41206
- init_admin_settings_audit();
41207
41320
  init_brainstormings();
41208
41321
  init_course_review_snapshots();
41209
41322
  init_learning_paths();
@@ -41261,7 +41374,6 @@ var init_relations_auth = __esm({
41261
41374
  dailyGoal: one(dailyGoals2),
41262
41375
  dailyGoalProgress: many(dailyGoalProgress2),
41263
41376
  regionalSettings: one(userRegionalSettings),
41264
- adminSettingsAuditLogs: many(adminSettingsAudit),
41265
41377
  creatorAuditLogs: many(creatorAuditLog),
41266
41378
  lessonRefinements: many(lessonRefinements),
41267
41379
  courseReviewSnapshots: many(courseReviewSnapshots),
@@ -43046,6 +43158,77 @@ var init_idempotency = __esm({
43046
43158
  }
43047
43159
  });
43048
43160
 
43161
+ // ../../packages/database/src/schema/referrals.ts
43162
+ var referralCodes, referrals;
43163
+ var init_referrals = __esm({
43164
+ "../../packages/database/src/schema/referrals.ts"() {
43165
+ "use strict";
43166
+ init_pg_core();
43167
+ init_users();
43168
+ referralCodes = pgTable("referral_codes", {
43169
+ userId: uuid("user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }),
43170
+ code: varchar("code", { length: 16 }).notNull().unique(),
43171
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
43172
+ });
43173
+ referrals = pgTable("referrals", {
43174
+ // PK = one referrer per person, one bonus per person, DB-enforced.
43175
+ referredUserId: uuid("referred_user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }),
43176
+ // null = referrer account deleted; referred keeps their referral record.
43177
+ referrerUserId: uuid("referrer_user_id").references(() => users.id, { onDelete: "set null" }),
43178
+ code: varchar("code", { length: 16 }).notNull(),
43179
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
43180
+ // Set exactly once by the bonus claim (UPDATE ... WHERE qualified_at IS NULL).
43181
+ qualifiedAt: timestamp("qualified_at", { withTimezone: true })
43182
+ });
43183
+ }
43184
+ });
43185
+
43186
+ // ../../packages/database/src/schema/user-consents.ts
43187
+ var userConsentDocumentEnum, userConsentSurfaceEnum, userConsents;
43188
+ var init_user_consents = __esm({
43189
+ "../../packages/database/src/schema/user-consents.ts"() {
43190
+ "use strict";
43191
+ init_pg_core();
43192
+ init_users();
43193
+ userConsentDocumentEnum = pgEnum("user_consent_document", ["terms", "privacy"]);
43194
+ userConsentSurfaceEnum = pgEnum("user_consent_surface", [
43195
+ "signup_form",
43196
+ // password signup, /signup
43197
+ "oauth",
43198
+ // provider buttons, on /signup AND on /login
43199
+ "magic_link",
43200
+ // magic-link request form
43201
+ "login_form"
43202
+ // /login, login-time capture for pre-chokepoint accounts (GH #1007)
43203
+ ]);
43204
+ userConsents = pgTable(
43205
+ "user_consents",
43206
+ {
43207
+ id: uuid("id").primaryKey().defaultRandom(),
43208
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
43209
+ document: userConsentDocumentEnum("document").notNull(),
43210
+ // Policy version the person was shown, e.g. "v1.0" or "v1.0-BR". Width matches
43211
+ // countries.terms_version so a per-jurisdiction scheme stays possible later.
43212
+ version: varchar("version", { length: 20 }).notNull(),
43213
+ // Which translation was on screen — 5 locales means 5 different texts.
43214
+ locale: varchar("locale", { length: 10 }).notNull(),
43215
+ surface: userConsentSurfaceEnum("surface").notNull(),
43216
+ acknowledgedAt: timestamp("acknowledged_at", { withTimezone: true }).defaultNow().notNull()
43217
+ },
43218
+ (table) => ({
43219
+ // Idempotency, not just a constraint: the OAuth callback runs on EVERY login,
43220
+ // so the writer must be safe to call repeatedly. The left-hand prefix also
43221
+ // serves every "what has this user acknowledged?" read, so no second index.
43222
+ userDocumentVersionUnique: uniqueIndex("user_consents_user_document_version_unique").on(
43223
+ table.userId,
43224
+ table.document,
43225
+ table.version
43226
+ )
43227
+ })
43228
+ );
43229
+ }
43230
+ });
43231
+
43049
43232
  // ../../packages/database/src/schema/user-deletions.ts
43050
43233
  var userDeletionStatusEnum, userDeletions;
43051
43234
  var init_user_deletions = __esm({
@@ -43249,6 +43432,55 @@ var init_subscription_redemptions = __esm({
43249
43432
  }
43250
43433
  });
43251
43434
 
43435
+ // ../../packages/database/src/schema/operation-billing-rates.ts
43436
+ var operationBillingRates, operationBillingRatesHistory;
43437
+ var init_operation_billing_rates = __esm({
43438
+ "../../packages/database/src/schema/operation-billing-rates.ts"() {
43439
+ "use strict";
43440
+ init_pg_core();
43441
+ init_users();
43442
+ operationBillingRates = pgTable(
43443
+ "operation_billing_rates",
43444
+ {
43445
+ id: uuid("id").primaryKey().defaultRandom(),
43446
+ operationType: varchar("operation_type", { length: 100 }).notNull().unique(),
43447
+ chargedInputPer1M: decimal("charged_input_per_1m", {
43448
+ precision: 10,
43449
+ scale: 4,
43450
+ mode: "number"
43451
+ }).notNull(),
43452
+ chargedOutputPer1M: decimal("charged_output_per_1m", {
43453
+ precision: 10,
43454
+ scale: 4,
43455
+ mode: "number"
43456
+ }).notNull(),
43457
+ isActive: boolean("is_active").notNull().default(true),
43458
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
43459
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
43460
+ },
43461
+ (table) => [index("idx_operation_billing_rates_active").on(table.isActive)]
43462
+ );
43463
+ operationBillingRatesHistory = pgTable(
43464
+ "operation_billing_rates_history",
43465
+ {
43466
+ id: uuid("id").primaryKey().defaultRandom(),
43467
+ operationType: varchar("operation_type", { length: 100 }).notNull(),
43468
+ action: varchar("action", { length: 20 }).notNull(),
43469
+ // 'create' | 'update' | 'delete'
43470
+ oldValues: jsonb("old_values"),
43471
+ newValues: jsonb("new_values"),
43472
+ changedBy: uuid("changed_by").notNull().references(() => users.id),
43473
+ changedAt: timestamp("changed_at", { withTimezone: true }).notNull().defaultNow(),
43474
+ reason: varchar("reason", { length: 500 })
43475
+ },
43476
+ (table) => [
43477
+ index("idx_obr_history_operation").on(table.operationType),
43478
+ index("idx_obr_history_date").on(table.changedAt)
43479
+ ]
43480
+ );
43481
+ }
43482
+ });
43483
+
43252
43484
  // ../../packages/database/src/schema/index.ts
43253
43485
  var schema_exports = {};
43254
43486
  __export(schema_exports, {
@@ -43264,6 +43496,8 @@ __export(schema_exports, {
43264
43496
  PACKAGE_MINIMUM_PRICES: () => PACKAGE_MINIMUM_PRICES,
43265
43497
  PACKAGE_SESSION_OPTIONS: () => PACKAGE_SESSION_OPTIONS,
43266
43498
  PACKAGE_VALIDITY_OPTIONS: () => PACKAGE_VALIDITY_OPTIONS,
43499
+ PIX_CHARGE_PURPOSES: () => PIX_CHARGE_PURPOSES,
43500
+ PIX_CHARGE_STATUSES: () => PIX_CHARGE_STATUSES,
43267
43501
  REVIEW_TAGS: () => REVIEW_TAGS,
43268
43502
  SUGGESTED_DISCOUNTS: () => SUGGESTED_DISCOUNTS,
43269
43503
  V3_CANCEL_REASON_TTL_SECONDS: () => V3_CANCEL_REASON_TTL_SECONDS,
@@ -43273,8 +43507,6 @@ __export(schema_exports, {
43273
43507
  actionTypeEnum: () => actionTypeEnum,
43274
43508
  adminActivityFeed: () => adminActivityFeed,
43275
43509
  adminActivityFeedRelations: () => adminActivityFeedRelations,
43276
- adminSettingsAudit: () => adminSettingsAudit,
43277
- adminSettingsAuditRelations: () => adminSettingsAuditRelations,
43278
43510
  announcementTypes: () => announcementTypes,
43279
43511
  announcements: () => announcements,
43280
43512
  applicableToEnum: () => applicableToEnum,
@@ -43441,7 +43673,6 @@ __export(schema_exports, {
43441
43673
  insertCourseMentoriaOfferSchema: () => insertCourseMentoriaOfferSchema,
43442
43674
  insertMentorshipCreditSchema: () => insertMentorshipCreditSchema,
43443
43675
  insertMentorshipPackageSchema: () => insertMentorshipPackageSchema,
43444
- insertPlatformSettingSchema: () => insertPlatformSettingSchema,
43445
43676
  isAmountSignValid: () => isAmountSignValid,
43446
43677
  isCreditUsable: () => isCreditUsable,
43447
43678
  isWithdrawableLedgerType: () => isWithdrawableLedgerType,
@@ -43529,9 +43760,10 @@ __export(schema_exports, {
43529
43760
  notificationsRelations: () => notificationsRelations2,
43530
43761
  onboardingAnalytics: () => onboardingAnalytics2,
43531
43762
  onboardingAnalyticsRelations: () => onboardingAnalyticsRelations2,
43763
+ operationBillingRates: () => operationBillingRates,
43764
+ operationBillingRatesHistory: () => operationBillingRatesHistory,
43532
43765
  optimizerRoleEnum: () => optimizerRoleEnum,
43533
43766
  parisAuthorizedCreators: () => parisAuthorizedCreators,
43534
- parseSettingValue: () => parseSettingValue,
43535
43767
  participantStatusEnum: () => participantStatusEnum,
43536
43768
  pathCourses: () => pathCourses2,
43537
43769
  pathCoursesRelations: () => pathCoursesRelations2,
@@ -43545,9 +43777,8 @@ __export(schema_exports, {
43545
43777
  payoutWithdrawalTransitions: () => payoutWithdrawalTransitions,
43546
43778
  payoutWithdrawals: () => payoutWithdrawals,
43547
43779
  pgBackendIdempotencyKeys: () => pgBackendIdempotencyKeys,
43780
+ pixCharges: () => pixCharges,
43548
43781
  pixKeyTypeEnum: () => pixKeyTypeEnum,
43549
- platformConfig: () => platformConfig,
43550
- platformSettings: () => platformSettings2,
43551
43782
  playgroundEvalScores: () => playgroundEvalScores,
43552
43783
  playgroundEvalScoresRelations: () => playgroundEvalScoresRelations,
43553
43784
  playgroundEvaluations: () => playgroundEvaluations,
@@ -43591,6 +43822,8 @@ __export(schema_exports, {
43591
43822
  pushTokens: () => pushTokens,
43592
43823
  pushTokensRelations: () => pushTokensRelations,
43593
43824
  quotaChangeHistory: () => quotaChangeHistory,
43825
+ referralCodes: () => referralCodes,
43826
+ referrals: () => referrals,
43594
43827
  refinementStatusEnum: () => refinementStatusEnum,
43595
43828
  refundAuditLog: () => refundAuditLog2,
43596
43829
  refundAuditLogRelations: () => refundAuditLogRelations2,
@@ -43613,7 +43846,6 @@ __export(schema_exports, {
43613
43846
  securityEventCategoryEnum: () => securityEventCategoryEnum,
43614
43847
  securityEventResultEnum: () => securityEventResultEnum,
43615
43848
  securityEventTypeEnum: () => securityEventTypeEnum,
43616
- selectPlatformSettingSchema: () => selectPlatformSettingSchema,
43617
43849
  serviceTypeEnum: () => serviceTypeEnum,
43618
43850
  sessionCancelledReasonEnum: () => sessionCancelledReasonEnum,
43619
43851
  sessionParticipants: () => sessionParticipants2,
@@ -43629,9 +43861,6 @@ __export(schema_exports, {
43629
43861
  sessionWaitlistRelations: () => sessionWaitlistRelations2,
43630
43862
  sessions: () => sessions,
43631
43863
  sessionsRelations: () => sessionsRelations2,
43632
- settingCategories: () => settingCategories,
43633
- settingKeys: () => settingKeys,
43634
- settingValueTypes: () => settingValueTypes,
43635
43864
  skillLevelEnum: () => skillLevelEnum,
43636
43865
  slaStatusEnum: () => slaStatusEnum,
43637
43866
  slashCommandAnalytics: () => slashCommandAnalytics2,
@@ -43641,7 +43870,6 @@ __export(schema_exports, {
43641
43870
  sparkChatSessions: () => sparkChatSessions2,
43642
43871
  sparkChatSessionsRelations: () => sparkChatSessionsRelations2,
43643
43872
  sparkPhaseValues: () => sparkPhaseValues,
43644
- stringifySettingValue: () => stringifySettingValue,
43645
43873
  studentCourseMemory: () => studentCourseMemory,
43646
43874
  studentCourseMemoryRelations: () => studentCourseMemoryRelations,
43647
43875
  studentFeedback: () => studentFeedback,
@@ -43699,6 +43927,9 @@ __export(schema_exports, {
43699
43927
  userBadgesRelations: () => userBadgesRelations2,
43700
43928
  userChallengeScores: () => userChallengeScores,
43701
43929
  userChallengeScoresRelations: () => userChallengeScoresRelations,
43930
+ userConsentDocumentEnum: () => userConsentDocumentEnum,
43931
+ userConsentSurfaceEnum: () => userConsentSurfaceEnum,
43932
+ userConsents: () => userConsents,
43702
43933
  userDeletionStatusEnum: () => userDeletionStatusEnum,
43703
43934
  userDeletions: () => userDeletions,
43704
43935
  userFollows: () => userFollows2,
@@ -43758,6 +43989,7 @@ var init_schema3 = __esm({
43758
43989
  init_creator_index();
43759
43990
  init_webhook_events();
43760
43991
  init_transactions();
43992
+ init_pix_charges();
43761
43993
  init_coupons();
43762
43994
  init_coupon_usages();
43763
43995
  init_onboarding_analytics();
@@ -43770,8 +44002,6 @@ var init_schema3 = __esm({
43770
44002
  init_user_follows();
43771
44003
  init_spark_chat_sessions();
43772
44004
  init_study_index();
43773
- init_platform_settings();
43774
- init_admin_settings_audit();
43775
44005
  init_admin_activity_feed();
43776
44006
  init_generation_state();
43777
44007
  init_security_audit_log();
@@ -43819,7 +44049,6 @@ var init_schema3 = __esm({
43819
44049
  init_relations_community();
43820
44050
  init_relations_gamification();
43821
44051
  init_moderation_log();
43822
- init_admin_settings_audit();
43823
44052
  init_creator_audit_log();
43824
44053
  init_banks();
43825
44054
  init_course_qa_reports();
@@ -43841,11 +44070,14 @@ var init_schema3 = __esm({
43841
44070
  init_course_visibility();
43842
44071
  init_course_allowlist();
43843
44072
  init_idempotency();
44073
+ init_referrals();
44074
+ init_user_consents();
43844
44075
  init_user_deletions();
43845
44076
  init_user_profiles();
43846
44077
  init_user_resource_audits();
43847
44078
  init_email_delivery();
43848
44079
  init_subscription_redemptions();
44080
+ init_operation_billing_rates();
43849
44081
  }
43850
44082
  });
43851
44083
 
@@ -43907,7 +44139,6 @@ var init_src3 = __esm({
43907
44139
  init_user_follows();
43908
44140
  init_generation_state();
43909
44141
  init_community();
43910
- init_platform_settings();
43911
44142
  init_marketing();
43912
44143
  init_security_audit_log();
43913
44144
  init_credit_movement_audit_log();
@@ -44246,7 +44477,7 @@ async function runStart(opts, deps = defaultDeps3) {
44246
44477
  deps.error(msg);
44247
44478
  }
44248
44479
  }
44249
- var logger9, defaultDeps3, startCommand;
44480
+ var logger10, defaultDeps3, startCommand;
44250
44481
  var init_start = __esm({
44251
44482
  "src/commands/start.ts"() {
44252
44483
  "use strict";
@@ -44259,7 +44490,7 @@ var init_start = __esm({
44259
44490
  init_status();
44260
44491
  init_formatter();
44261
44492
  init_errors();
44262
- logger9 = createLogger("cli:start");
44493
+ logger10 = createLogger("cli:start");
44263
44494
  defaultDeps3 = {
44264
44495
  requireSession,
44265
44496
  requireActiveCourse,
@@ -44273,7 +44504,7 @@ var init_start = __esm({
44273
44504
  output,
44274
44505
  error,
44275
44506
  stderrWrite: (message) => process.stderr.write(message),
44276
- logger: logger9
44507
+ logger: logger10
44277
44508
  };
44278
44509
  startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
44279
44510
  await runStart(opts);
@@ -44283,7 +44514,7 @@ var init_start = __esm({
44283
44514
 
44284
44515
  // src/commands/start-next.ts
44285
44516
  import { Command as Command9 } from "commander";
44286
- var logger10, startNextCommand;
44517
+ var logger11, startNextCommand;
44287
44518
  var init_start_next = __esm({
44288
44519
  "src/commands/start-next.ts"() {
44289
44520
  "use strict";
@@ -44295,7 +44526,7 @@ var init_start_next = __esm({
44295
44526
  init_workspace_state();
44296
44527
  init_formatter();
44297
44528
  init_errors();
44298
- logger10 = createLogger("cli:start-next");
44529
+ logger11 = createLogger("cli:start-next");
44299
44530
  startNextCommand = new Command9("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
44300
44531
  try {
44301
44532
  const session = await requireSession();
@@ -44303,7 +44534,7 @@ var init_start_next = __esm({
44303
44534
  const driftWarning = await checkCourseDrift();
44304
44535
  if (driftWarning) process.stderr.write(driftWarning + "\n");
44305
44536
  const data = createHttpProvider(session.apiUrl, session.token);
44306
- const deps = { data, logger: logger10 };
44537
+ const deps = { data, logger: logger11 };
44307
44538
  const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
44308
44539
  const ws = await findWorkspaceState();
44309
44540
  if (ws)
@@ -44340,7 +44571,7 @@ var init_start_next = __esm({
44340
44571
 
44341
44572
  // src/commands/next.ts
44342
44573
  import { Command as Command10 } from "commander";
44343
- var logger11, nextCommand;
44574
+ var logger12, nextCommand;
44344
44575
  var init_next = __esm({
44345
44576
  "src/commands/next.ts"() {
44346
44577
  "use strict";
@@ -44352,7 +44583,7 @@ var init_next = __esm({
44352
44583
  init_workspace_state();
44353
44584
  init_formatter();
44354
44585
  init_errors();
44355
- logger11 = createLogger("cli:next");
44586
+ logger12 = createLogger("cli:next");
44356
44587
  nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
44357
44588
  try {
44358
44589
  const session = await requireSession();
@@ -44360,7 +44591,7 @@ var init_next = __esm({
44360
44591
  const driftWarning = await checkCourseDrift();
44361
44592
  if (driftWarning) process.stderr.write(driftWarning + "\n");
44362
44593
  const data = createHttpProvider(session.apiUrl, session.token);
44363
- const deps = { data, logger: logger11 };
44594
+ const deps = { data, logger: logger12 };
44364
44595
  const lessonData = await nextLesson(
44365
44596
  { enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
44366
44597
  deps
@@ -44433,7 +44664,7 @@ function replacePlaceholders(text2, workspacePath, courseTitle) {
44433
44664
  const base = path13.basename(workspacePath) === ".tostudy" ? path13.dirname(workspacePath) : workspacePath;
44434
44665
  const slug = courseSlug(courseTitle);
44435
44666
  const vaultPath = resolveVaultPath(workspacePath, slug);
44436
- return text2.replaceAll("$WORKSPACE", base).replaceAll("$VAULT", vaultPath);
44667
+ return text2.replace(/\$WORKSPACE(?![A-Za-z0-9_])/g, () => base).replace(/\$VAULT(?![A-Za-z0-9_])/g, () => vaultPath);
44437
44668
  }
44438
44669
  function formatLessonContent(data, workspacePath, courseTitle) {
44439
44670
  const time4 = adjustTimeEstimate(data.type, data.estimatedTimeMinutes);
@@ -44474,7 +44705,7 @@ function formatLessonContent(data, workspacePath, courseTitle) {
44474
44705
  }
44475
44706
  return lines.join("\n");
44476
44707
  }
44477
- var logger12, lessonCommand;
44708
+ var logger13, lessonCommand;
44478
44709
  var init_lesson = __esm({
44479
44710
  "src/commands/lesson.ts"() {
44480
44711
  "use strict";
@@ -44487,7 +44718,7 @@ var init_lesson = __esm({
44487
44718
  init_formatter();
44488
44719
  init_resolve();
44489
44720
  init_errors();
44490
- logger12 = createLogger("cli:lesson");
44721
+ logger13 = createLogger("cli:lesson");
44491
44722
  lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").option(
44492
44723
  "--read <lessonId>",
44493
44724
  "Read a specific lesson by id (works on archived / read-only courses)"
@@ -44498,7 +44729,7 @@ var init_lesson = __esm({
44498
44729
  const driftWarning = await checkCourseDrift();
44499
44730
  if (driftWarning) process.stderr.write(driftWarning + "\n");
44500
44731
  const data = createHttpProvider(session.apiUrl, session.token);
44501
- const deps = { data, logger: logger12 };
44732
+ const deps = { data, logger: logger13 };
44502
44733
  let lessonId = opts.read ?? activeCourse.currentLessonId;
44503
44734
  if (!lessonId) {
44504
44735
  try {
@@ -44593,7 +44824,7 @@ function formatKnowledge(data) {
44593
44824
  });
44594
44825
  return lines.join("\n").trimEnd();
44595
44826
  }
44596
- var logger13, knowledgeCommand;
44827
+ var logger14, knowledgeCommand;
44597
44828
  var init_knowledge2 = __esm({
44598
44829
  "src/commands/knowledge.ts"() {
44599
44830
  "use strict";
@@ -44601,7 +44832,7 @@ var init_knowledge2 = __esm({
44601
44832
  init_http();
44602
44833
  init_guards();
44603
44834
  init_formatter();
44604
- logger13 = createLogger("cli:knowledge");
44835
+ logger14 = createLogger("cli:knowledge");
44605
44836
  knowledgeCommand = new Command12("knowledge").description("Show the active course's knowledge base (base de conhecimento)").option("--json", "Output structured JSON").action(async (opts) => {
44606
44837
  try {
44607
44838
  const activeCourse = await requireActiveCourse();
@@ -44625,7 +44856,7 @@ var init_knowledge2 = __esm({
44625
44856
  else error(msg);
44626
44857
  return;
44627
44858
  }
44628
- logger13.warn("knowledge failed", { error: msg });
44859
+ logger14.warn("knowledge failed", { error: msg });
44629
44860
  if (opts.json) jsonError(msg);
44630
44861
  else error(msg);
44631
44862
  }
@@ -44635,7 +44866,7 @@ var init_knowledge2 = __esm({
44635
44866
 
44636
44867
  // src/commands/hint.ts
44637
44868
  import { Command as Command13 } from "commander";
44638
- var logger14, hintCommand;
44869
+ var logger15, hintCommand;
44639
44870
  var init_hint = __esm({
44640
44871
  "src/commands/hint.ts"() {
44641
44872
  "use strict";
@@ -44646,7 +44877,7 @@ var init_hint = __esm({
44646
44877
  init_course_state();
44647
44878
  init_formatter();
44648
44879
  init_errors();
44649
- logger14 = createLogger("cli:hint");
44880
+ logger15 = createLogger("cli:hint");
44650
44881
  hintCommand = new Command13("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
44651
44882
  try {
44652
44883
  const session = await requireSession();
@@ -44654,7 +44885,7 @@ var init_hint = __esm({
44654
44885
  const driftWarning = await checkCourseDrift();
44655
44886
  if (driftWarning) process.stderr.write(driftWarning + "\n");
44656
44887
  const data = createHttpProvider(session.apiUrl, session.token);
44657
- const deps = { data, logger: logger14 };
44888
+ const deps = { data, logger: logger15 };
44658
44889
  const hint = await getHint(
44659
44890
  { userId: session.userId, enrollmentId: activeCourse.enrollmentId },
44660
44891
  deps
@@ -45008,7 +45239,7 @@ var init_init_template = __esm({
45008
45239
  import fs12 from "node:fs";
45009
45240
  import path14 from "node:path";
45010
45241
  import { Command as Command14 } from "commander";
45011
- var logger15, validateCommand;
45242
+ var logger16, validateCommand;
45012
45243
  var init_validate = __esm({
45013
45244
  "src/commands/validate.ts"() {
45014
45245
  "use strict";
@@ -45021,7 +45252,7 @@ var init_validate = __esm({
45021
45252
  init_formatter();
45022
45253
  init_init_template();
45023
45254
  init_errors();
45024
- logger15 = createLogger("cli:validate");
45255
+ logger16 = createLogger("cli:validate");
45025
45256
  validateCommand = new Command14("validate").description("Validate your solution for the current exercise").argument("[file]", "Path to the solution file to read").option("--stdin", "Read solution from stdin instead of a file").option("--json", "Output structured JSON").action(async (file2, opts) => {
45026
45257
  try {
45027
45258
  const session = await requireSession();
@@ -45029,7 +45260,7 @@ var init_validate = __esm({
45029
45260
  const driftWarning = await checkCourseDrift();
45030
45261
  if (driftWarning) process.stderr.write(driftWarning + "\n");
45031
45262
  const data = createHttpProvider(session.apiUrl, session.token);
45032
- const deps = { data, logger: logger15 };
45263
+ const deps = { data, logger: logger16 };
45033
45264
  let lessonId = activeCourse.currentLessonId;
45034
45265
  if (!lessonId) {
45035
45266
  try {
@@ -45478,7 +45709,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
45478
45709
  deps.output(artifacts.learnerBrief, { json: false });
45479
45710
  }
45480
45711
  }
45481
- var logger16, defaultDeps4, initCommand;
45712
+ var logger17, defaultDeps4, initCommand;
45482
45713
  var init_init = __esm({
45483
45714
  "src/commands/init.ts"() {
45484
45715
  "use strict";
@@ -45494,7 +45725,7 @@ var init_init = __esm({
45494
45725
  init_api3();
45495
45726
  init_instruction_pipeline();
45496
45727
  init_pipeline_deps();
45497
- logger16 = createLogger("cli:init");
45728
+ logger17 = createLogger("cli:init");
45498
45729
  defaultDeps4 = {
45499
45730
  getSession,
45500
45731
  findWorkspaceState,
@@ -45508,7 +45739,7 @@ var init_init = __esm({
45508
45739
  saveCourseLearnerProfile,
45509
45740
  buildInitArtifacts,
45510
45741
  output,
45511
- logger: logger16,
45742
+ logger: logger17,
45512
45743
  createHttpProvider,
45513
45744
  resolveAndGenerate,
45514
45745
  buildPipelineDeps
@@ -45830,7 +46061,7 @@ import { Command as Command17 } from "commander";
45830
46061
  import path17 from "node:path";
45831
46062
  import os9 from "node:os";
45832
46063
  import fs15 from "node:fs/promises";
45833
- var logger17, workspaceCommand;
46064
+ var logger18, workspaceCommand;
45834
46065
  var init_workspace2 = __esm({
45835
46066
  "src/commands/workspace.ts"() {
45836
46067
  "use strict";
@@ -45840,7 +46071,7 @@ var init_workspace2 = __esm({
45840
46071
  init_course_state();
45841
46072
  init_resolve();
45842
46073
  init_errors();
45843
- logger17 = createLogger("cli:workspace");
46074
+ logger18 = createLogger("cli:workspace");
45844
46075
  workspaceCommand = new Command17("workspace").description(
45845
46076
  getErrors().workspaceCommandDescription
45846
46077
  );
@@ -45906,7 +46137,7 @@ Pr\xF3ximo passo: tostudy export
45906
46137
  );
45907
46138
  }
45908
46139
  } catch (err) {
45909
- logger17.error("workspace setup failed", { error: err });
46140
+ logger18.error("workspace setup failed", { error: err });
45910
46141
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
45911
46142
  `);
45912
46143
  process.exit(1);
@@ -46014,7 +46245,7 @@ import { Command as Command18 } from "commander";
46014
46245
  import path18 from "node:path";
46015
46246
  import os10 from "node:os";
46016
46247
  import fs16 from "node:fs/promises";
46017
- var logger18, exportCommand;
46248
+ var logger19, exportCommand;
46018
46249
  var init_export = __esm({
46019
46250
  "src/commands/export.ts"() {
46020
46251
  "use strict";
@@ -46025,7 +46256,7 @@ var init_export = __esm({
46025
46256
  init_course_state();
46026
46257
  init_resolve();
46027
46258
  init_errors();
46028
- logger18 = createLogger("cli:export");
46259
+ logger19 = createLogger("cli:export");
46029
46260
  exportCommand = new Command18("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path18.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
46030
46261
  try {
46031
46262
  const session = await requireSession();
@@ -46053,7 +46284,7 @@ var init_export = __esm({
46053
46284
  }
46054
46285
  if (!hasConfig) {
46055
46286
  const slug = courseSlug(activeCourse.courseTitle);
46056
- logger18.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
46287
+ logger19.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
46057
46288
  await fs16.mkdir(ws.workspacePath, { recursive: true });
46058
46289
  for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
46059
46290
  await fs16.mkdir(path18.join(ws.workspacePath, dir), { recursive: true });
@@ -46115,7 +46346,7 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
46115
46346
  );
46116
46347
  }
46117
46348
  } catch (err) {
46118
- logger18.error("export failed", { error: err });
46349
+ logger19.error("export failed", { error: err });
46119
46350
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
46120
46351
  `);
46121
46352
  process.exit(1);
@@ -46129,7 +46360,7 @@ import { Command as Command19 } from "commander";
46129
46360
  import { execFile as execFile3 } from "node:child_process";
46130
46361
  import path19 from "node:path";
46131
46362
  import os11 from "node:os";
46132
- var logger19, openCommand;
46363
+ var logger20, openCommand;
46133
46364
  var init_open = __esm({
46134
46365
  "src/commands/open.ts"() {
46135
46366
  "use strict";
@@ -46138,7 +46369,7 @@ var init_open = __esm({
46138
46369
  init_course_state();
46139
46370
  init_resolve();
46140
46371
  init_errors();
46141
- logger19 = createLogger("cli:open");
46372
+ logger20 = createLogger("cli:open");
46142
46373
  openCommand = new Command19("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path19.join(os11.homedir(), "study")).action(async (opts) => {
46143
46374
  try {
46144
46375
  const activeCourse = await requireActiveCourse();
@@ -46156,7 +46387,7 @@ var init_open = __esm({
46156
46387
  const editor = process.env["EDITOR"] ?? "code";
46157
46388
  execFile3(editor, [ws.workspacePath], (err) => {
46158
46389
  if (err) {
46159
- logger19.error("open failed", { editor, workspacePath: ws.workspacePath });
46390
+ logger20.error("open failed", { editor, workspacePath: ws.workspacePath });
46160
46391
  process.stderr.write(`\u274C Falha ao abrir: ${err.message}
46161
46392
  `);
46162
46393
  process.exit(1);
@@ -46165,7 +46396,7 @@ var init_open = __esm({
46165
46396
  `);
46166
46397
  });
46167
46398
  } catch (err) {
46168
- logger19.error("open command failed", { error: err });
46399
+ logger20.error("open command failed", { error: err });
46169
46400
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
46170
46401
  `);
46171
46402
  process.exit(1);
@@ -46237,7 +46468,7 @@ import { Command as Command20 } from "commander";
46237
46468
  import path21 from "node:path";
46238
46469
  import os12 from "node:os";
46239
46470
  import fs18 from "node:fs/promises";
46240
- var logger20, vaultCommand;
46471
+ var logger21, vaultCommand;
46241
46472
  var init_vault2 = __esm({
46242
46473
  "src/commands/vault.ts"() {
46243
46474
  "use strict";
@@ -46249,7 +46480,7 @@ var init_vault2 = __esm({
46249
46480
  init_course_state();
46250
46481
  init_resolve();
46251
46482
  init_errors();
46252
- logger20 = createLogger("cli:vault");
46483
+ logger21 = createLogger("cli:vault");
46253
46484
  vaultCommand = new Command20("vault").description("Gerenciar vault Obsidian do curso");
46254
46485
  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) => {
46255
46486
  try {
@@ -46294,7 +46525,7 @@ var init_vault2 = __esm({
46294
46525
  activeCourse.courseId,
46295
46526
  slug
46296
46527
  );
46297
- logger20.info("Vault generated", {
46528
+ logger21.info("Vault generated", {
46298
46529
  courseId: activeCourse.courseId,
46299
46530
  vaultPath: result.vaultPath,
46300
46531
  filesWritten: result.filesWritten
@@ -46327,7 +46558,7 @@ Para visualizar:
46327
46558
  );
46328
46559
  }
46329
46560
  } catch (err) {
46330
- logger20.error("vault init failed", { error: err });
46561
+ logger21.error("vault init failed", { error: err });
46331
46562
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
46332
46563
  `);
46333
46564
  process.exit(1);
@@ -46357,7 +46588,7 @@ Para visualizar:
46357
46588
  process.exit(1);
46358
46589
  }
46359
46590
  const data = createHttpProvider(session.apiUrl, session.token);
46360
- const deps = { data, logger: logger20 };
46591
+ const deps = { data, logger: logger21 };
46361
46592
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
46362
46593
  const markerPath = path21.join(vaultPath, ".ana-vault.json");
46363
46594
  const markerRaw = await fs18.readFile(markerPath, "utf-8");
@@ -46411,7 +46642,7 @@ Para visualizar:
46411
46642
  );
46412
46643
  }
46413
46644
  } catch (err) {
46414
- logger20.error("vault sync failed", { error: err });
46645
+ logger21.error("vault sync failed", { error: err });
46415
46646
  process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
46416
46647
  `);
46417
46648
  process.exit(1);
@@ -46491,7 +46722,7 @@ var init_profile = __esm({
46491
46722
 
46492
46723
  // src/commands/sync.ts
46493
46724
  import { Command as Command22 } from "commander";
46494
- var logger21, syncCommand;
46725
+ var logger22, syncCommand;
46495
46726
  var init_sync = __esm({
46496
46727
  "src/commands/sync.ts"() {
46497
46728
  "use strict";
@@ -46500,15 +46731,18 @@ var init_sync = __esm({
46500
46731
  init_formatter();
46501
46732
  init_instruction_pipeline();
46502
46733
  init_pipeline_deps();
46503
- logger21 = createLogger("cli:sync");
46504
- syncCommand = new Command22("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").action(async (opts) => {
46734
+ logger22 = createLogger("cli:sync");
46735
+ syncCommand = new Command22("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").option(
46736
+ "--all-runtimes",
46737
+ "Write instruction files for every supported runtime, even undetected ones"
46738
+ ).action(async (opts) => {
46505
46739
  try {
46506
46740
  await requireSession();
46507
46741
  const activeCourse = await requireActiveCourse();
46508
46742
  const pipelineResult = await resolveAndGenerate(
46509
46743
  { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
46510
46744
  { forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
46511
- { cwd: process.cwd(), deps: buildPipelineDeps() }
46745
+ { cwd: process.cwd(), deps: buildPipelineDeps({ allRuntimes: opts.allRuntimes }) }
46512
46746
  );
46513
46747
  if (opts.json) {
46514
46748
  output(
@@ -46539,7 +46773,7 @@ var init_sync = __esm({
46539
46773
  } catch (err) {
46540
46774
  const msg = err instanceof Error ? err.message : String(err);
46541
46775
  if (msg.includes("process.exit")) return;
46542
- logger21.warn("sync failed", { error: msg });
46776
+ logger22.warn("sync failed", { error: msg });
46543
46777
  error(msg);
46544
46778
  }
46545
46779
  });
@@ -46548,7 +46782,7 @@ var init_sync = __esm({
46548
46782
 
46549
46783
  // src/commands/brief.ts
46550
46784
  import { Command as Command23 } from "commander";
46551
- var logger22, briefCommand;
46785
+ var logger23, briefCommand;
46552
46786
  var init_brief = __esm({
46553
46787
  "src/commands/brief.ts"() {
46554
46788
  "use strict";
@@ -46557,7 +46791,7 @@ var init_brief = __esm({
46557
46791
  init_cache();
46558
46792
  init_api();
46559
46793
  init_formatter();
46560
- logger22 = createLogger("cli:brief");
46794
+ logger23 = createLogger("cli:brief");
46561
46795
  briefCommand = new Command23("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
46562
46796
  try {
46563
46797
  const session = await requireSession();
@@ -46591,7 +46825,7 @@ var init_brief = __esm({
46591
46825
  }
46592
46826
  output(lines.join("\n"), { json: false });
46593
46827
  } catch (err) {
46594
- logger22.error("Failed to show brief", { err });
46828
+ logger23.error("Failed to show brief", { err });
46595
46829
  error(`Erro ao buscar brief: ${err instanceof Error ? err.message : String(err)}`);
46596
46830
  }
46597
46831
  });
@@ -46600,7 +46834,7 @@ var init_brief = __esm({
46600
46834
 
46601
46835
  // src/commands/brief-create.ts
46602
46836
  import { Command as Command24 } from "commander";
46603
- var logger23, briefCreateCommand;
46837
+ var logger24, briefCreateCommand;
46604
46838
  var init_brief_create = __esm({
46605
46839
  "src/commands/brief-create.ts"() {
46606
46840
  "use strict";
@@ -46610,7 +46844,7 @@ var init_brief_create = __esm({
46610
46844
  init_api();
46611
46845
  init_cache();
46612
46846
  init_formatter();
46613
- logger23 = createLogger("cli:brief-create");
46847
+ logger24 = createLogger("cli:brief-create");
46614
46848
  briefCreateCommand = new Command24("brief-create").description("Create your base learner brief via interactive prompts (T1 bootstrap)").action(async () => {
46615
46849
  try {
46616
46850
  const session = await requireSession();
@@ -46635,7 +46869,7 @@ var init_brief_create = __esm({
46635
46869
  ];
46636
46870
  output(doneLines.join("\n"), { json: false });
46637
46871
  } catch (err) {
46638
- logger23.error("Failed to create brief", { err });
46872
+ logger24.error("Failed to create brief", { err });
46639
46873
  error(`Erro ao criar brief: ${err instanceof Error ? err.message : String(err)}`);
46640
46874
  }
46641
46875
  });
@@ -46700,7 +46934,7 @@ async function saveModuleSummary(workspacePath, input2) {
46700
46934
  ""
46701
46935
  ].join("\n");
46702
46936
  fs19.writeFileSync(filePath, header + input2.summary, { mode: 384 });
46703
- logger24.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
46937
+ logger25.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
46704
46938
  return filePath;
46705
46939
  }
46706
46940
  async function loadSessionContext(workspacePath) {
@@ -46721,19 +46955,19 @@ async function loadSessionContext(workspacePath) {
46721
46955
  }
46722
46956
  return { moduleSummaries: summaries };
46723
46957
  }
46724
- var logger24;
46958
+ var logger25;
46725
46959
  var init_storage = __esm({
46726
46960
  "src/sessions/storage.ts"() {
46727
46961
  "use strict";
46728
46962
  init_dist();
46729
- logger24 = createLogger("cli:sessions");
46963
+ logger25 = createLogger("cli:sessions");
46730
46964
  }
46731
46965
  });
46732
46966
 
46733
46967
  // src/commands/compact.ts
46734
46968
  import fs20 from "node:fs";
46735
46969
  import { Command as Command26 } from "commander";
46736
- var logger25, compactCommand;
46970
+ var logger26, compactCommand;
46737
46971
  var init_compact = __esm({
46738
46972
  "src/commands/compact.ts"() {
46739
46973
  "use strict";
@@ -46742,7 +46976,7 @@ var init_compact = __esm({
46742
46976
  init_workspace_state();
46743
46977
  init_storage();
46744
46978
  init_formatter();
46745
- logger25 = createLogger("cli:compact");
46979
+ logger26 = createLogger("cli:compact");
46746
46980
  compactCommand = new Command26("compact").description("Save a module study summary (LLM-generated, from stdin)").option("--module-id <id>", "Module ID").option("--module-title <title>", "Module title").option("--json", "Output structured JSON").action(async (opts) => {
46747
46981
  try {
46748
46982
  const activeCourse = await requireActiveCourse();
@@ -46763,7 +46997,7 @@ var init_compact = __esm({
46763
46997
  moduleTitle,
46764
46998
  summary
46765
46999
  });
46766
- logger25.debug("Compact summary saved", { moduleId, filePath });
47000
+ logger26.debug("Compact summary saved", { moduleId, filePath });
46767
47001
  if (opts.json) {
46768
47002
  output({ saved: true, path: filePath, moduleId }, { json: true });
46769
47003
  } else {
@@ -46772,7 +47006,7 @@ var init_compact = __esm({
46772
47006
  } catch (err) {
46773
47007
  const msg = err instanceof Error ? err.message : String(err);
46774
47008
  if (msg.includes("process.exit")) return;
46775
- logger25.warn("compact failed", { error: msg });
47009
+ logger26.warn("compact failed", { error: msg });
46776
47010
  if (opts.json) jsonError(msg);
46777
47011
  error(msg);
46778
47012
  }
@@ -46782,7 +47016,7 @@ var init_compact = __esm({
46782
47016
 
46783
47017
  // src/commands/context.ts
46784
47018
  import { Command as Command27 } from "commander";
46785
- var logger26, contextCommand;
47019
+ var logger27, contextCommand;
46786
47020
  var init_context = __esm({
46787
47021
  "src/commands/context.ts"() {
46788
47022
  "use strict";
@@ -46790,7 +47024,7 @@ var init_context = __esm({
46790
47024
  init_workspace_state();
46791
47025
  init_storage();
46792
47026
  init_formatter();
46793
- logger26 = createLogger("cli:context");
47027
+ logger27 = createLogger("cli:context");
46794
47028
  contextCommand = new Command27("context").description("Load session context (workspace state + module summaries) for LLM consumption").option("--json", "Output structured JSON").action(async (opts) => {
46795
47029
  try {
46796
47030
  const ws = await findWorkspaceState();
@@ -46818,7 +47052,7 @@ var init_context = __esm({
46818
47052
  })),
46819
47053
  totalModulesCompleted: sessionCtx.moduleSummaries.length
46820
47054
  };
46821
- logger26.debug("Context loaded", {
47055
+ logger27.debug("Context loaded", {
46822
47056
  courseId: ws.state.courseId,
46823
47057
  moduleSummaries: sessionCtx.moduleSummaries.length
46824
47058
  });
@@ -46832,7 +47066,7 @@ var init_context = __esm({
46832
47066
  } catch (err) {
46833
47067
  const msg = err instanceof Error ? err.message : String(err);
46834
47068
  if (msg.includes("process.exit")) return;
46835
- logger26.warn("context failed", { error: msg });
47069
+ logger27.warn("context failed", { error: msg });
46836
47070
  if (opts.json) jsonError(msg);
46837
47071
  error(msg);
46838
47072
  }
@@ -46842,7 +47076,7 @@ var init_context = __esm({
46842
47076
 
46843
47077
  // src/commands/memory.ts
46844
47078
  import { Command as Command28 } from "commander";
46845
- var logger27, memoryCommand;
47079
+ var logger28, memoryCommand;
46846
47080
  var init_memory = __esm({
46847
47081
  "src/commands/memory.ts"() {
46848
47082
  "use strict";
@@ -46851,7 +47085,7 @@ var init_memory = __esm({
46851
47085
  init_workspace_state();
46852
47086
  init_guards();
46853
47087
  init_formatter();
46854
- logger27 = createLogger("cli:memory");
47088
+ logger28 = createLogger("cli:memory");
46855
47089
  memoryCommand = new Command28("memory").description("Load accumulated student memory (learning profile + recent lessons) for the tutor").option("--json", "Output structured JSON").action(async (opts) => {
46856
47090
  try {
46857
47091
  const ws = await findWorkspaceState();
@@ -46875,7 +47109,7 @@ var init_memory = __esm({
46875
47109
  } catch (err) {
46876
47110
  const msg = err instanceof Error ? err.message : String(err);
46877
47111
  if (msg.includes("process.exit")) return;
46878
- logger27.warn("memory failed", { error: msg });
47112
+ logger28.warn("memory failed", { error: msg });
46879
47113
  if (opts.json) jsonError(msg);
46880
47114
  else error(msg);
46881
47115
  }
@@ -46885,7 +47119,7 @@ var init_memory = __esm({
46885
47119
 
46886
47120
  // src/commands/insight.ts
46887
47121
  import { Command as Command29 } from "commander";
46888
- var logger28, VALID_TYPES, insightCommand;
47122
+ var logger29, VALID_TYPES, insightCommand;
46889
47123
  var init_insight = __esm({
46890
47124
  "src/commands/insight.ts"() {
46891
47125
  "use strict";
@@ -46894,7 +47128,7 @@ var init_insight = __esm({
46894
47128
  init_workspace_state();
46895
47129
  init_guards();
46896
47130
  init_formatter();
46897
- logger28 = createLogger("cli:insight");
47131
+ logger29 = createLogger("cli:insight");
46898
47132
  VALID_TYPES = ["difficulty", "breakthrough", "question"];
46899
47133
  insightCommand = new Command29("insight").description(
46900
47134
  "Persist a student cognitive insight (difficulty | breakthrough | question) into course memory"
@@ -46936,7 +47170,7 @@ var init_insight = __esm({
46936
47170
  } catch (err) {
46937
47171
  const msg = err instanceof Error ? err.message : String(err);
46938
47172
  if (msg.includes("process.exit")) return;
46939
- logger28.warn("insight failed", { error: msg });
47173
+ logger29.warn("insight failed", { error: msg });
46940
47174
  if (opts.json) jsonError(msg);
46941
47175
  else error(msg);
46942
47176
  }
@@ -46949,7 +47183,7 @@ import { Command as Command30 } from "commander";
46949
47183
  function isExerciseLevel2(value) {
46950
47184
  return ALL_LEVELS.includes(value);
46951
47185
  }
46952
- var logger29, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
47186
+ var logger30, ALL_LEVELS, LEVEL_LABELS2, levelCommand;
46953
47187
  var init_level = __esm({
46954
47188
  "src/commands/level.ts"() {
46955
47189
  "use strict";
@@ -46958,7 +47192,7 @@ var init_level = __esm({
46958
47192
  init_exercises();
46959
47193
  init_guards();
46960
47194
  init_formatter();
46961
- logger29 = createLogger("cli:level");
47195
+ logger30 = createLogger("cli:level");
46962
47196
  ALL_LEVELS = ["L0", "L1", "L2", "L3", "L4"];
46963
47197
  LEVEL_LABELS2 = {
46964
47198
  L0: "Pr\xE9-check (perguntas conceituais)",
@@ -46972,7 +47206,7 @@ var init_level = __esm({
46972
47206
  const session = await requireSession();
46973
47207
  const activeCourse = await requireActiveCourse();
46974
47208
  const data = createHttpProvider(session.apiUrl, session.token);
46975
- const deps = { data, logger: logger29 };
47209
+ const deps = { data, logger: logger30 };
46976
47210
  if (!rawLevel) {
46977
47211
  const levels = await getEnrollmentLevels({ enrollmentId: activeCourse.enrollmentId }, deps);
46978
47212
  if (opts.json) {
@@ -47031,7 +47265,7 @@ var init_level = __esm({
47031
47265
 
47032
47266
  // src/commands/theory.ts
47033
47267
  import { Command as Command31 } from "commander";
47034
- var logger30, EXERCISE_LEVELS2, theoryCommand;
47268
+ var logger31, EXERCISE_LEVELS2, theoryCommand;
47035
47269
  var init_theory = __esm({
47036
47270
  "src/commands/theory.ts"() {
47037
47271
  "use strict";
@@ -47039,7 +47273,7 @@ var init_theory = __esm({
47039
47273
  init_guards();
47040
47274
  init_formatter();
47041
47275
  init_errors();
47042
- logger30 = createLogger("cli:theory");
47276
+ logger31 = createLogger("cli:theory");
47043
47277
  EXERCISE_LEVELS2 = ["L0", "L1", "L2", "L3", "L4"];
47044
47278
  theoryCommand = new Command31("theory").description("Request more theory on the current lesson (escape hatch)").option("--focus <text>", "Optional focus area in PT-BR (max 500 chars)").option("--level <L0|L1|L2|L3|L4>", "Override current exercise level (otherwise auto-detect)").option("--lesson <uuid>", "Override lesson ID (defaults to current workspace lesson)").option("--json", "Output structured JSON").action(async (opts) => {
47045
47279
  try {
@@ -47078,7 +47312,7 @@ var init_theory = __esm({
47078
47312
  currentLevel = levels.currentLevel;
47079
47313
  }
47080
47314
  const requestUrl = `${session.apiUrl}/api/cli/exercises/more-theory`;
47081
- logger30.debug("Requesting more theory", { lessonId, currentLevel });
47315
+ logger31.debug("Requesting more theory", { lessonId, currentLevel });
47082
47316
  const res = await fetch(requestUrl, {
47083
47317
  method: "POST",
47084
47318
  headers: {
@@ -47483,7 +47717,7 @@ async function runExportGrades(opts, deps = defaultDeps5) {
47483
47717
  if (!rendered.endsWith("\n")) rendered += "\n";
47484
47718
  deps.stdoutWrite(rendered);
47485
47719
  }
47486
- var logger31, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
47720
+ var logger32, SUPPORTED_FORMATS, defaultDeps5, exportGradesCommand;
47487
47721
  var init_export_grades = __esm({
47488
47722
  "src/commands/export-grades.ts"() {
47489
47723
  "use strict";
@@ -47491,7 +47725,7 @@ var init_export_grades = __esm({
47491
47725
  init_guards();
47492
47726
  init_grades();
47493
47727
  init_resolve_enrollment();
47494
- logger31 = createLogger("cli:export-grades");
47728
+ logger32 = createLogger("cli:export-grades");
47495
47729
  SUPPORTED_FORMATS = ["json", "csv", "md"];
47496
47730
  defaultDeps5 = {
47497
47731
  requireSession,
@@ -47501,7 +47735,7 @@ var init_export_grades = __esm({
47501
47735
  stderrWrite: (message) => process.stderr.write(message),
47502
47736
  // process.exit is typed as `never` — wrap so the cast is local to one line.
47503
47737
  exit: (code) => process.exit(code),
47504
- logger: logger31,
47738
+ logger: logger32,
47505
47739
  resolveBySlug: resolveEnrollmentBySlug
47506
47740
  };
47507
47741
  exportGradesCommand = new Command32("export-grades").description("Exporta o hist\xF3rico de valida\xE7\xF5es (notas) do curso ativo").option(
@@ -47670,7 +47904,7 @@ async function runGrades(opts, deps = defaultDeps6) {
47670
47904
  );
47671
47905
  deps.stdoutWrite(formatGradesAllEnrollmentsTable(summaries) + "\n");
47672
47906
  }
47673
- var logger32, defaultDeps6, gradesCommand;
47907
+ var logger33, defaultDeps6, gradesCommand;
47674
47908
  var init_grades2 = __esm({
47675
47909
  "src/commands/grades.ts"() {
47676
47910
  "use strict";
@@ -47680,14 +47914,14 @@ var init_grades2 = __esm({
47680
47914
  init_guards();
47681
47915
  init_grades();
47682
47916
  init_resolve_enrollment();
47683
- logger32 = createLogger("cli:grades");
47917
+ logger33 = createLogger("cli:grades");
47684
47918
  defaultDeps6 = {
47685
47919
  requireSession,
47686
47920
  fetch: globalThis.fetch.bind(globalThis),
47687
47921
  stdoutWrite: (message) => process.stdout.write(message),
47688
47922
  stderrWrite: (message) => process.stderr.write(message),
47689
47923
  exit: (code) => process.exit(code),
47690
- logger: logger32,
47924
+ logger: logger33,
47691
47925
  resolveBySlug: resolveEnrollmentBySlug,
47692
47926
  buildDataProvider: createHttpProvider,
47693
47927
  listCoursesFn: listCourses
@@ -47852,7 +48086,7 @@ async function runReview(slug, opts, deps = defaultDeps7) {
47852
48086
  }) + "\n"
47853
48087
  );
47854
48088
  }
47855
- var logger33, defaultDeps7, reviewCommand;
48089
+ var logger34, defaultDeps7, reviewCommand;
47856
48090
  var init_review2 = __esm({
47857
48091
  "src/commands/review.ts"() {
47858
48092
  "use strict";
@@ -47860,14 +48094,14 @@ var init_review2 = __esm({
47860
48094
  init_guards();
47861
48095
  init_grades();
47862
48096
  init_resolve_enrollment();
47863
- logger33 = createLogger("cli:review");
48097
+ logger34 = createLogger("cli:review");
47864
48098
  defaultDeps7 = {
47865
48099
  requireSession,
47866
48100
  fetch: globalThis.fetch.bind(globalThis),
47867
48101
  stdoutWrite: (message) => process.stdout.write(message),
47868
48102
  stderrWrite: (message) => process.stderr.write(message),
47869
48103
  exit: (code) => process.exit(code),
47870
- logger: logger33,
48104
+ logger: logger34,
47871
48105
  resolveBySlug: resolveEnrollmentBySlug,
47872
48106
  promptLessonChoice: defaultPromptLessonChoice
47873
48107
  };