@tostudy-ai/cli 0.16.3 → 0.16.5

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,7 +41,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
41
41
  mod
42
42
  ));
43
43
 
44
- // ../../node_modules/@parisgroup-ai/logger/dist/sanitize-4AyoT9ic.mjs
44
+ // ../../node_modules/@parisgroup-ai/logger/dist/sanitize-DSkXy3jL.mjs
45
45
  function isSensitiveKey(key) {
46
46
  if (matchesSensitiveField(key)) return true;
47
47
  return matchesPluralOfSensitiveField(key);
@@ -114,11 +114,12 @@ function sanitizeRecord(obj, ctx) {
114
114
  if (ctx.visited.has(obj)) return "[Circular]";
115
115
  ctx.visited.add(obj);
116
116
  const sanitized = { ...obj };
117
+ const mask = ctx.redactMask ?? "[REDACTED]";
117
118
  for (const [key, value] of Object.entries(sanitized)) {
118
119
  const isAdditionalKey = ctx.additionalSensitiveKeys?.some((k) => key.toLowerCase() === k.toLowerCase());
119
120
  const pluralCountExemption = typeof value === "number" && !isAdditionalKey && !matchesSensitiveField(key) && matchesPluralOfSensitiveField(key);
120
121
  if ((isSensitiveKey(key) || isAdditionalKey) && !pluralCountExemption) {
121
- sanitized[key] = "[REDACTED]";
122
+ sanitized[key] = mask;
122
123
  continue;
123
124
  }
124
125
  if (value instanceof Error) {
@@ -139,16 +140,17 @@ function sanitizeRecord(obj, ctx) {
139
140
  ctx.visited.delete(obj);
140
141
  return sanitized;
141
142
  }
142
- function sanitizeEntryData(obj, additionalSensitiveKeys, includeStack) {
143
+ function sanitizeEntryData(obj, additionalSensitiveKeys, includeStack, redactMask) {
143
144
  return sanitizeRecord(obj, {
144
145
  additionalSensitiveKeys,
145
146
  includeStack,
146
- visited: /* @__PURE__ */ new WeakSet()
147
+ visited: /* @__PURE__ */ new WeakSet(),
148
+ redactMask
147
149
  });
148
150
  }
149
151
  var SENSITIVE_FIELDS;
150
- var init_sanitize_4AyoT9ic = __esm({
151
- "../../node_modules/@parisgroup-ai/logger/dist/sanitize-4AyoT9ic.mjs"() {
152
+ var init_sanitize_DSkXy3jL = __esm({
153
+ "../../node_modules/@parisgroup-ai/logger/dist/sanitize-DSkXy3jL.mjs"() {
152
154
  SENSITIVE_FIELDS = [
153
155
  "password",
154
156
  "senha",
@@ -279,7 +281,7 @@ var require_picocolors = __commonJS({
279
281
  }
280
282
  });
281
283
 
282
- // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-DOZPgsDx.mjs
284
+ // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-DkbC4pBB.mjs
283
285
  function formatJson(entry) {
284
286
  const { timestamp, level, message, service, environment, context, data, error: error49 } = entry;
285
287
  const output3 = {
@@ -383,9 +385,9 @@ function extractErrorInfo(error49, includeStack, visited = /* @__PURE__ */ new W
383
385
  return info;
384
386
  }
385
387
  var import_picocolors, LOG_LEVELS, LEVEL_COLORS, LEVEL_WIDTH, BaseLogger;
386
- var init_logger_base_DOZPgsDx = __esm({
387
- "../../node_modules/@parisgroup-ai/logger/dist/logger.base-DOZPgsDx.mjs"() {
388
- init_sanitize_4AyoT9ic();
388
+ var init_logger_base_DkbC4pBB = __esm({
389
+ "../../node_modules/@parisgroup-ai/logger/dist/logger.base-DkbC4pBB.mjs"() {
390
+ init_sanitize_DSkXy3jL();
389
391
  import_picocolors = __toESM(require_picocolors(), 1);
390
392
  LOG_LEVELS = {
391
393
  trace: 0,
@@ -414,6 +416,8 @@ var init_logger_base_DOZPgsDx = __esm({
414
416
  transports;
415
417
  shouldSanitize;
416
418
  sensitiveFields;
419
+ redactMask;
420
+ sampleRate;
417
421
  includeCaller;
418
422
  piiFilter;
419
423
  constructor(options, defaults) {
@@ -426,6 +430,8 @@ var init_logger_base_DOZPgsDx = __esm({
426
430
  this.transports = [];
427
431
  this.shouldSanitize = options.sanitize ?? true;
428
432
  this.sensitiveFields = options.sensitiveFields;
433
+ this.redactMask = options.redactMask;
434
+ this.sampleRate = options.sampleRate;
429
435
  this.includeCaller = options.includeCaller ?? "errors";
430
436
  this.piiFilter = options.piiFilter;
431
437
  }
@@ -440,14 +446,19 @@ var init_logger_base_DOZPgsDx = __esm({
440
446
  flush() {
441
447
  }
442
448
  shouldLog(level) {
443
- return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
449
+ if (LOG_LEVELS[level] < LOG_LEVELS[this.level]) return false;
450
+ if (this.sampleRate !== void 0 && (level === "trace" || level === "debug" || level === "info")) {
451
+ if (this.sampleRate <= 0) return false;
452
+ if (this.sampleRate < 1 && Math.random() > this.sampleRate) return false;
453
+ }
454
+ return true;
444
455
  }
445
456
  buildEntry(level, message, data, error49) {
446
457
  const merged = {
447
458
  ...this.getLogContext() ?? {},
448
459
  ...this.additionalContext
449
460
  };
450
- const context = this.shouldSanitize ? sanitizeEntryData(merged, this.sensitiveFields, !this.isProd()) : merged;
461
+ const context = this.shouldSanitize ? sanitizeEntryData(merged, this.sensitiveFields, !this.isProd(), this.redactMask) : merged;
451
462
  if (this.module) context.module = this.module;
452
463
  if (this.includeCaller === true || this.includeCaller === "errors" && (level === "error" || level === "fatal")) {
453
464
  const caller = this.getCaller();
@@ -478,11 +489,11 @@ var init_logger_base_DOZPgsDx = __esm({
478
489
  if (typeof data === "string" || typeof data === "number" || typeof data === "boolean") return { value: data };
479
490
  if (Array.isArray(data)) {
480
491
  const wrapped = { items: data };
481
- return this.shouldSanitize ? sanitizeEntryData(wrapped, this.sensitiveFields, includeStack) : wrapped;
492
+ return this.shouldSanitize ? sanitizeEntryData(wrapped, this.sensitiveFields, includeStack, this.redactMask) : wrapped;
482
493
  }
483
494
  if (typeof data === "object") {
484
495
  const obj = data;
485
- return this.shouldSanitize ? sanitizeEntryData(obj, this.sensitiveFields, includeStack) : obj;
496
+ return this.shouldSanitize ? sanitizeEntryData(obj, this.sensitiveFields, includeStack, this.redactMask) : obj;
486
497
  }
487
498
  return { value: String(data) };
488
499
  }
@@ -590,16 +601,60 @@ import { join } from "node:path";
590
601
  function getLogContext() {
591
602
  return asyncLocalStorage.getStore();
592
603
  }
593
- function globalSlot() {
594
- return globalThis;
604
+ function getOrCreateGlobalSingleton(key, factory) {
605
+ const slot = globalThis;
606
+ const existing = slot[key];
607
+ if (existing) return existing;
608
+ const created = factory();
609
+ slot[key] = created;
610
+ return created;
595
611
  }
596
612
  function getOrCreateHttpTransport(options) {
597
- const slot = globalSlot();
598
- const existing = slot[SINGLETON_KEY];
599
- if (existing) return existing;
600
- const transport = new HttpTransport(options);
601
- slot[SINGLETON_KEY] = transport;
602
- return transport;
613
+ return getOrCreateGlobalSingleton(SINGLETON_KEY$1, () => new HttpTransport(options));
614
+ }
615
+ function toAttributeValue(value) {
616
+ if (typeof value === "boolean") return { boolValue: value };
617
+ if (typeof value === "number") return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
618
+ if (value == null) return { stringValue: "" };
619
+ if (typeof value === "object") try {
620
+ return { stringValue: JSON.stringify(value) };
621
+ } catch {
622
+ return { stringValue: String(value) };
623
+ }
624
+ return { stringValue: String(value) };
625
+ }
626
+ function toAttributes(record2) {
627
+ return Object.entries(record2).map(([key, value]) => ({
628
+ key,
629
+ value: toAttributeValue(value)
630
+ }));
631
+ }
632
+ function toLogRecord(entry) {
633
+ const { number: number4, text } = SEVERITY[entry.level] ?? SEVERITY.info;
634
+ const attributes = {
635
+ ...entry.context,
636
+ ...entry.data
637
+ };
638
+ return {
639
+ timeUnixNano: String(BigInt(Date.parse(entry.timestamp)) * 1000000n),
640
+ severityNumber: number4,
641
+ severityText: text,
642
+ body: { stringValue: entry.message },
643
+ attributes: toAttributes(attributes)
644
+ };
645
+ }
646
+ function getOrCreateOtlpLogsTransport(options) {
647
+ return getOrCreateGlobalSingleton(SINGLETON_KEY, () => new OtlpLogsTransport(options));
648
+ }
649
+ function parseOtlpResourceAttributes(raw) {
650
+ if (!raw) return {};
651
+ const out = {};
652
+ for (const pair of raw.split(",")) {
653
+ const eq = pair.indexOf("=");
654
+ if (eq <= 0) continue;
655
+ out[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
656
+ }
657
+ return out;
603
658
  }
604
659
  function detectEnvironment() {
605
660
  const isProd = process.env.NODE_ENV === "production" || !!process.env.VERCEL || !!process.env.RAILWAY_ENVIRONMENT;
@@ -650,11 +705,11 @@ function createLogger(module, options) {
650
705
  module
651
706
  });
652
707
  }
653
- var asyncLocalStorage, ConsoleTransport, FileTransport, HttpTransport, SINGLETON_KEY, Logger, logger;
708
+ var asyncLocalStorage, ConsoleTransport, FileTransport, HttpTransport, SINGLETON_KEY$1, SEVERITY, OtlpLogsTransport, SINGLETON_KEY, Logger, logger;
654
709
  var init_dist = __esm({
655
710
  "../../node_modules/@parisgroup-ai/logger/dist/index.mjs"() {
656
- init_logger_base_DOZPgsDx();
657
- init_sanitize_4AyoT9ic();
711
+ init_logger_base_DkbC4pBB();
712
+ init_sanitize_DSkXy3jL();
658
713
  asyncLocalStorage = new AsyncLocalStorage();
659
714
  ConsoleTransport = class {
660
715
  log(entry, formatted) {
@@ -758,7 +813,113 @@ var init_dist = __esm({
758
813
  });
759
814
  }
760
815
  };
761
- SINGLETON_KEY = /* @__PURE__ */ Symbol.for("@repo/logger.sharedHttpTransport");
816
+ SINGLETON_KEY$1 = /* @__PURE__ */ Symbol.for("@repo/logger.sharedHttpTransport");
817
+ SEVERITY = {
818
+ trace: {
819
+ number: 1,
820
+ text: "TRACE"
821
+ },
822
+ debug: {
823
+ number: 5,
824
+ text: "DEBUG"
825
+ },
826
+ info: {
827
+ number: 9,
828
+ text: "INFO"
829
+ },
830
+ warn: {
831
+ number: 13,
832
+ text: "WARN"
833
+ },
834
+ error: {
835
+ number: 17,
836
+ text: "ERROR"
837
+ },
838
+ fatal: {
839
+ number: 21,
840
+ text: "FATAL"
841
+ }
842
+ };
843
+ OtlpLogsTransport = class {
844
+ buffer = [];
845
+ timer = null;
846
+ flushing = false;
847
+ disposed = false;
848
+ url;
849
+ serviceName;
850
+ resourceAttributes;
851
+ extraHeaders;
852
+ maxBufferSize;
853
+ flushInterval;
854
+ onError;
855
+ constructor(options) {
856
+ this.url = `${options.endpoint.replace(/\/+$/, "")}/v1/logs`;
857
+ this.serviceName = options.serviceName;
858
+ this.resourceAttributes = options.resourceAttributes ?? {};
859
+ this.extraHeaders = options.headers ?? {};
860
+ this.maxBufferSize = options.maxBufferSize ?? 50;
861
+ this.flushInterval = options.flushInterval ?? 5e3;
862
+ this.onError = options.onError;
863
+ this.timer = setInterval(() => {
864
+ this.flush().catch(() => {
865
+ });
866
+ }, this.flushInterval);
867
+ process.once("beforeExit", () => this.dispose());
868
+ process.once("SIGTERM", () => this.dispose());
869
+ }
870
+ log(entry, _formatted) {
871
+ if (this.disposed) return;
872
+ this.buffer.push(entry);
873
+ if (this.buffer.length >= this.maxBufferSize) this.flush().catch(() => {
874
+ });
875
+ }
876
+ serialize(batch) {
877
+ const resourceAttrs = {
878
+ "service.name": this.serviceName,
879
+ ...this.resourceAttributes
880
+ };
881
+ return JSON.stringify({ resourceLogs: [{
882
+ resource: { attributes: toAttributes(resourceAttrs) },
883
+ scopeLogs: [{
884
+ scope: { name: "@parisgroup-ai/logger" },
885
+ logRecords: batch.map(toLogRecord)
886
+ }]
887
+ }] });
888
+ }
889
+ async flush() {
890
+ if (this.flushing || this.buffer.length === 0) return;
891
+ this.flushing = true;
892
+ const batch = this.buffer.splice(0);
893
+ try {
894
+ const response = await fetch(this.url, {
895
+ method: "POST",
896
+ headers: {
897
+ "Content-Type": "application/json",
898
+ ...this.extraHeaders
899
+ },
900
+ body: this.serialize(batch),
901
+ signal: AbortSignal.timeout(1e4)
902
+ });
903
+ if (!response.ok) throw new Error(`OTLP log ingest failed: ${response.status} ${response.statusText}`);
904
+ } catch (error49) {
905
+ if (this.buffer.length < this.maxBufferSize * 3) this.buffer.unshift(...batch);
906
+ this.onError?.(error49, batch.length);
907
+ } finally {
908
+ this.flushing = false;
909
+ }
910
+ }
911
+ dispose() {
912
+ if (this.disposed) return;
913
+ this.disposed = true;
914
+ if (this.timer) {
915
+ clearInterval(this.timer);
916
+ this.timer = null;
917
+ }
918
+ if (this.buffer.length > 0) this.flush().catch(() => {
919
+ });
920
+ }
921
+ };
922
+ SINGLETON_KEY = /* @__PURE__ */ Symbol.for("@repo/logger.sharedOtlpLogsTransport");
762
923
  Logger = class Logger2 extends BaseLogger {
763
924
  fileTransport;
764
925
  _isProd;
@@ -783,6 +944,16 @@ var init_dist = __esm({
783
944
  authorization: `Bearer ${drainToken}`,
784
945
  onError: (err, count) => {
785
946
  process.stderr.write(`[logger] HTTP transport error: ${err.message} (${count} entries)
947
+ `);
948
+ }
949
+ }));
950
+ const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
951
+ if (otlpEndpoint) this.transports.push(getOrCreateOtlpLogsTransport({
952
+ endpoint: otlpEndpoint,
953
+ serviceName: process.env.OTEL_SERVICE_NAME || getDefaultService(),
954
+ resourceAttributes: parseOtlpResourceAttributes(process.env.OTEL_RESOURCE_ATTRIBUTES),
955
+ onError: (err, count) => {
956
+ process.stderr.write(`[logger] OTLP transport error: ${err.message} (${count} entries)
786
957
  `);
787
958
  }
788
959
  }));
@@ -812,6 +983,8 @@ var init_dist = __esm({
812
983
  environment: this.environment,
813
984
  sanitize: this.shouldSanitize,
814
985
  sensitiveFields: this.sensitiveFields,
986
+ redactMask: this.redactMask,
987
+ sampleRate: this.sampleRate,
815
988
  includeCaller: this.includeCaller,
816
989
  piiFilter: this.piiFilter
817
990
  });
@@ -1414,7 +1587,7 @@ function formatCourseList(courses) {
1414
1587
  return [
1415
1588
  "Nenhum curso encontrado.",
1416
1589
  "",
1417
- "\u2192 tostudy enroll <curso> para se matricular em um curso"
1590
+ "\u2192 Acesse tostudy.ai para se matricular em um curso"
1418
1591
  ].join("\n");
1419
1592
  }
1420
1593
  const lines = ["Seus cursos:", ""];
@@ -2578,6 +2751,101 @@ var init_user_profile = __esm({
2578
2751
  }
2579
2752
  });
2580
2753
 
2754
+ // src/errors/errors-pt-br.ts
2755
+ var ptBrErrors;
2756
+ var init_errors_pt_br = __esm({
2757
+ "src/errors/errors-pt-br.ts"() {
2758
+ "use strict";
2759
+ ptBrErrors = {
2760
+ notFound: "n\xE3o encontrado",
2761
+ workspaceNotFoundShort: "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n",
2762
+ workspaceNotFoundFromExport: "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' ou rode 'tostudy select' desta pasta.\n",
2763
+ vaultNotFound: "\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n",
2764
+ logoutSuccess: "\n Deslogado com sucesso.\n",
2765
+ workspaceCommandDescription: "Gerenciar workspace de estudo local",
2766
+ workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2767
+ insufficientCredits: "\u274C Voc\xEA est\xE1 sem cr\xE9ditos. Cada resposta do tutor de IA consome cr\xE9ditos para cobrir o processamento. Recarregue em https://tostudy.ai/student/credits para continuar de onde parou.\n",
2768
+ workspaceRefusedAtHome: "N\xE3o d\xE1 para configurar o estudo direto na sua pasta pessoal.\nCrie uma pasta para o curso e entre nela antes de continuar:\n\n mkdir ~/meus-cursos && cd ~/meus-cursos\n",
2769
+ courseArchived: "Este curso foi arquivado e est\xE1 dispon\xEDvel somente para leitura.\nUse `tostudy lesson` para revisar o conte\xFAdo j\xE1 estudado.\n",
2770
+ enrollmentNotEntitled: "\u{1F6AB} Sua matr\xEDcula neste curso n\xE3o est\xE1 mais ativa (reembolso, cancelamento ou preview expirado).\nPara voltar a estudar, adquira o curso novamente em https://tostudy.ai.\n"
2771
+ };
2772
+ }
2773
+ });
2774
+
2775
+ // src/errors/errors-en-us.ts
2776
+ var enUsErrors;
2777
+ var init_errors_en_us = __esm({
2778
+ "src/errors/errors-en-us.ts"() {
2779
+ "use strict";
2780
+ enUsErrors = {
2781
+ notFound: "not found",
2782
+ workspaceNotFoundShort: "\u274C Workspace not found. Run 'tostudy workspace setup' first.\n",
2783
+ workspaceNotFoundFromExport: "\u274C Workspace not found. Run 'tostudy workspace setup' or 'tostudy select' from this folder.\n",
2784
+ vaultNotFound: "\u274C Vault not found. Run 'tostudy vault init' first.\n",
2785
+ logoutSuccess: "\n Logged out successfully.\n",
2786
+ workspaceCommandDescription: "Manage local study workspace",
2787
+ workspaceSetupDescription: "Create the workspace structure for the active course",
2788
+ insufficientCredits: "\u274C You're out of credits. Every reply from the AI tutor uses credits to cover processing. Top up at https://tostudy.ai/student/credits to continue from where you stopped.\n",
2789
+ workspaceRefusedAtHome: "Study can't be set up directly in your home folder.\nCreate a folder for the course and switch into it first:\n\n mkdir ~/my-courses && cd ~/my-courses\n",
2790
+ courseArchived: "This course has been archived and is available for reading only.\nUse `tostudy lesson` to review the content you've already studied.\n",
2791
+ enrollmentNotEntitled: "\u{1F6AB} Your enrollment in this course is no longer active (refund, cancellation, or expired preview).\nTo study again, purchase the course at https://tostudy.ai.\n"
2792
+ };
2793
+ }
2794
+ });
2795
+
2796
+ // src/errors/index.ts
2797
+ function resolveLocale() {
2798
+ if (_cachedLocale !== null) return _cachedLocale;
2799
+ const argv = process.argv;
2800
+ const flagIdx = argv.indexOf("--locale");
2801
+ if (flagIdx !== -1 && flagIdx + 1 < argv.length) {
2802
+ const val = argv[flagIdx + 1];
2803
+ if (val === "pt-BR" || val === "en-US") {
2804
+ _cachedLocale = val;
2805
+ return val;
2806
+ }
2807
+ }
2808
+ const lang = (process.env["LANG"] ?? "").toLowerCase();
2809
+ if (lang.startsWith("en")) {
2810
+ _cachedLocale = "en-US";
2811
+ return "en-US";
2812
+ }
2813
+ _cachedLocale = "pt-BR";
2814
+ return "pt-BR";
2815
+ }
2816
+ function getErrors(locale) {
2817
+ return BUNDLES[locale ?? resolveLocale()];
2818
+ }
2819
+ function resolveErrorCode(err) {
2820
+ if (err && typeof err === "object" && "code" in err) {
2821
+ const code = err.code;
2822
+ if (typeof code === "string" && code.length > 0) return code;
2823
+ }
2824
+ return err instanceof Error ? err.message : String(err);
2825
+ }
2826
+ function isInsufficientCreditsError(err) {
2827
+ return resolveErrorCode(err).includes("INSUFFICIENT_CREDITS");
2828
+ }
2829
+ function isCourseArchivedError(err) {
2830
+ return resolveErrorCode(err).includes("COURSE_ARCHIVED");
2831
+ }
2832
+ function isEnrollmentNotEntitledError(err) {
2833
+ return resolveErrorCode(err).includes("ENROLLMENT_NOT_ENTITLED");
2834
+ }
2835
+ var BUNDLES, _cachedLocale;
2836
+ var init_errors = __esm({
2837
+ "src/errors/index.ts"() {
2838
+ "use strict";
2839
+ init_errors_pt_br();
2840
+ init_errors_en_us();
2841
+ BUNDLES = {
2842
+ "pt-BR": ptBrErrors,
2843
+ "en-US": enUsErrors
2844
+ };
2845
+ _cachedLocale = null;
2846
+ }
2847
+ });
2848
+
2581
2849
  // src/workspace/workspace-marker.ts
2582
2850
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync2 } from "node:fs";
2583
2851
  import os4 from "node:os";
@@ -2607,9 +2875,7 @@ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
2607
2875
  const homeDir = path7.resolve(options.homeDir ?? os4.homedir());
2608
2876
  const resolvedWorkspace = path7.resolve(workspacePath);
2609
2877
  if (resolvedWorkspace === homeDir || homeDir.startsWith(resolvedWorkspace + path7.sep)) {
2610
- throw new Error(
2611
- `Refusing to write workspace marker at ${workspacePath}: it is the home directory or an ancestor of it.`
2612
- );
2878
+ throw new Error(getErrors().workspaceRefusedAtHome);
2613
2879
  }
2614
2880
  const filePath = resolveMarkerPath(workspacePath);
2615
2881
  const dir = path7.dirname(filePath);
@@ -2634,6 +2900,7 @@ var CURRENT_VERSION, SUPPORTED_VERSIONS;
2634
2900
  var init_workspace_marker = __esm({
2635
2901
  "src/workspace/workspace-marker.ts"() {
2636
2902
  "use strict";
2903
+ init_errors();
2637
2904
  CURRENT_VERSION = 1;
2638
2905
  SUPPORTED_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
2639
2906
  }
@@ -2785,99 +3052,6 @@ var init_course_state = __esm({
2785
3052
  }
2786
3053
  });
2787
3054
 
2788
- // src/errors/errors-pt-br.ts
2789
- var ptBrErrors;
2790
- var init_errors_pt_br = __esm({
2791
- "src/errors/errors-pt-br.ts"() {
2792
- "use strict";
2793
- ptBrErrors = {
2794
- notFound: "n\xE3o encontrado",
2795
- workspaceNotFoundShort: "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n",
2796
- workspaceNotFoundFromExport: "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' ou rode 'tostudy select' desta pasta.\n",
2797
- vaultNotFound: "\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n",
2798
- logoutSuccess: "\n Deslogado com sucesso.\n",
2799
- workspaceCommandDescription: "Gerenciar workspace de estudo local",
2800
- workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2801
- insufficientCredits: "\u274C Voc\xEA est\xE1 sem cr\xE9ditos. Cada resposta do tutor de IA consome cr\xE9ditos para cobrir o processamento. Recarregue em https://tostudy.ai/student/credits para continuar de onde parou.\n",
2802
- courseArchived: "Este curso foi arquivado e est\xE1 dispon\xEDvel somente para leitura.\nUse `tostudy lesson` para revisar o conte\xFAdo j\xE1 estudado.\n",
2803
- enrollmentNotEntitled: "\u{1F6AB} Sua matr\xEDcula neste curso n\xE3o est\xE1 mais ativa (reembolso, cancelamento ou preview expirado).\nPara voltar a estudar, adquira o curso novamente em https://tostudy.ai.\n"
2804
- };
2805
- }
2806
- });
2807
-
2808
- // src/errors/errors-en-us.ts
2809
- var enUsErrors;
2810
- var init_errors_en_us = __esm({
2811
- "src/errors/errors-en-us.ts"() {
2812
- "use strict";
2813
- enUsErrors = {
2814
- notFound: "not found",
2815
- workspaceNotFoundShort: "\u274C Workspace not found. Run 'tostudy workspace setup' first.\n",
2816
- workspaceNotFoundFromExport: "\u274C Workspace not found. Run 'tostudy workspace setup' or 'tostudy select' from this folder.\n",
2817
- vaultNotFound: "\u274C Vault not found. Run 'tostudy vault init' first.\n",
2818
- logoutSuccess: "\n Logged out successfully.\n",
2819
- workspaceCommandDescription: "Manage local study workspace",
2820
- workspaceSetupDescription: "Create the workspace structure for the active course",
2821
- insufficientCredits: "\u274C You're out of credits. Every reply from the AI tutor uses credits to cover processing. Top up at https://tostudy.ai/student/credits to continue from where you stopped.\n",
2822
- courseArchived: "This course has been archived and is available for reading only.\nUse `tostudy lesson` to review the content you've already studied.\n",
2823
- enrollmentNotEntitled: "\u{1F6AB} Your enrollment in this course is no longer active (refund, cancellation, or expired preview).\nTo study again, purchase the course at https://tostudy.ai.\n"
2824
- };
2825
- }
2826
- });
2827
-
2828
- // src/errors/index.ts
2829
- function resolveLocale() {
2830
- if (_cachedLocale !== null) return _cachedLocale;
2831
- const argv = process.argv;
2832
- const flagIdx = argv.indexOf("--locale");
2833
- if (flagIdx !== -1 && flagIdx + 1 < argv.length) {
2834
- const val = argv[flagIdx + 1];
2835
- if (val === "pt-BR" || val === "en-US") {
2836
- _cachedLocale = val;
2837
- return val;
2838
- }
2839
- }
2840
- const lang = (process.env["LANG"] ?? "").toLowerCase();
2841
- if (lang.startsWith("en")) {
2842
- _cachedLocale = "en-US";
2843
- return "en-US";
2844
- }
2845
- _cachedLocale = "pt-BR";
2846
- return "pt-BR";
2847
- }
2848
- function getErrors(locale) {
2849
- return BUNDLES[locale ?? resolveLocale()];
2850
- }
2851
- function resolveErrorCode(err) {
2852
- if (err && typeof err === "object" && "code" in err) {
2853
- const code = err.code;
2854
- if (typeof code === "string" && code.length > 0) return code;
2855
- }
2856
- return err instanceof Error ? err.message : String(err);
2857
- }
2858
- function isInsufficientCreditsError(err) {
2859
- return resolveErrorCode(err).includes("INSUFFICIENT_CREDITS");
2860
- }
2861
- function isCourseArchivedError(err) {
2862
- return resolveErrorCode(err).includes("COURSE_ARCHIVED");
2863
- }
2864
- function isEnrollmentNotEntitledError(err) {
2865
- return resolveErrorCode(err).includes("ENROLLMENT_NOT_ENTITLED");
2866
- }
2867
- var BUNDLES, _cachedLocale;
2868
- var init_errors = __esm({
2869
- "src/errors/index.ts"() {
2870
- "use strict";
2871
- init_errors_pt_br();
2872
- init_errors_en_us();
2873
- BUNDLES = {
2874
- "pt-BR": ptBrErrors,
2875
- "en-US": enUsErrors
2876
- };
2877
- _cachedLocale = null;
2878
- }
2879
- });
2880
-
2881
3055
  // src/commands/logout.ts
2882
3056
  import { Command as Command2 } from "commander";
2883
3057
  var logoutCommand;
@@ -3196,7 +3370,7 @@ var CLI_VERSION;
3196
3370
  var init_version = __esm({
3197
3371
  "src/version.ts"() {
3198
3372
  "use strict";
3199
- CLI_VERSION = true ? "0.16.3" : "0.7.1";
3373
+ CLI_VERSION = true ? "0.16.5" : "0.7.1";
3200
3374
  }
3201
3375
  });
3202
3376
 
@@ -22238,7 +22412,10 @@ var init_export_grades = __esm({
22238
22412
  defaultDeps5 = {
22239
22413
  requireSession,
22240
22414
  requireActiveCourse,
22241
- fetch: globalThis.fetch.bind(globalThis),
22415
+ // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
22416
+ // antes de qualquer guard. Em runtime sem `fetch` global isso derruba o import
22417
+ // inteiro do cli.ts e, com ele, TODO comando. Encaminhar mantem o load inerte.
22418
+ fetch: (...args) => globalThis.fetch(...args),
22242
22419
  stdoutWrite: (message) => process.stdout.write(message),
22243
22420
  stderrWrite: (message) => process.stderr.write(message),
22244
22421
  // process.exit is typed as `never` — wrap so the cast is local to one line.
@@ -22425,7 +22602,10 @@ var init_grades2 = __esm({
22425
22602
  logger33 = createLogger("cli:grades");
22426
22603
  defaultDeps6 = {
22427
22604
  requireSession,
22428
- fetch: globalThis.fetch.bind(globalThis),
22605
+ // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
22606
+ // antes de qualquer guard. Em runtime sem `fetch` global isso derruba o import
22607
+ // inteiro do cli.ts e, com ele, TODO comando. Encaminhar mantem o load inerte.
22608
+ fetch: (...args) => globalThis.fetch(...args),
22429
22609
  stdoutWrite: (message) => process.stdout.write(message),
22430
22610
  stderrWrite: (message) => process.stderr.write(message),
22431
22611
  exit: (code) => process.exit(code),
@@ -22605,7 +22785,10 @@ var init_review2 = __esm({
22605
22785
  logger34 = createLogger("cli:review");
22606
22786
  defaultDeps7 = {
22607
22787
  requireSession,
22608
- fetch: globalThis.fetch.bind(globalThis),
22788
+ // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
22789
+ // antes de qualquer guard. Em runtime sem `fetch` global isso derruba o import
22790
+ // inteiro do cli.ts e, com ele, TODO comando. Encaminhar mantem o load inerte.
22791
+ fetch: (...args) => globalThis.fetch(...args),
22609
22792
  stdoutWrite: (message) => process.stdout.write(message),
22610
22793
  stderrWrite: (message) => process.stderr.write(message),
22611
22794
  exit: (code) => process.exit(code),
@@ -22790,6 +22973,21 @@ var init_auto_updater = __esm({
22790
22973
  if (!process.env.LOG_LEVEL) {
22791
22974
  process.env.LOG_LEVEL = "fatal";
22792
22975
  }
22976
+ var MIN_NODE_MAJOR = 18;
22977
+ var nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
22978
+ if (Number.isFinite(nodeMajor) && nodeMajor < MIN_NODE_MAJOR) {
22979
+ const pt = !(process.env["LANG"] ?? "").toLowerCase().startsWith("en");
22980
+ process.stderr.write(
22981
+ pt ? `Erro: o ToStudy CLI precisa do Node.js ${MIN_NODE_MAJOR} ou superior.
22982
+ Voce esta no Node.js ${process.versions.node}.
22983
+ Atualize em https://nodejs.org e rode o comando de novo.
22984
+ ` : `Error: the ToStudy CLI requires Node.js ${MIN_NODE_MAJOR} or newer.
22985
+ You are on Node.js ${process.versions.node}.
22986
+ Update at https://nodejs.org and run the command again.
22987
+ `
22988
+ );
22989
+ process.exit(1);
22990
+ }
22793
22991
  var { createProgram: createProgram2, CLI_VERSION: CLI_VERSION2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
22794
22992
  var { checkForUpdates: checkForUpdates2 } = await Promise.resolve().then(() => (init_update_checker(), update_checker_exports));
22795
22993
  var { maybeAutoUpdate: maybeAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_updater(), auto_updater_exports));