@tostudy-ai/cli 0.16.3 → 0.16.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -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
  });
@@ -1193,7 +1366,7 @@ async function loadStoredSessionSecrets(configDir) {
1193
1366
  const account = getStoreKey(configDir);
1194
1367
  if (process.platform === "darwin") {
1195
1368
  try {
1196
- const { stdout } = await execFileAsync("security", [
1369
+ const { stdout: stdout2 } = await execFileAsync("security", [
1197
1370
  "find-generic-password",
1198
1371
  "-s",
1199
1372
  "tostudy-cli-session",
@@ -1201,21 +1374,21 @@ async function loadStoredSessionSecrets(configDir) {
1201
1374
  account,
1202
1375
  "-w"
1203
1376
  ]);
1204
- return JSON.parse(stdout.trim());
1377
+ return JSON.parse(stdout2.trim());
1205
1378
  } catch {
1206
1379
  return null;
1207
1380
  }
1208
1381
  }
1209
1382
  if (process.platform === "linux" && await hasCommand("secret-tool")) {
1210
1383
  try {
1211
- const { stdout } = await execFileAsync("secret-tool", [
1384
+ const { stdout: stdout2 } = await execFileAsync("secret-tool", [
1212
1385
  "lookup",
1213
1386
  "service",
1214
1387
  "tostudy-cli-session",
1215
1388
  "account",
1216
1389
  account
1217
1390
  ]);
1218
- return JSON.parse(stdout.trim());
1391
+ return JSON.parse(stdout2.trim());
1219
1392
  } catch {
1220
1393
  return null;
1221
1394
  }
@@ -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:", ""];
@@ -2045,6 +2218,7 @@ var runtime_registry_exports = {};
2045
2218
  __export(runtime_registry_exports, {
2046
2219
  RUNTIMES: () => RUNTIMES,
2047
2220
  detectedSlashHints: () => detectedSlashHints,
2221
+ needsRootAgentsConsent: () => needsRootAgentsConsent,
2048
2222
  upsertRootAgentsBlock: () => upsertRootAgentsBlock
2049
2223
  });
2050
2224
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -2083,6 +2257,15 @@ ${content}`;
2083
2257
  function detectedSlashHints(slug, home, cwd) {
2084
2258
  return RUNTIMES.filter((r) => r.slashHint && r.detect(home, cwd)).map((r) => r.slashHint(slug));
2085
2259
  }
2260
+ function needsRootAgentsConsent(cwd) {
2261
+ const filePath = join2(cwd, "AGENTS.md");
2262
+ if (!existsSync2(filePath)) return false;
2263
+ try {
2264
+ return !TOSTUDY_BLOCK_PATTERN.test(readFileSync(filePath, "utf-8"));
2265
+ } catch {
2266
+ return false;
2267
+ }
2268
+ }
2086
2269
  function upsertRootAgentsBlock(cwd, ctx) {
2087
2270
  const slug = slugifyTitle(ctx.courseTitle);
2088
2271
  const block = [
@@ -2261,9 +2444,10 @@ ${content}`;
2261
2444
  name: "Generic (AGENTS.md)",
2262
2445
  // Windsurf is the remaining consumer of the generic universal install.
2263
2446
  detect: (home) => existsSync2(join2(home, ".codeium", "windsurf")),
2264
- writePerCourse({ cwd, slug, ctx, content }) {
2447
+ writePerCourse({ cwd, slug, ctx, content, rootAgentsConsent }) {
2265
2448
  writeFile2(join2(cwd, ".tostudy", "courses", slug), "AGENTS.md", content);
2266
2449
  const written = [`.tostudy/courses/${slug}/AGENTS.md`];
2450
+ if (needsRootAgentsConsent(cwd) && rootAgentsConsent !== true) return written;
2267
2451
  const rootAgents = upsertRootAgentsBlock(cwd, ctx);
2268
2452
  if (rootAgents) written.push(rootAgents);
2269
2453
  return written;
@@ -2284,6 +2468,7 @@ ${content}`;
2284
2468
  var instruction_files_exports = {};
2285
2469
  __export(instruction_files_exports, {
2286
2470
  installUniversalCommand: () => installUniversalCommand,
2471
+ needsRootAgentsConsent: () => needsRootAgentsConsent,
2287
2472
  slugify: () => slugify,
2288
2473
  upsertRootAgentsBlock: () => upsertRootAgentsBlock,
2289
2474
  writeInstructionFiles: () => writeInstructionFiles
@@ -2293,8 +2478,9 @@ function writeInstructionFiles(cwd, ctx, content, opts = {}) {
2293
2478
  const home = opts.home ?? homedir();
2294
2479
  const force = opts.allRuntimes ?? false;
2295
2480
  const slug = slugify(ctx.courseTitle);
2481
+ const rootAgentsConsent = opts.rootAgentsConsent ?? false;
2296
2482
  const written = RUNTIMES.flatMap(
2297
- (r) => r.writePerCourse?.({ cwd, home, slug, ctx, content, force }) ?? []
2483
+ (r) => r.writePerCourse?.({ cwd, home, slug, ctx, content, force, rootAgentsConsent }) ?? []
2298
2484
  );
2299
2485
  logger3.info("Generated instruction files (v3)", { slug, files: written });
2300
2486
  return written;
@@ -2578,6 +2764,101 @@ var init_user_profile = __esm({
2578
2764
  }
2579
2765
  });
2580
2766
 
2767
+ // src/errors/errors-pt-br.ts
2768
+ var ptBrErrors;
2769
+ var init_errors_pt_br = __esm({
2770
+ "src/errors/errors-pt-br.ts"() {
2771
+ "use strict";
2772
+ ptBrErrors = {
2773
+ notFound: "n\xE3o encontrado",
2774
+ workspaceNotFoundShort: "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n",
2775
+ workspaceNotFoundFromExport: "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' ou rode 'tostudy select' desta pasta.\n",
2776
+ vaultNotFound: "\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n",
2777
+ logoutSuccess: "\n Deslogado com sucesso.\n",
2778
+ workspaceCommandDescription: "Gerenciar workspace de estudo local",
2779
+ workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2780
+ 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",
2781
+ 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",
2782
+ 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",
2783
+ 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"
2784
+ };
2785
+ }
2786
+ });
2787
+
2788
+ // src/errors/errors-en-us.ts
2789
+ var enUsErrors;
2790
+ var init_errors_en_us = __esm({
2791
+ "src/errors/errors-en-us.ts"() {
2792
+ "use strict";
2793
+ enUsErrors = {
2794
+ notFound: "not found",
2795
+ workspaceNotFoundShort: "\u274C Workspace not found. Run 'tostudy workspace setup' first.\n",
2796
+ workspaceNotFoundFromExport: "\u274C Workspace not found. Run 'tostudy workspace setup' or 'tostudy select' from this folder.\n",
2797
+ vaultNotFound: "\u274C Vault not found. Run 'tostudy vault init' first.\n",
2798
+ logoutSuccess: "\n Logged out successfully.\n",
2799
+ workspaceCommandDescription: "Manage local study workspace",
2800
+ workspaceSetupDescription: "Create the workspace structure for the active course",
2801
+ 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",
2802
+ 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",
2803
+ courseArchived: "This course has been archived and is available for reading only.\nUse `tostudy lesson` to review the content you've already studied.\n",
2804
+ 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"
2805
+ };
2806
+ }
2807
+ });
2808
+
2809
+ // src/errors/index.ts
2810
+ function resolveLocale() {
2811
+ if (_cachedLocale !== null) return _cachedLocale;
2812
+ const argv = process.argv;
2813
+ const flagIdx = argv.indexOf("--locale");
2814
+ if (flagIdx !== -1 && flagIdx + 1 < argv.length) {
2815
+ const val = argv[flagIdx + 1];
2816
+ if (val === "pt-BR" || val === "en-US") {
2817
+ _cachedLocale = val;
2818
+ return val;
2819
+ }
2820
+ }
2821
+ const lang = (process.env["LANG"] ?? "").toLowerCase();
2822
+ if (lang.startsWith("en")) {
2823
+ _cachedLocale = "en-US";
2824
+ return "en-US";
2825
+ }
2826
+ _cachedLocale = "pt-BR";
2827
+ return "pt-BR";
2828
+ }
2829
+ function getErrors(locale) {
2830
+ return BUNDLES[locale ?? resolveLocale()];
2831
+ }
2832
+ function resolveErrorCode(err) {
2833
+ if (err && typeof err === "object" && "code" in err) {
2834
+ const code = err.code;
2835
+ if (typeof code === "string" && code.length > 0) return code;
2836
+ }
2837
+ return err instanceof Error ? err.message : String(err);
2838
+ }
2839
+ function isInsufficientCreditsError(err) {
2840
+ return resolveErrorCode(err).includes("INSUFFICIENT_CREDITS");
2841
+ }
2842
+ function isCourseArchivedError(err) {
2843
+ return resolveErrorCode(err).includes("COURSE_ARCHIVED");
2844
+ }
2845
+ function isEnrollmentNotEntitledError(err) {
2846
+ return resolveErrorCode(err).includes("ENROLLMENT_NOT_ENTITLED");
2847
+ }
2848
+ var BUNDLES, _cachedLocale;
2849
+ var init_errors = __esm({
2850
+ "src/errors/index.ts"() {
2851
+ "use strict";
2852
+ init_errors_pt_br();
2853
+ init_errors_en_us();
2854
+ BUNDLES = {
2855
+ "pt-BR": ptBrErrors,
2856
+ "en-US": enUsErrors
2857
+ };
2858
+ _cachedLocale = null;
2859
+ }
2860
+ });
2861
+
2581
2862
  // src/workspace/workspace-marker.ts
2582
2863
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync2 } from "node:fs";
2583
2864
  import os4 from "node:os";
@@ -2607,9 +2888,7 @@ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
2607
2888
  const homeDir = path7.resolve(options.homeDir ?? os4.homedir());
2608
2889
  const resolvedWorkspace = path7.resolve(workspacePath);
2609
2890
  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
- );
2891
+ throw new Error(getErrors().workspaceRefusedAtHome);
2613
2892
  }
2614
2893
  const filePath = resolveMarkerPath(workspacePath);
2615
2894
  const dir = path7.dirname(filePath);
@@ -2634,6 +2913,7 @@ var CURRENT_VERSION, SUPPORTED_VERSIONS;
2634
2913
  var init_workspace_marker = __esm({
2635
2914
  "src/workspace/workspace-marker.ts"() {
2636
2915
  "use strict";
2916
+ init_errors();
2637
2917
  CURRENT_VERSION = 1;
2638
2918
  SUPPORTED_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
2639
2919
  }
@@ -2785,99 +3065,6 @@ var init_course_state = __esm({
2785
3065
  }
2786
3066
  });
2787
3067
 
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
3068
  // src/commands/logout.ts
2882
3069
  import { Command as Command2 } from "commander";
2883
3070
  var logoutCommand;
@@ -3196,7 +3383,7 @@ var CLI_VERSION;
3196
3383
  var init_version = __esm({
3197
3384
  "src/version.ts"() {
3198
3385
  "use strict";
3199
- CLI_VERSION = true ? "0.16.3" : "0.7.1";
3386
+ CLI_VERSION = true ? "0.16.6" : "0.7.1";
3200
3387
  }
3201
3388
  });
3202
3389
 
@@ -4030,7 +4217,7 @@ async function writeFilesAdapter(cwd, courseMeta, content, opts = {}) {
4030
4217
  courseDescription: courseMeta.courseDescription
4031
4218
  },
4032
4219
  content,
4033
- { allRuntimes: opts.allRuntimes }
4220
+ { allRuntimes: opts.allRuntimes, rootAgentsConsent: opts.rootAgentsConsent }
4034
4221
  );
4035
4222
  }
4036
4223
  function buildPipelineDeps(opts = {}) {
@@ -4053,6 +4240,43 @@ var init_pipeline_deps = __esm({
4053
4240
  }
4054
4241
  });
4055
4242
 
4243
+ // src/commands/_shared/root-agents-consent.ts
4244
+ import readline2 from "node:readline/promises";
4245
+ import { stdin, stdout } from "node:process";
4246
+ function rootAgentsSkippedHint() {
4247
+ return " AGENTS.md desta pasta n\xE3o foi alterado \u2014 rode `tostudy sync --root-agents` para adicionar o bloco do ToStudy.";
4248
+ }
4249
+ async function askViaReadline(question) {
4250
+ const rl = readline2.createInterface({ input: stdin, output: stdout });
4251
+ try {
4252
+ return await rl.question(question);
4253
+ } finally {
4254
+ rl.close();
4255
+ }
4256
+ }
4257
+ async function resolveRootAgentsConsent(input2) {
4258
+ const needs = (input2.needsConsent ?? needsRootAgentsConsent)(input2.cwd);
4259
+ if (!needs) return { consent: true, asked: false };
4260
+ if (input2.flag === true) return { consent: true, asked: true };
4261
+ if (!input2.interactive) return { consent: false, asked: true };
4262
+ const answer = (await (input2.ask ?? askViaReadline)(ROOT_AGENTS_QUESTION)).trim().toLowerCase();
4263
+ return {
4264
+ consent: answer === "s" || answer === "sim" || answer === "y" || answer === "yes",
4265
+ asked: true
4266
+ };
4267
+ }
4268
+ function isInteractiveTerminal(json2) {
4269
+ return Boolean(stdin.isTTY && stdout.isTTY) && json2 !== true;
4270
+ }
4271
+ var ROOT_AGENTS_QUESTION;
4272
+ var init_root_agents_consent = __esm({
4273
+ "src/commands/_shared/root-agents-consent.ts"() {
4274
+ "use strict";
4275
+ init_runtime_registry();
4276
+ ROOT_AGENTS_QUESTION = "Esta pasta j\xE1 tem um AGENTS.md. Adicionar o bloco do ToStudy nele? [s/N] ";
4277
+ }
4278
+ });
4279
+
4056
4280
  // src/commands/select.ts
4057
4281
  import { Command as Command6 } from "commander";
4058
4282
  import os7 from "node:os";
@@ -4072,8 +4296,12 @@ var init_select = __esm({
4072
4296
  init_status();
4073
4297
  init_instruction_pipeline();
4074
4298
  init_pipeline_deps();
4299
+ init_root_agents_consent();
4075
4300
  logger8 = createLogger("cli:select");
4076
- 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) => {
4301
+ 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").option(
4302
+ "--root-agents",
4303
+ "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
4304
+ ).action(async (course, opts) => {
4077
4305
  try {
4078
4306
  const session = await requireSession();
4079
4307
  const data = createHttpProvider(session.apiUrl, session.token);
@@ -4108,12 +4336,20 @@ var init_select = __esm({
4108
4336
  enrollmentId
4109
4337
  };
4110
4338
  const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
4339
+ const rootAgents = await resolveRootAgentsConsent({
4340
+ cwd: process.cwd(),
4341
+ flag: opts.rootAgents,
4342
+ interactive: isInteractiveTerminal(opts.json)
4343
+ });
4111
4344
  let pipelineResult = null;
4112
4345
  try {
4113
4346
  pipelineResult = await resolveAndGenerate(
4114
4347
  { courseId: detail.courseId, enrollmentId },
4115
4348
  { forceRefresh: false, collectT1Interactive: false, collectT2Interactive: false },
4116
- { cwd: process.cwd(), deps: buildPipelineDeps() }
4349
+ {
4350
+ cwd: process.cwd(),
4351
+ deps: buildPipelineDeps({ rootAgentsConsent: rootAgents.consent })
4352
+ }
4117
4353
  );
4118
4354
  } catch (err) {
4119
4355
  logger8.warn("Failed to run instruction pipeline", {
@@ -4131,6 +4367,7 @@ var init_select = __esm({
4131
4367
  workspacePath: onboarding.workspacePath,
4132
4368
  workspaceSource: onboarding.workspaceSource,
4133
4369
  wroteFiles: pipelineResult?.wroteFiles ?? [],
4370
+ rootAgentsSkipped: rootAgents.asked && !rootAgents.consent,
4134
4371
  markerPath: pipelineResult?.markerPath ?? null,
4135
4372
  usedT0: pipelineResult?.usedT0 ?? false,
4136
4373
  usedT1: pipelineResult?.usedT1 ?? false,
@@ -4167,6 +4404,9 @@ var init_select = __esm({
4167
4404
  if (pipelineResult && !pipelineResult.usedT1) {
4168
4405
  lines.push(" (sem brief base \u2014 rode `tostudy init` para configurar)");
4169
4406
  }
4407
+ if (rootAgents.asked && !rootAgents.consent) {
4408
+ lines.push(rootAgentsSkippedHint());
4409
+ }
4170
4410
  lines.push("", "\u2192 Abra sua plataforma (claude, codex, cursor...)");
4171
4411
  const hints = courseSlug2 ? detectedSlashHints(courseSlug2, home, cwd) : [];
4172
4412
  if (hints.length > 0) {
@@ -19926,7 +20166,7 @@ var init_menu = __esm({
19926
20166
  });
19927
20167
 
19928
20168
  // src/onboarding/learner-context.ts
19929
- import readline2 from "node:readline/promises";
20169
+ import readline3 from "node:readline/promises";
19930
20170
  import { stdin as input, stdout as output2 } from "node:process";
19931
20171
  async function askNonEmpty2(question, deps, defaultValue) {
19932
20172
  while (true) {
@@ -19944,7 +20184,7 @@ async function askChoice(question, choices, deps, defaultChoice) {
19944
20184
  }
19945
20185
  }
19946
20186
  function createPromptDeps() {
19947
- const rl = readline2.createInterface({ input, output: output2 });
20187
+ const rl = readline3.createInterface({ input, output: output2 });
19948
20188
  return {
19949
20189
  ask: (question) => rl.question(question),
19950
20190
  write: (content) => output2.write(content),
@@ -20167,10 +20407,18 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
20167
20407
  });
20168
20408
  await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
20169
20409
  try {
20410
+ const rootAgents = await resolveRootAgentsConsent({
20411
+ cwd: process.cwd(),
20412
+ flag: flags.rootAgents,
20413
+ interactive: isInteractiveTerminal(flags.json)
20414
+ });
20170
20415
  const pipelineResult = await deps.resolveAndGenerate(
20171
20416
  { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
20172
20417
  { forceRefresh: false, collectT1Interactive: true, collectT2Interactive: false },
20173
- { cwd: process.cwd(), deps: deps.buildPipelineDeps() }
20418
+ {
20419
+ cwd: process.cwd(),
20420
+ deps: deps.buildPipelineDeps({ rootAgentsConsent: rootAgents.consent })
20421
+ }
20174
20422
  );
20175
20423
  deps.logger.info("Instruction files regenerated via pipeline", {
20176
20424
  wroteFiles: pipelineResult.wroteFiles,
@@ -20211,6 +20459,7 @@ var init_init = __esm({
20211
20459
  init_learner_context();
20212
20460
  init_api3();
20213
20461
  init_instruction_pipeline();
20462
+ init_root_agents_consent();
20214
20463
  init_pipeline_deps();
20215
20464
  logger17 = createLogger("cli:init");
20216
20465
  defaultDeps4 = {
@@ -20231,7 +20480,10 @@ var init_init = __esm({
20231
20480
  resolveAndGenerate,
20232
20481
  buildPipelineDeps
20233
20482
  };
20234
- initCommand = new Command16("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option("--json", "Output structured JSON").action(async (opts) => {
20483
+ initCommand = new Command16("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option(
20484
+ "--root-agents",
20485
+ "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
20486
+ ).option("--json", "Output structured JSON").action(async (opts) => {
20235
20487
  await runInit(defaultDeps4, opts);
20236
20488
  });
20237
20489
  }
@@ -21233,20 +21485,38 @@ var init_sync = __esm({
21233
21485
  init_guards();
21234
21486
  init_formatter();
21235
21487
  init_instruction_pipeline();
21488
+ init_root_agents_consent();
21236
21489
  init_pipeline_deps();
21237
21490
  logger22 = createLogger("cli:sync");
21238
21491
  syncCommand = new Command22("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").option(
21239
21492
  "--all-runtimes",
21240
21493
  "Write instruction files for every supported runtime, even undetected ones"
21494
+ ).option(
21495
+ "--root-agents",
21496
+ "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
21241
21497
  ).action(async (opts) => {
21242
21498
  try {
21243
21499
  await requireSession();
21244
21500
  const activeCourse = await requireActiveCourse();
21501
+ const rootAgents = await resolveRootAgentsConsent({
21502
+ cwd: process.cwd(),
21503
+ flag: opts.rootAgents,
21504
+ interactive: isInteractiveTerminal(opts.json)
21505
+ });
21245
21506
  const pipelineResult = await resolveAndGenerate(
21246
21507
  { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
21247
21508
  { forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
21248
- { cwd: process.cwd(), deps: buildPipelineDeps({ allRuntimes: opts.allRuntimes }) }
21509
+ {
21510
+ cwd: process.cwd(),
21511
+ deps: buildPipelineDeps({
21512
+ allRuntimes: opts.allRuntimes,
21513
+ rootAgentsConsent: rootAgents.consent
21514
+ })
21515
+ }
21249
21516
  );
21517
+ if (rootAgents.asked && !rootAgents.consent && !opts.json) {
21518
+ output(rootAgentsSkippedHint(), { json: false });
21519
+ }
21250
21520
  if (opts.json) {
21251
21521
  output(
21252
21522
  {
@@ -22238,7 +22508,10 @@ var init_export_grades = __esm({
22238
22508
  defaultDeps5 = {
22239
22509
  requireSession,
22240
22510
  requireActiveCourse,
22241
- fetch: globalThis.fetch.bind(globalThis),
22511
+ // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
22512
+ // antes de qualquer guard. Em runtime sem `fetch` global isso derruba o import
22513
+ // inteiro do cli.ts e, com ele, TODO comando. Encaminhar mantem o load inerte.
22514
+ fetch: (...args) => globalThis.fetch(...args),
22242
22515
  stdoutWrite: (message) => process.stdout.write(message),
22243
22516
  stderrWrite: (message) => process.stderr.write(message),
22244
22517
  // process.exit is typed as `never` — wrap so the cast is local to one line.
@@ -22425,7 +22698,10 @@ var init_grades2 = __esm({
22425
22698
  logger33 = createLogger("cli:grades");
22426
22699
  defaultDeps6 = {
22427
22700
  requireSession,
22428
- fetch: globalThis.fetch.bind(globalThis),
22701
+ // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
22702
+ // antes de qualquer guard. Em runtime sem `fetch` global isso derruba o import
22703
+ // inteiro do cli.ts e, com ele, TODO comando. Encaminhar mantem o load inerte.
22704
+ fetch: (...args) => globalThis.fetch(...args),
22429
22705
  stdoutWrite: (message) => process.stdout.write(message),
22430
22706
  stderrWrite: (message) => process.stderr.write(message),
22431
22707
  exit: (code) => process.exit(code),
@@ -22605,7 +22881,10 @@ var init_review2 = __esm({
22605
22881
  logger34 = createLogger("cli:review");
22606
22882
  defaultDeps7 = {
22607
22883
  requireSession,
22608
- fetch: globalThis.fetch.bind(globalThis),
22884
+ // Lazy: ler `globalThis.fetch` no escopo do modulo executa no LOAD do arquivo,
22885
+ // antes de qualquer guard. Em runtime sem `fetch` global isso derruba o import
22886
+ // inteiro do cli.ts e, com ele, TODO comando. Encaminhar mantem o load inerte.
22887
+ fetch: (...args) => globalThis.fetch(...args),
22609
22888
  stdoutWrite: (message) => process.stdout.write(message),
22610
22889
  stderrWrite: (message) => process.stderr.write(message),
22611
22890
  exit: (code) => process.exit(code),
@@ -22790,6 +23069,21 @@ var init_auto_updater = __esm({
22790
23069
  if (!process.env.LOG_LEVEL) {
22791
23070
  process.env.LOG_LEVEL = "fatal";
22792
23071
  }
23072
+ var MIN_NODE_MAJOR = 18;
23073
+ var nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
23074
+ if (Number.isFinite(nodeMajor) && nodeMajor < MIN_NODE_MAJOR) {
23075
+ const pt = !(process.env["LANG"] ?? "").toLowerCase().startsWith("en");
23076
+ process.stderr.write(
23077
+ pt ? `Erro: o ToStudy CLI precisa do Node.js ${MIN_NODE_MAJOR} ou superior.
23078
+ Voce esta no Node.js ${process.versions.node}.
23079
+ Atualize em https://nodejs.org e rode o comando de novo.
23080
+ ` : `Error: the ToStudy CLI requires Node.js ${MIN_NODE_MAJOR} or newer.
23081
+ You are on Node.js ${process.versions.node}.
23082
+ Update at https://nodejs.org and run the command again.
23083
+ `
23084
+ );
23085
+ process.exit(1);
23086
+ }
22793
23087
  var { createProgram: createProgram2, CLI_VERSION: CLI_VERSION2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
22794
23088
  var { checkForUpdates: checkForUpdates2 } = await Promise.resolve().then(() => (init_update_checker(), update_checker_exports));
22795
23089
  var { maybeAutoUpdate: maybeAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_updater(), auto_updater_exports));