@tostudy-ai/cli 0.16.0 → 0.16.1

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,8 +41,18 @@ 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-D8bjOQig.mjs
44
+ // ../../node_modules/@parisgroup-ai/logger/dist/sanitize-4AyoT9ic.mjs
45
45
  function isSensitiveKey(key) {
46
+ if (matchesSensitiveField(key)) return true;
47
+ return matchesPluralOfSensitiveField(key);
48
+ }
49
+ function matchesPluralOfSensitiveField(key) {
50
+ const lower = key.toLowerCase();
51
+ if (lower.endsWith("es") && matchesSensitiveField(key.slice(0, -2))) return true;
52
+ if (lower.endsWith("s") && matchesSensitiveField(key.slice(0, -1))) return true;
53
+ return false;
54
+ }
55
+ function matchesSensitiveField(key) {
46
56
  const lowerKey = key.toLowerCase();
47
57
  return SENSITIVE_FIELDS.some((field) => {
48
58
  const lowerField = field.toLowerCase();
@@ -106,7 +116,8 @@ function sanitizeRecord(obj, ctx) {
106
116
  const sanitized = { ...obj };
107
117
  for (const [key, value] of Object.entries(sanitized)) {
108
118
  const isAdditionalKey = ctx.additionalSensitiveKeys?.some((k) => key.toLowerCase() === k.toLowerCase());
109
- if (isSensitiveKey(key) || isAdditionalKey) {
119
+ const pluralCountExemption = typeof value === "number" && !isAdditionalKey && !matchesSensitiveField(key) && matchesPluralOfSensitiveField(key);
120
+ if ((isSensitiveKey(key) || isAdditionalKey) && !pluralCountExemption) {
110
121
  sanitized[key] = "[REDACTED]";
111
122
  continue;
112
123
  }
@@ -136,8 +147,8 @@ function sanitizeEntryData(obj, additionalSensitiveKeys, includeStack) {
136
147
  });
137
148
  }
138
149
  var SENSITIVE_FIELDS;
139
- var init_sanitize_D8bjOQig = __esm({
140
- "../../node_modules/@parisgroup-ai/logger/dist/sanitize-D8bjOQig.mjs"() {
150
+ var init_sanitize_4AyoT9ic = __esm({
151
+ "../../node_modules/@parisgroup-ai/logger/dist/sanitize-4AyoT9ic.mjs"() {
141
152
  SENSITIVE_FIELDS = [
142
153
  "password",
143
154
  "senha",
@@ -268,7 +279,7 @@ var require_picocolors = __commonJS({
268
279
  }
269
280
  });
270
281
 
271
- // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-MYys2STJ.mjs
282
+ // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-DOZPgsDx.mjs
272
283
  function formatJson(entry) {
273
284
  const { timestamp, level, message, service, environment, context, data, error: error49 } = entry;
274
285
  const output3 = {
@@ -327,6 +338,35 @@ function formatPretty(entry) {
327
338
  }
328
339
  return output3;
329
340
  }
341
+ function filterStrings(value, safe, visited) {
342
+ if (typeof value === "string") return safe(value);
343
+ if (value === null || typeof value !== "object") return value;
344
+ if (visited.has(value)) return value;
345
+ visited.add(value);
346
+ if (Array.isArray(value)) return value.map((item) => filterStrings(item, safe, visited));
347
+ const proto = Object.getPrototypeOf(value);
348
+ if (proto !== Object.prototype && proto !== null) return value;
349
+ const out = {};
350
+ for (const [key, item] of Object.entries(value)) out[key] = filterStrings(item, safe, visited);
351
+ return out;
352
+ }
353
+ function applyPiiFilterToEntry(entry, filter) {
354
+ const safe = (text) => {
355
+ try {
356
+ return filter(text);
357
+ } catch {
358
+ return "[PII_FILTER_ERROR]";
359
+ }
360
+ };
361
+ const visited = /* @__PURE__ */ new WeakSet();
362
+ return {
363
+ ...entry,
364
+ message: safe(entry.message),
365
+ context: filterStrings(entry.context, safe, visited),
366
+ data: entry.data ? filterStrings(entry.data, safe, visited) : entry.data,
367
+ error: entry.error ? filterStrings(entry.error, safe, visited) : entry.error
368
+ };
369
+ }
330
370
  function extractErrorInfo(error49, includeStack, visited = /* @__PURE__ */ new WeakSet()) {
331
371
  const info = {
332
372
  name: error49.name,
@@ -343,9 +383,9 @@ function extractErrorInfo(error49, includeStack, visited = /* @__PURE__ */ new W
343
383
  return info;
344
384
  }
345
385
  var import_picocolors, LOG_LEVELS, LEVEL_COLORS, LEVEL_WIDTH, BaseLogger;
346
- var init_logger_base_MYys2STJ = __esm({
347
- "../../node_modules/@parisgroup-ai/logger/dist/logger.base-MYys2STJ.mjs"() {
348
- init_sanitize_D8bjOQig();
386
+ var init_logger_base_DOZPgsDx = __esm({
387
+ "../../node_modules/@parisgroup-ai/logger/dist/logger.base-DOZPgsDx.mjs"() {
388
+ init_sanitize_4AyoT9ic();
349
389
  import_picocolors = __toESM(require_picocolors(), 1);
350
390
  LOG_LEVELS = {
351
391
  trace: 0,
@@ -375,6 +415,7 @@ var init_logger_base_MYys2STJ = __esm({
375
415
  shouldSanitize;
376
416
  sensitiveFields;
377
417
  includeCaller;
418
+ piiFilter;
378
419
  constructor(options, defaults) {
379
420
  this.level = options.level ?? defaults.level;
380
421
  this.format = options.format ?? defaults.format;
@@ -386,6 +427,7 @@ var init_logger_base_MYys2STJ = __esm({
386
427
  this.shouldSanitize = options.sanitize ?? true;
387
428
  this.sensitiveFields = options.sensitiveFields;
388
429
  this.includeCaller = options.includeCaller ?? "errors";
430
+ this.piiFilter = options.piiFilter;
389
431
  }
390
432
  /**
391
433
  * Get caller information for error logging (Node.js only).
@@ -401,10 +443,11 @@ var init_logger_base_MYys2STJ = __esm({
401
443
  return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
402
444
  }
403
445
  buildEntry(level, message, data, error49) {
404
- const context = {
446
+ const merged = {
405
447
  ...this.getLogContext() ?? {},
406
448
  ...this.additionalContext
407
449
  };
450
+ const context = this.shouldSanitize ? sanitizeEntryData(merged, this.sensitiveFields, !this.isProd()) : merged;
408
451
  if (this.module) context.module = this.module;
409
452
  if (this.includeCaller === true || this.includeCaller === "errors" && (level === "error" || level === "fatal")) {
410
453
  const caller = this.getCaller();
@@ -447,7 +490,8 @@ var init_logger_base_MYys2STJ = __esm({
447
490
  if (!this.shouldLog(level)) return;
448
491
  const normalizedData = this.normalizeData(data);
449
492
  const errorInfo = errorObj ? extractErrorInfo(errorObj, !this.isProd()) : void 0;
450
- const entry = this.buildEntry(level, message, normalizedData, errorInfo);
493
+ const built = this.buildEntry(level, message, normalizedData, errorInfo);
494
+ const entry = this.shouldSanitize && this.piiFilter ? applyPiiFilterToEntry(built, this.piiFilter) : built;
451
495
  const formatted = this.format === "json" ? formatJson(entry) : formatPretty(entry);
452
496
  for (const transport of this.transports) transport.log(entry, formatted);
453
497
  this.flush();
@@ -609,8 +653,8 @@ function createLogger(module, options) {
609
653
  var asyncLocalStorage, ConsoleTransport, FileTransport, HttpTransport, SINGLETON_KEY, Logger, logger;
610
654
  var init_dist = __esm({
611
655
  "../../node_modules/@parisgroup-ai/logger/dist/index.mjs"() {
612
- init_logger_base_MYys2STJ();
613
- init_sanitize_D8bjOQig();
656
+ init_logger_base_DOZPgsDx();
657
+ init_sanitize_4AyoT9ic();
614
658
  asyncLocalStorage = new AsyncLocalStorage();
615
659
  ConsoleTransport = class {
616
660
  log(entry, formatted) {
@@ -768,7 +812,8 @@ var init_dist = __esm({
768
812
  environment: this.environment,
769
813
  sanitize: this.shouldSanitize,
770
814
  sensitiveFields: this.sensitiveFields,
771
- includeCaller: this.includeCaller
815
+ includeCaller: this.includeCaller,
816
+ piiFilter: this.piiFilter
772
817
  });
773
818
  child.additionalContext = {
774
819
  ...this.additionalContext,
@@ -836,9 +881,15 @@ async function cliApiFetch(url2, token, init) {
836
881
  ...init?.headers
837
882
  }
838
883
  });
839
- const body = await response.json();
884
+ const body = await response.json().catch(() => ({}));
840
885
  if (!response.ok) {
841
- throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
886
+ const code = typeof body.error === "string" && body.error.length > 0 ? body.error : void 0;
887
+ const friendly = typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
888
+ throw new CliApiError(
889
+ friendly ?? code ?? `API error ${response.status}`,
890
+ response.status,
891
+ code
892
+ );
842
893
  }
843
894
  return body;
844
895
  }
@@ -973,12 +1024,14 @@ var init_http = __esm({
973
1024
  "../../packages/tostudy-core/src/adapters/http.ts"() {
974
1025
  "use strict";
975
1026
  CliApiError = class extends Error {
976
- constructor(message, status) {
1027
+ constructor(message, status, code) {
977
1028
  super(message);
978
1029
  this.status = status;
1030
+ this.code = code;
979
1031
  this.name = "CliApiError";
980
1032
  }
981
1033
  status;
1034
+ code;
982
1035
  };
983
1036
  }
984
1037
  });
@@ -2734,17 +2787,21 @@ function resolveLocale() {
2734
2787
  function getErrors(locale) {
2735
2788
  return BUNDLES[locale ?? resolveLocale()];
2736
2789
  }
2790
+ function resolveErrorCode(err) {
2791
+ if (err && typeof err === "object" && "code" in err) {
2792
+ const code = err.code;
2793
+ if (typeof code === "string" && code.length > 0) return code;
2794
+ }
2795
+ return err instanceof Error ? err.message : String(err);
2796
+ }
2737
2797
  function isInsufficientCreditsError(err) {
2738
- const msg = err instanceof Error ? err.message : String(err);
2739
- return msg.includes("INSUFFICIENT_CREDITS");
2798
+ return resolveErrorCode(err).includes("INSUFFICIENT_CREDITS");
2740
2799
  }
2741
2800
  function isCourseArchivedError(err) {
2742
- const msg = err instanceof Error ? err.message : String(err);
2743
- return msg.includes("COURSE_ARCHIVED");
2801
+ return resolveErrorCode(err).includes("COURSE_ARCHIVED");
2744
2802
  }
2745
2803
  function isEnrollmentNotEntitledError(err) {
2746
- const msg = err instanceof Error ? err.message : String(err);
2747
- return msg.includes("ENROLLMENT_NOT_ENTITLED");
2804
+ return resolveErrorCode(err).includes("ENROLLMENT_NOT_ENTITLED");
2748
2805
  }
2749
2806
  var BUNDLES, _cachedLocale;
2750
2807
  var init_errors = __esm({
@@ -2942,7 +2999,7 @@ var CLI_VERSION;
2942
2999
  var init_version = __esm({
2943
3000
  "src/version.ts"() {
2944
3001
  "use strict";
2945
- CLI_VERSION = true ? "0.16.0" : "0.7.1";
3002
+ CLI_VERSION = true ? "0.16.1" : "0.7.1";
2946
3003
  }
2947
3004
  });
2948
3005
 
@@ -4163,7 +4220,7 @@ async function runStart(opts, deps = defaultDeps3) {
4163
4220
  `);
4164
4221
  }
4165
4222
  } catch (err) {
4166
- if (err instanceof CliApiError && err.status === 403 && err.message === "CHANNEL_MISMATCH") {
4223
+ if (err instanceof CliApiError && err.status === 403 && (err.code ?? err.message) === "CHANNEL_MISMATCH") {
4167
4224
  process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
4168
4225
  process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
4169
4226
  process.exit(1);
@@ -4251,7 +4308,7 @@ var init_start_next = __esm({
4251
4308
  output(formatModuleStart(moduleData), { json: false });
4252
4309
  }
4253
4310
  } catch (err) {
4254
- if (err instanceof CliApiError && err.message === "COURSE_COMPLETE") {
4311
+ if (err instanceof CliApiError && (err.code ?? err.message) === "COURSE_COMPLETE") {
4255
4312
  process.stderr.write("\u{1F389} Parab\xE9ns! Curso conclu\xEDdo!\n");
4256
4313
  process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
4257
4314
  process.exit(0);
@@ -4324,26 +4381,37 @@ var init_next = __esm({
4324
4381
  process.exit(1);
4325
4382
  }
4326
4383
  if (err instanceof CliApiError) {
4327
- if (err.status === 403 && err.message === "CHANNEL_MISMATCH") {
4384
+ const code = err.code ?? err.message;
4385
+ if (err.status === 403 && code === "CHANNEL_MISMATCH") {
4328
4386
  process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
4329
4387
  process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
4330
4388
  process.exit(1);
4331
4389
  }
4332
- if (err.message === "MODULE_COMPLETE") {
4390
+ if (code === "MODULE_COMPLETE") {
4333
4391
  process.stderr.write("\u2713 M\xF3dulo conclu\xEDdo!\n");
4334
4392
  process.stderr.write("\u2192 tostudy start-next para iniciar o pr\xF3ximo m\xF3dulo\n");
4335
4393
  process.exit(0);
4336
4394
  }
4337
- if (err.message === "COURSE_COMPLETE") {
4395
+ if (code === "COURSE_COMPLETE") {
4338
4396
  process.stderr.write("\u{1F389} Parab\xE9ns! Curso conclu\xEDdo!\n");
4339
4397
  process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
4340
4398
  process.exit(0);
4341
4399
  }
4342
- if (err.message === "NO_ACTIVE_LESSON") {
4400
+ if (code === "EXERCISE_NOT_PASSED") {
4401
+ process.stderr.write("Voc\xEA ainda n\xE3o foi aprovado no exerc\xEDcio desta li\xE7\xE3o.\n");
4402
+ process.stderr.write("\u2192 tostudy validate para enviar sua solu\xE7\xE3o\n");
4403
+ process.exit(1);
4404
+ }
4405
+ if (code === "NO_ACTIVE_LESSON") {
4343
4406
  process.stderr.write("Nenhuma li\xE7\xE3o em progresso.\n");
4344
4407
  process.stderr.write("\u2192 tostudy start-next para iniciar o pr\xF3ximo m\xF3dulo\n");
4345
4408
  process.exit(0);
4346
4409
  }
4410
+ if (err.status === 409) {
4411
+ process.stderr.write(`${err.message}
4412
+ `);
4413
+ process.exit(1);
4414
+ }
4347
4415
  }
4348
4416
  const msg = err instanceof Error ? err.message : String(err);
4349
4417
  if (opts.json) jsonError(msg);
@@ -4611,6 +4679,11 @@ var init_hint = __esm({
4611
4679
  else process.stderr.write(friendly);
4612
4680
  process.exit(1);
4613
4681
  }
4682
+ if (err instanceof CliApiError && (err.code ?? err.message) === "NO_ACTIVE_LESSON") {
4683
+ process.stderr.write("Nenhuma li\xE7\xE3o em progresso.\n");
4684
+ process.stderr.write("\u2192 tostudy start-next para iniciar o pr\xF3ximo m\xF3dulo\n");
4685
+ process.exit(0);
4686
+ }
4614
4687
  if (isInsufficientCreditsError(err)) {
4615
4688
  const friendly = getErrors().insufficientCredits;
4616
4689
  if (opts.json) jsonError("insufficient_credits", { message: friendly });
@@ -21666,7 +21739,12 @@ var init_theory = __esm({
21666
21739
  });
21667
21740
  if (!res.ok) {
21668
21741
  const body = await res.json().catch(() => ({}));
21669
- const msg = body.error ?? `Falha ao pedir teoria (${res.status})`;
21742
+ if (body.error === "INSUFFICIENT_CREDITS" || (body.message ?? "").includes("INSUFFICIENT_CREDITS")) {
21743
+ const friendly = getErrors().insufficientCredits;
21744
+ if (opts.json) jsonError("insufficient_credits", { message: friendly });
21745
+ error(friendly);
21746
+ }
21747
+ const msg = body.error ?? body.message ?? `Falha ao pedir teoria (${res.status})`;
21670
21748
  if (opts.json) jsonError("request_failed", { message: msg, code: res.status });
21671
21749
  error(msg);
21672
21750
  }