@tostudy-ai/cli 0.11.0 → 0.11.2

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
@@ -104,7 +104,7 @@ var require_picocolors = __commonJS({
104
104
  }
105
105
  });
106
106
 
107
- // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-CHajMTDD.js
107
+ // ../../node_modules/@parisgroup-ai/logger/dist/logger.base-CG4i4G4l.mjs
108
108
  function formatJson(entry) {
109
109
  const { timestamp: timestamp2, level, message, service, environment, context, data, error: error49 } = entry;
110
110
  const output3 = {
@@ -284,8 +284,8 @@ function extractErrorInfo(error49, includeStack) {
284
284
  return info;
285
285
  }
286
286
  var import_picocolors, LOG_LEVELS, LEVEL_COLORS, LEVEL_WIDTH, SENSITIVE_FIELDS, sanitize, BaseLogger;
287
- var init_logger_base_CHajMTDD = __esm({
288
- "../../node_modules/@parisgroup-ai/logger/dist/logger.base-CHajMTDD.js"() {
287
+ var init_logger_base_CG4i4G4l = __esm({
288
+ "../../node_modules/@parisgroup-ai/logger/dist/logger.base-CG4i4G4l.mjs"() {
289
289
  import_picocolors = __toESM(require_picocolors(), 1);
290
290
  LOG_LEVELS = {
291
291
  trace: 0,
@@ -541,7 +541,7 @@ var init_logger_base_CHajMTDD = __esm({
541
541
  }
542
542
  });
543
543
 
544
- // ../../node_modules/@parisgroup-ai/logger/dist/index.js
544
+ // ../../node_modules/@parisgroup-ai/logger/dist/index.mjs
545
545
  import { AsyncLocalStorage } from "node:async_hooks";
546
546
  import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
547
547
  import { join } from "node:path";
@@ -610,8 +610,8 @@ function createLogger(module, options) {
610
610
  }
611
611
  var asyncLocalStorage, ConsoleTransport, FileTransport, HttpTransport, SINGLETON_KEY, Logger, logger;
612
612
  var init_dist = __esm({
613
- "../../node_modules/@parisgroup-ai/logger/dist/index.js"() {
614
- init_logger_base_CHajMTDD();
613
+ "../../node_modules/@parisgroup-ai/logger/dist/index.mjs"() {
614
+ init_logger_base_CG4i4G4l();
615
615
  asyncLocalStorage = new AsyncLocalStorage();
616
616
  ConsoleTransport = class {
617
617
  log(entry, formatted) {
@@ -859,6 +859,21 @@ function createHttpProvider(apiUrl, token2) {
859
859
  const res = await apiFetch(`${base}/courses`, token2);
860
860
  return res.courses;
861
861
  },
862
+ listCreatorCourses: async (filters) => {
863
+ const params = new URLSearchParams();
864
+ params.append("status", filters.status || "all");
865
+ if (filters.search) params.append("search", filters.search);
866
+ if (filters.sortBy) params.append("sortBy", filters.sortBy);
867
+ if (filters.sortOrder) params.append("sortOrder", filters.sortOrder);
868
+ if (filters.limit) params.append("limit", filters.limit.toString());
869
+ if (filters.offset) params.append("offset", filters.offset.toString());
870
+ if (filters.projectId) params.append("projectId", filters.projectId);
871
+ const res = await apiFetch(
872
+ `${base}/creator-courses?${params.toString()}`,
873
+ token2
874
+ );
875
+ return res.courses;
876
+ },
862
877
  select: async (_userId, courseId) => {
863
878
  const res = await apiFetch(`${base}/courses/select`, token2, {
864
879
  method: "POST",
@@ -1265,12 +1280,16 @@ function formatCourseList(courses3) {
1265
1280
  month: "short",
1266
1281
  day: "numeric"
1267
1282
  }) : null;
1268
- lines.push(` ${idx + 1}. ${course.title}`);
1283
+ const archivedBadge = course.status === "archived" ? " [ARQUIVADO]" : "";
1284
+ lines.push(` ${idx + 1}. ${course.title}${archivedBadge}`);
1269
1285
  lines.push(` ${bar}`);
1270
1286
  lines.push(` Professor: ${course.creatorName}`);
1271
1287
  if (enrolledDate) {
1272
1288
  lines.push(` Inscrito em: ${enrolledDate}`);
1273
1289
  }
1290
+ if (course.status === "archived") {
1291
+ lines.push(` \u26A0 Arquivado \u2014 somente leitura (use \`tostudy lesson\`)`);
1292
+ }
1274
1293
  lines.push("");
1275
1294
  });
1276
1295
  lines.push("\u2192 tostudy select <n\xFAmero> para ativar um curso");
@@ -1291,10 +1310,19 @@ function formatProgress(data) {
1291
1310
  `Li\xE7\xF5es conclu\xEDdas: ${data.completedLessons}`,
1292
1311
  `Tempo restante estimado: ~${data.estimatedMinutesRemaining} min`,
1293
1312
  "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1294
- "",
1295
- "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
1296
- "\u2192 tostudy lesson para ver o conte\xFAdo da li\xE7\xE3o atual"
1313
+ ""
1297
1314
  ];
1315
+ if (data.isArchived) {
1316
+ lines.push(
1317
+ "\u26A0 Curso arquivado \u2014 somente leitura.",
1318
+ "\u2192 tostudy lesson para reler o conte\xFAdo j\xE1 estudado"
1319
+ );
1320
+ } else {
1321
+ lines.push(
1322
+ "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
1323
+ "\u2192 tostudy lesson para ver o conte\xFAdo da li\xE7\xE3o atual"
1324
+ );
1325
+ }
1298
1326
  return lines.join("\n");
1299
1327
  }
1300
1328
  function formatLesson(data) {
@@ -2303,7 +2331,9 @@ var init_errors_pt_br = __esm({
2303
2331
  vaultNotFound: "\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n",
2304
2332
  logoutSuccess: "\n Deslogado com sucesso.\n",
2305
2333
  workspaceCommandDescription: "Gerenciar workspace de estudo local",
2306
- workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo"
2334
+ workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2335
+ 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",
2336
+ 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"
2307
2337
  };
2308
2338
  }
2309
2339
  });
@@ -2320,7 +2350,9 @@ var init_errors_en_us = __esm({
2320
2350
  vaultNotFound: "\u274C Vault not found. Run 'tostudy vault init' first.\n",
2321
2351
  logoutSuccess: "\n Logged out successfully.\n",
2322
2352
  workspaceCommandDescription: "Manage local study workspace",
2323
- workspaceSetupDescription: "Create the workspace structure for the active course"
2353
+ workspaceSetupDescription: "Create the workspace structure for the active course",
2354
+ 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",
2355
+ courseArchived: "This course has been archived and is available for reading only.\nUse `tostudy lesson` to review the content you've already studied.\n"
2324
2356
  };
2325
2357
  }
2326
2358
  });
@@ -2348,6 +2380,14 @@ function resolveLocale() {
2348
2380
  function getErrors(locale) {
2349
2381
  return BUNDLES[locale ?? resolveLocale()];
2350
2382
  }
2383
+ function isInsufficientCreditsError(err) {
2384
+ const msg = err instanceof Error ? err.message : String(err);
2385
+ return msg.includes("INSUFFICIENT_CREDITS");
2386
+ }
2387
+ function isCourseArchivedError(err) {
2388
+ const msg = err instanceof Error ? err.message : String(err);
2389
+ return msg.includes("COURSE_ARCHIVED");
2390
+ }
2351
2391
  var BUNDLES, _cachedLocale;
2352
2392
  var init_errors = __esm({
2353
2393
  "src/errors/index.ts"() {
@@ -2544,7 +2584,7 @@ var CLI_VERSION;
2544
2584
  var init_version = __esm({
2545
2585
  "src/version.ts"() {
2546
2586
  "use strict";
2547
- CLI_VERSION = true ? "0.11.0" : "0.7.1";
2587
+ CLI_VERSION = true ? "0.11.2" : "0.7.1";
2548
2588
  }
2549
2589
  });
2550
2590
 
@@ -2947,16 +2987,24 @@ var init_courses2 = __esm({
2947
2987
  init_guards();
2948
2988
  init_formatter();
2949
2989
  logger4 = createLogger("cli:courses");
2950
- coursesCommand = new Command5("courses").description("List your enrolled courses with progress").option("--json", "Output structured JSON").action(async (opts) => {
2990
+ 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) => {
2951
2991
  try {
2952
2992
  const session = await requireSession();
2953
2993
  const data = createHttpProvider(session.apiUrl, session.token);
2954
2994
  const deps = { data, logger: logger4 };
2955
- const courses3 = await listCourses({ userId: session.userId }, deps);
2995
+ let coursesToList;
2996
+ if (opts.mine) {
2997
+ coursesToList = await data.courses.listCreatorCourses({
2998
+ userId: session.userId,
2999
+ status: "all"
3000
+ });
3001
+ } else {
3002
+ coursesToList = await listCourses({ userId: session.userId }, deps);
3003
+ }
2956
3004
  if (opts.json) {
2957
- output(courses3, { json: true });
3005
+ output(coursesToList, { json: true });
2958
3006
  } else {
2959
- output(formatCourseList(courses3), { json: false });
3007
+ output(formatCourseList(coursesToList), { json: false });
2960
3008
  }
2961
3009
  } catch (err) {
2962
3010
  const msg = err instanceof Error ? err.message : String(err);
@@ -28729,6 +28777,12 @@ var init_users = __esm({
28729
28777
  // Stripe Integration
28730
28778
  stripeCustomerId: varchar("stripe_customer_id", { length: 255 }),
28731
28779
  // Stripe Customer ID (cus_xxx) for payment method reuse
28780
+ // Paris Group integration: server-to-server provisioning attribution.
28781
+ // Set by `POST /api/internal/v1/cohort-enrollments` so the same paris
28782
+ // participant maps idempotently to a single tostudy user, and outbound
28783
+ // webhooks can scope themselves to `provisioning_source='paris-immersion'`.
28784
+ provisioningSource: text("provisioning_source"),
28785
+ externalUserId: text("external_user_id"),
28732
28786
  metadata: jsonb("metadata").$type().default({}),
28733
28787
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
28734
28788
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
@@ -28747,7 +28801,12 @@ var init_users = __esm({
28747
28801
  // FEAT-754: Portfolio index for public portfolio queries
28748
28802
  portfolioEnabledIdx: index("users_portfolio_enabled_idx").on(table.portfolioEnabled),
28749
28803
  // Admin analytics: date-range queries on users.createdAt (gte/lte)
28750
- createdAtIdx: index("users_created_at_idx").on(table.createdAt)
28804
+ createdAtIdx: index("users_created_at_idx").on(table.createdAt),
28805
+ // Paris Group integration: composite lookup for idempotent provisioning.
28806
+ externalIdIdx: index("idx_users_external_id").on(
28807
+ table.provisioningSource,
28808
+ table.externalUserId
28809
+ )
28751
28810
  })
28752
28811
  );
28753
28812
  }
@@ -28888,6 +28947,8 @@ var init_user_preferences = __esm({
28888
28947
  emailNewReplies: boolean("email_new_replies").default(true).notNull(),
28889
28948
  /** Whether user wants push notifications for mentorship (via MCP) */
28890
28949
  pushMentoria: boolean("push_mentoria").default(true).notNull(),
28950
+ // i18n nickname pilot column (dogfooding)
28951
+ nicknameI18n: jsonb("nickname_i18n").$type(),
28891
28952
  // Timestamps
28892
28953
  createdAt: timestamp("created_at").defaultNow().notNull(),
28893
28954
  updatedAt: timestamp("updated_at").defaultNow().notNull()
@@ -29071,6 +29132,20 @@ var init_course_categories = __esm({
29071
29132
  }
29072
29133
  });
29073
29134
 
29135
+ // ../../packages/database/src/schema/course-visibility.ts
29136
+ var courseVisibilityEnum;
29137
+ var init_course_visibility = __esm({
29138
+ "../../packages/database/src/schema/course-visibility.ts"() {
29139
+ "use strict";
29140
+ init_pg_core();
29141
+ courseVisibilityEnum = pgEnum("course_visibility", [
29142
+ "public",
29143
+ "unlisted",
29144
+ "private_allowlist"
29145
+ ]);
29146
+ }
29147
+ });
29148
+
29074
29149
  // ../../packages/database/src/schema/courses.ts
29075
29150
  var courseStatusEnum, courseFormatEnum, courseLevelEnum, creatorReviewStatusEnum, studyChannelEnum, courses;
29076
29151
  var init_courses3 = __esm({
@@ -29079,6 +29154,7 @@ var init_courses3 = __esm({
29079
29154
  init_pg_core();
29080
29155
  init_users();
29081
29156
  init_course_categories();
29157
+ init_course_visibility();
29082
29158
  courseStatusEnum = pgEnum("course_status", [
29083
29159
  "draft",
29084
29160
  "pending_review",
@@ -29111,6 +29187,7 @@ var init_courses3 = __esm({
29111
29187
  type: varchar("type", { length: 50 }),
29112
29188
  // "self-paced" | "with-mentorship"
29113
29189
  status: courseStatusEnum("status").default("draft"),
29190
+ visibility: courseVisibilityEnum("visibility").default("public").notNull(),
29114
29191
  featured: boolean("featured").default(false).notNull(),
29115
29192
  verified: boolean("verified").default(false).notNull(),
29116
29193
  isEssential: boolean("is_essential").default(false).notNull(),
@@ -29206,7 +29283,20 @@ var init_courses3 = __esm({
29206
29283
  // Widened to 800 in BUG-1346 (GH #382) — `deriveCertificateSummary` can
29207
29284
  // return up to 203 chars (truncated + "..."), and AI-path edge cases
29208
29285
  // exceeded varchar(200) silently. 800 covers worst observed (~660) + buffer.
29209
- certificateSummary: varchar("certificate_summary", { length: 800 })
29286
+ certificateSummary: varchar("certificate_summary", { length: 800 }),
29287
+ /**
29288
+ * paris-immersion onboarding gate: true quando o curso foi endossado
29289
+ * pela equipe Paris para ser servido como onboarding de turmas. Default
29290
+ * false. NÃO afeta o marketplace ou a publicação normal — apenas o
29291
+ * endpoint `/api/internal/v1/courses?tag=paris-onboarding&endorsed=true`
29292
+ * filtra por este campo.
29293
+ *
29294
+ * Auto-marcado true quando o creator está em `paris_authorized_creators`
29295
+ * E o curso tem `tags` contendo 'paris-onboarding' (ver application/paris-endorsement.ts).
29296
+ * Admins paris-immersion podem endossar manualmente via
29297
+ * POST /api/internal/v1/courses/:slug/endorse.
29298
+ */
29299
+ parisEndorsed: boolean("paris_endorsed").default(false).notNull()
29210
29300
  },
29211
29301
  (table) => ({
29212
29302
  creatorIdIdx: index("courses_creator_id_idx").on(table.creatorId),
@@ -29233,6 +29323,8 @@ var init_courses3 = __esm({
29233
29323
  // B-tree for FIFO ordering
29234
29324
  statusSubmittedIdx: index("courses_status_submitted_idx").on(table.status, table.submittedAt),
29235
29325
  // Composite for queue queries
29326
+ visibilityIdx: index("courses_visibility_idx").on(table.visibility),
29327
+ statusVisibilityIdx: index("courses_status_visibility_idx").on(table.status, table.visibility),
29236
29328
  // Course completeness tracking index
29237
29329
  completenessStatusIdx: index("courses_completeness_status_idx").on(table.completenessStatus),
29238
29330
  // Verified Seal Quality System - Indexes for quality metrics filtering
@@ -29770,6 +29862,9 @@ var init_badges = __esm({
29770
29862
  // Link to specific module (for module badges)
29771
29863
  isEssentialBadge: boolean("is_essential_badge").default(false).notNull(),
29772
29864
  // Flag for essential course badges
29865
+ // i18n dynamic translation pilot columns (Expand pattern)
29866
+ nameI18n: jsonb("name_i18n").$type(),
29867
+ descriptionI18n: jsonb("description_i18n").$type(),
29773
29868
  // Display
29774
29869
  order: integer("order").notNull().default(0),
29775
29870
  // Display order
@@ -30203,6 +30298,34 @@ var init_course_reports = __esm({
30203
30298
  }
30204
30299
  });
30205
30300
 
30301
+ // ../../packages/database/src/schema/cohorts.ts
30302
+ var cohorts;
30303
+ var init_cohorts = __esm({
30304
+ "../../packages/database/src/schema/cohorts.ts"() {
30305
+ "use strict";
30306
+ init_pg_core();
30307
+ cohorts = pgTable(
30308
+ "cohorts",
30309
+ {
30310
+ id: uuid("id").defaultRandom().primaryKey(),
30311
+ slug: text("slug").notNull().unique(),
30312
+ name: text("name").notNull(),
30313
+ /** e.g. "paris-immersion" — namespace for the external system that owns this cohort. */
30314
+ source: text("source").notNull(),
30315
+ /** Stable identifier in the source system (e.g. immersion slug). */
30316
+ sourceExternalId: text("source_external_id").notNull(),
30317
+ startsAt: timestamp("starts_at", { withTimezone: true }).notNull(),
30318
+ endsAt: timestamp("ends_at", { withTimezone: true }).notNull(),
30319
+ metadata: jsonb("metadata").$type().notNull().default({}),
30320
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
30321
+ },
30322
+ (table) => ({
30323
+ sourceIdx: index("idx_cohorts_source").on(table.source, table.sourceExternalId)
30324
+ })
30325
+ );
30326
+ }
30327
+ });
30328
+
30206
30329
  // ../../packages/database/src/schema/enrollments.ts
30207
30330
  var enrollmentStatusEnum, enrollmentSourceEnum, exerciseEntryLevelEnum, EXERCISE_ENTRY_LEVELS, enrollments;
30208
30331
  var init_enrollments = __esm({
@@ -30211,6 +30334,7 @@ var init_enrollments = __esm({
30211
30334
  init_pg_core();
30212
30335
  init_users();
30213
30336
  init_courses3();
30337
+ init_cohorts();
30214
30338
  enrollmentStatusEnum = pgEnum("enrollment_status", [
30215
30339
  "active",
30216
30340
  "preview",
@@ -30264,6 +30388,10 @@ var init_enrollments = __esm({
30264
30388
  // adjusts via the `enrollments.setExerciseLevel` mutation.
30265
30389
  entryLevel: exerciseEntryLevelEnum("entry_level").notNull().default("L4"),
30266
30390
  currentLevel: exerciseEntryLevelEnum("current_level").notNull().default("L4"),
30391
+ // Paris Group integration: links a provisioned enrollment to its cohort.
30392
+ // Set when tostudy provisions an account via `POST /api/internal/v1/cohort-enrollments`.
30393
+ // Nullable for non-cohort enrollments (self-enrollment, subscriptions, etc.).
30394
+ cohortId: uuid("cohort_id").references(() => cohorts.id, { onDelete: "set null" }),
30267
30395
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
30268
30396
  metadata: jsonb("metadata").$type().default({})
30269
30397
  },
@@ -30282,7 +30410,9 @@ var init_enrollments = __esm({
30282
30410
  courseCurrentLevelIdx: index("enrollments_course_current_level_idx").on(
30283
30411
  table.courseId,
30284
30412
  table.currentLevel
30285
- )
30413
+ ),
30414
+ // Paris Group integration: partial index for cohort lookups.
30415
+ cohortIdIdx: index("idx_enrollments_cohort_id").on(table.cohortId)
30286
30416
  })
30287
30417
  );
30288
30418
  }
@@ -30443,6 +30573,29 @@ var init_course_index = __esm({
30443
30573
  }
30444
30574
  });
30445
30575
 
30576
+ // ../../packages/database/src/schema/paris-authorized-creators.ts
30577
+ var parisAuthorizedCreators;
30578
+ var init_paris_authorized_creators = __esm({
30579
+ "../../packages/database/src/schema/paris-authorized-creators.ts"() {
30580
+ "use strict";
30581
+ init_pg_core();
30582
+ init_users();
30583
+ parisAuthorizedCreators = pgTable(
30584
+ "paris_authorized_creators",
30585
+ {
30586
+ userId: uuid("user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }),
30587
+ /** Quem aprovou (admin Paris). NULL se setado via seed/SQL direto. */
30588
+ authorizedBy: uuid("authorized_by").references(() => users.id, { onDelete: "set null" }),
30589
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
30590
+ notes: text("notes")
30591
+ },
30592
+ (table) => ({
30593
+ authorizedByIdx: index("idx_paris_authorized_creators_authorized_by").on(table.authorizedBy)
30594
+ })
30595
+ );
30596
+ }
30597
+ });
30598
+
30446
30599
  // ../../packages/database/src/schema/enrollment-channel-changes.ts
30447
30600
  var enrollmentChannelChanges;
30448
30601
  var init_enrollment_channel_changes = __esm({
@@ -33693,6 +33846,8 @@ var init_mentorship_services = __esm({
33693
33846
  // 30, 45, 60, 90
33694
33847
  priceCents: integer("price_cents").notNull(),
33695
33848
  // min 5000 for 1:1, 3000 for group/workshop
33849
+ currency: varchar("currency", { length: 3 }).notNull().default("BRL"),
33850
+ // ISO 4217 uppercase; Phase 1 threads through DTOs
33696
33851
  priceCredits: integer("price_credits"),
33697
33852
  // nullable during migration, NOT NULL after Phase 3
33698
33853
  maxParticipants: integer("max_participants").default(1).notNull(),
@@ -33943,37 +34098,50 @@ var init_mentorship_packages = __esm({
33943
34098
  init_users();
33944
34099
  init_mentorship_services();
33945
34100
  init_zod();
33946
- mentorshipPackages2 = pgTable("mentorship_packages", {
33947
- id: uuid("id").defaultRandom().primaryKey(),
33948
- mentorId: uuid("mentor_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
33949
- serviceId: uuid("service_id").references(() => mentorshipServices2.id, { onDelete: "cascade" }).notNull(),
33950
- // Package configuration
33951
- numSessions: integer("num_sessions").notNull(),
33952
- // 4, 8, or 12 (enforced by check constraint)
33953
- discountPercent: integer("discount_percent").notNull().default(0),
33954
- // 0-50 (enforced by check constraint)
33955
- validityMonths: integer("validity_months").notNull().default(6),
33956
- // 3, 6, or 12
33957
- // Calculated prices (stored for consistency and to avoid recalculation issues)
33958
- pricePerSessionCents: integer("price_per_session_cents").notNull(),
33959
- // Price per session after discount
33960
- totalPriceCents: integer("total_price_cents").notNull(),
33961
- // Total package price
33962
- // Status
33963
- isActive: boolean("is_active").notNull().default(true),
33964
- // Timestamps
33965
- createdAt: timestamp("created_at").defaultNow().notNull(),
33966
- updatedAt: timestamp("updated_at").defaultNow().notNull()
33967
- }, (table) => ({
33968
- mentorIdIdx: index("mentorship_packages_mentor_id_idx").on(table.mentorId),
33969
- serviceIdIdx: index("mentorship_packages_service_id_idx").on(table.serviceId),
33970
- isActiveIdx: index("mentorship_packages_is_active_idx").on(table.isActive),
33971
- mentorServiceActiveIdx: index("mentorship_packages_mentor_service_active_idx").on(table.mentorId, table.serviceId, table.isActive),
33972
- // Check constraints for business rules
33973
- numSessionsCheck: check("num_sessions_check", sql`${table.numSessions} IN (4, 8, 12)`),
33974
- discountPercentCheck: check("discount_percent_check", sql`${table.discountPercent} >= 0 AND ${table.discountPercent} <= 50`),
33975
- validityMonthsCheck: check("validity_months_check", sql`${table.validityMonths} IN (3, 6, 12)`)
33976
- }));
34101
+ mentorshipPackages2 = pgTable(
34102
+ "mentorship_packages",
34103
+ {
34104
+ id: uuid("id").defaultRandom().primaryKey(),
34105
+ mentorId: uuid("mentor_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
34106
+ serviceId: uuid("service_id").references(() => mentorshipServices2.id, { onDelete: "cascade" }).notNull(),
34107
+ // Package configuration
34108
+ numSessions: integer("num_sessions").notNull(),
34109
+ // 4, 8, or 12 (enforced by check constraint)
34110
+ discountPercent: integer("discount_percent").notNull().default(0),
34111
+ // 0-50 (enforced by check constraint)
34112
+ validityMonths: integer("validity_months").notNull().default(6),
34113
+ // 3, 6, or 12
34114
+ // Calculated prices (stored for consistency and to avoid recalculation issues)
34115
+ pricePerSessionCents: integer("price_per_session_cents").notNull(),
34116
+ // Price per session after discount
34117
+ totalPriceCents: integer("total_price_cents").notNull(),
34118
+ // Total package price
34119
+ currency: varchar("currency", { length: 3 }).notNull().default("BRL"),
34120
+ // ISO 4217 uppercase; Phase 2 threads through DTOs (derived from service.currency)
34121
+ // Status
34122
+ isActive: boolean("is_active").notNull().default(true),
34123
+ // Timestamps
34124
+ createdAt: timestamp("created_at").defaultNow().notNull(),
34125
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
34126
+ },
34127
+ (table) => ({
34128
+ mentorIdIdx: index("mentorship_packages_mentor_id_idx").on(table.mentorId),
34129
+ serviceIdIdx: index("mentorship_packages_service_id_idx").on(table.serviceId),
34130
+ isActiveIdx: index("mentorship_packages_is_active_idx").on(table.isActive),
34131
+ mentorServiceActiveIdx: index("mentorship_packages_mentor_service_active_idx").on(
34132
+ table.mentorId,
34133
+ table.serviceId,
34134
+ table.isActive
34135
+ ),
34136
+ // Check constraints for business rules
34137
+ numSessionsCheck: check("num_sessions_check", sql`${table.numSessions} IN (4, 8, 12)`),
34138
+ discountPercentCheck: check(
34139
+ "discount_percent_check",
34140
+ sql`${table.discountPercent} >= 0 AND ${table.discountPercent} <= 50`
34141
+ ),
34142
+ validityMonthsCheck: check("validity_months_check", sql`${table.validityMonths} IN (3, 6, 12)`)
34143
+ })
34144
+ );
33977
34145
  PACKAGE_SESSION_OPTIONS = [4, 8, 12];
33978
34146
  PACKAGE_VALIDITY_OPTIONS = [3, 6, 12];
33979
34147
  MAX_DISCOUNT_PERCENT = 50;
@@ -34207,7 +34375,7 @@ var init_mentorship_transactions = __esm({
34207
34375
  transactionType: mentorshipTransactionTypeEnum("transaction_type").default("session").notNull(),
34208
34376
  packageId: uuid("package_id").references(() => mentorshipPackages2.id, { onDelete: "set null" }),
34209
34377
  serviceId: uuid("service_id").references(() => mentorshipServices2.id, { onDelete: "set null" }),
34210
- // Financial amounts in cents (BRL) - Story 19.3: Split tracking
34378
+ // Financial amounts in cents - Story 19.3: Split tracking
34211
34379
  grossAmountCents: integer("gross_amount_cents").notNull(),
34212
34380
  // Original price before discount
34213
34381
  discountCents: integer("discount_cents").default(0).notNull(),
@@ -34216,6 +34384,8 @@ var init_mentorship_transactions = __esm({
34216
34384
  // 15% of net amount
34217
34385
  mentorPayoutCents: integer("mentor_payout_cents").notNull(),
34218
34386
  // 85% of net amount (netAmountCents)
34387
+ currency: varchar("currency", { length: 3 }).notNull().default("BRL"),
34388
+ // ISO 4217 uppercase; Phase 3 threads through DTOs (derived from package.currency / service.currency)
34219
34389
  // Transaction status
34220
34390
  status: mentorshipTransactionStatusEnum("status").default("pending").notNull(),
34221
34391
  // Stripe integration
@@ -34462,53 +34632,66 @@ var init_transactions = __esm({
34462
34632
  init_pg_core();
34463
34633
  init_courses3();
34464
34634
  init_users();
34465
- transactions2 = pgTable("transactions", {
34466
- id: uuid("id").defaultRandom().primaryKey(),
34467
- // Stripe references
34468
- stripePaymentIntentId: varchar("stripe_payment_intent_id", { length: 255 }).notNull().unique(),
34469
- stripeTransferId: varchar("stripe_transfer_id", { length: 255 }),
34470
- // Stripe Transfer ID (if separate transfer)
34471
- // Foreign keys
34472
- courseId: uuid("course_id").notNull().references(() => courses.id, { onDelete: "restrict" }),
34473
- creatorId: uuid("creator_id").notNull().references(() => users.id, { onDelete: "restrict" }),
34474
- studentId: uuid("student_id").notNull().references(() => users.id, { onDelete: "restrict" }),
34475
- // Financial details (all amounts in cents)
34476
- amount: integer("amount").notNull(),
34477
- // Total amount charged
34478
- platformFee: integer("platform_fee").notNull(),
34479
- // Amount kept by platform
34480
- creatorPayout: integer("creator_payout").notNull(),
34481
- // Amount transferred to creator
34482
- stripeFee: integer("stripe_fee"),
34483
- // Stripe processing fee (nullable, updated from webhook)
34484
- netPlatformRevenue: integer("net_platform_revenue"),
34485
- // platformFee - stripeFee
34486
- // Transaction metadata
34487
- type: varchar("type", { length: 20 }).notNull(),
34488
- // 'mentoria' or 'self-paced'
34489
- paymentMethod: varchar("payment_method", { length: 50 }),
34490
- // 'card' or 'pix'
34491
- status: varchar("status", { length: 20 }).notNull().default("pending"),
34492
- // 'pending', 'succeeded', 'failed', 'refunded'
34493
- // Timestamps
34494
- createdAt: timestamp("created_at").defaultNow().notNull(),
34495
- processedAt: timestamp("processed_at"),
34496
- refundedAt: timestamp("refunded_at"),
34497
- updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
34498
- }, (table) => ({
34499
- // Indexes for fast lookups
34500
- stripePaymentIntentIdIdx: index("transactions_stripe_payment_intent_id_idx").on(table.stripePaymentIntentId),
34501
- creatorIdIdx: index("transactions_creator_id_idx").on(table.creatorId),
34502
- studentIdIdx: index("transactions_student_id_idx").on(table.studentId),
34503
- statusIdx: index("transactions_status_idx").on(table.status),
34504
- paymentMethodIdx: index("transactions_payment_method_idx").on(table.paymentMethod),
34505
- // For PIX analytics
34506
- createdAtIdx: index("transactions_created_at_idx").on(table.createdAt),
34507
- // For analytics queries
34508
- refundedAtIdx: index("transactions_refunded_at_idx").on(table.refundedAt),
34509
- // Issue #4: Database Index Gaps - Composite index for refund lookups
34510
- refundLookupIdx: index("transactions_refund_lookup_idx").on(table.courseId, table.studentId, table.status, table.createdAt)
34511
- }));
34635
+ transactions2 = pgTable(
34636
+ "transactions",
34637
+ {
34638
+ id: uuid("id").defaultRandom().primaryKey(),
34639
+ // Stripe references
34640
+ stripePaymentIntentId: varchar("stripe_payment_intent_id", { length: 255 }).notNull().unique(),
34641
+ stripeTransferId: varchar("stripe_transfer_id", { length: 255 }),
34642
+ // Stripe Transfer ID (if separate transfer)
34643
+ // Foreign keys
34644
+ courseId: uuid("course_id").notNull().references(() => courses.id, { onDelete: "restrict" }),
34645
+ creatorId: uuid("creator_id").notNull().references(() => users.id, { onDelete: "restrict" }),
34646
+ studentId: uuid("student_id").notNull().references(() => users.id, { onDelete: "restrict" }),
34647
+ // Financial details (all amounts in cents)
34648
+ amount: integer("amount").notNull(),
34649
+ // Total amount charged
34650
+ currency: varchar("currency", { length: 3 }).notNull().default("BRL"),
34651
+ // ISO 4217 uppercase; seeded from Stripe charge.currency on webhook insert
34652
+ platformFee: integer("platform_fee").notNull(),
34653
+ // Amount kept by platform
34654
+ creatorPayout: integer("creator_payout").notNull(),
34655
+ // Amount transferred to creator
34656
+ stripeFee: integer("stripe_fee"),
34657
+ // Stripe processing fee (nullable, updated from webhook)
34658
+ netPlatformRevenue: integer("net_platform_revenue"),
34659
+ // platformFee - stripeFee
34660
+ // Transaction metadata
34661
+ type: varchar("type", { length: 20 }).notNull(),
34662
+ // 'mentoria' or 'self-paced'
34663
+ paymentMethod: varchar("payment_method", { length: 50 }),
34664
+ // 'card' or 'pix'
34665
+ status: varchar("status", { length: 20 }).notNull().default("pending"),
34666
+ // 'pending', 'succeeded', 'failed', 'refunded'
34667
+ // Timestamps
34668
+ createdAt: timestamp("created_at").defaultNow().notNull(),
34669
+ processedAt: timestamp("processed_at"),
34670
+ refundedAt: timestamp("refunded_at"),
34671
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
34672
+ },
34673
+ (table) => ({
34674
+ // Indexes for fast lookups
34675
+ stripePaymentIntentIdIdx: index("transactions_stripe_payment_intent_id_idx").on(
34676
+ table.stripePaymentIntentId
34677
+ ),
34678
+ creatorIdIdx: index("transactions_creator_id_idx").on(table.creatorId),
34679
+ studentIdIdx: index("transactions_student_id_idx").on(table.studentId),
34680
+ statusIdx: index("transactions_status_idx").on(table.status),
34681
+ paymentMethodIdx: index("transactions_payment_method_idx").on(table.paymentMethod),
34682
+ // For PIX analytics
34683
+ createdAtIdx: index("transactions_created_at_idx").on(table.createdAt),
34684
+ // For analytics queries
34685
+ refundedAtIdx: index("transactions_refunded_at_idx").on(table.refundedAt),
34686
+ // Issue #4: Database Index Gaps - Composite index for refund lookups
34687
+ refundLookupIdx: index("transactions_refund_lookup_idx").on(
34688
+ table.courseId,
34689
+ table.studentId,
34690
+ table.status,
34691
+ table.createdAt
34692
+ )
34693
+ })
34694
+ );
34512
34695
  }
34513
34696
  });
34514
34697
 
@@ -36827,7 +37010,16 @@ var init_security_audit_log = __esm({
36827
37010
  "admin.impersonate_start",
36828
37011
  "admin.impersonate_end",
36829
37012
  "admin.config_change",
36830
- "admin.student_progress_reset"
37013
+ "admin.student_progress_reset",
37014
+ // Paris integration events (PR #575)
37015
+ "account.claimed_by_external",
37016
+ "account.claim_denied_by_policy",
37017
+ "user.created_by_external",
37018
+ "authorized_creator.added",
37019
+ "authorized_creator.add_attempt_unknown_user",
37020
+ "authorized_creator.removed",
37021
+ "course.paris_endorsed",
37022
+ "course.paris_endorsement_revoked"
36831
37023
  ]);
36832
37024
  securityEventResultEnum = pgEnum("security_event_result", [
36833
37025
  "success",
@@ -36936,7 +37128,7 @@ var init_credit_movement_audit_log = __esm({
36936
37128
  });
36937
37129
 
36938
37130
  // ../../packages/database/src/schema/marketing.ts
36939
- var testimonialStatusEnum, marketingContentTypeEnum, creatorTestimonials2, marketingContent2, newsletterSubscribers, contactSubmissions;
37131
+ var testimonialStatusEnum, testimonialPlacementEnum, marketingContentTypeEnum, creatorTestimonials2, marketingContent2, newsletterSubscribers, contactSubmissions;
36940
37132
  var init_marketing = __esm({
36941
37133
  "../../packages/database/src/schema/marketing.ts"() {
36942
37134
  "use strict";
@@ -36947,6 +37139,11 @@ var init_marketing = __esm({
36947
37139
  "published",
36948
37140
  "archived"
36949
37141
  ]);
37142
+ testimonialPlacementEnum = pgEnum("testimonial_placement", [
37143
+ "homepage",
37144
+ "creators",
37145
+ "both"
37146
+ ]);
36950
37147
  marketingContentTypeEnum = pgEnum("marketing_content_type", [
36951
37148
  "text",
36952
37149
  // Simple text content
@@ -36981,6 +37178,7 @@ var init_marketing = __esm({
36981
37178
  featured: boolean("featured").default(false).notNull(),
36982
37179
  // Show in carousel
36983
37180
  displayOrder: integer("display_order").default(0).notNull(),
37181
+ placement: testimonialPlacementEnum("placement").default("creators").notNull(),
36984
37182
  // Localization support (null = applies to all locales)
36985
37183
  locale: varchar("locale", { length: 10 }),
36986
37184
  // Metadata
@@ -36993,7 +37191,8 @@ var init_marketing = __esm({
36993
37191
  statusIdx: index("creator_testimonials_status_idx").on(table.status),
36994
37192
  featuredIdx: index("creator_testimonials_featured_idx").on(table.featured),
36995
37193
  displayOrderIdx: index("creator_testimonials_display_order_idx").on(table.displayOrder),
36996
- localeIdx: index("creator_testimonials_locale_idx").on(table.locale)
37194
+ localeIdx: index("creator_testimonials_locale_idx").on(table.locale),
37195
+ placementIdx: index("creator_testimonials_placement_idx").on(table.placement)
36997
37196
  })
36998
37197
  );
36999
37198
  marketingContent2 = pgTable(
@@ -40896,6 +41095,75 @@ var init_brainstorm_knowledge_bases = __esm({
40896
41095
  }
40897
41096
  });
40898
41097
 
41098
+ // ../../packages/database/src/schema/course-allowlist.ts
41099
+ var courseAllowlist;
41100
+ var init_course_allowlist = __esm({
41101
+ "../../packages/database/src/schema/course-allowlist.ts"() {
41102
+ "use strict";
41103
+ init_pg_core();
41104
+ init_courses3();
41105
+ init_users();
41106
+ courseAllowlist = pgTable(
41107
+ "course_allowlist",
41108
+ {
41109
+ id: uuid("id").defaultRandom().primaryKey(),
41110
+ courseId: uuid("course_id").references(() => courses.id, { onDelete: "cascade" }).notNull(),
41111
+ email: varchar("email", { length: 255 }).notNull(),
41112
+ userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }),
41113
+ invitedBy: uuid("invited_by").references(() => users.id, {
41114
+ onDelete: "set null"
41115
+ }),
41116
+ invitedAt: timestamp("invited_at", { withTimezone: true }).defaultNow().notNull(),
41117
+ resolvedAt: timestamp("resolved_at", { withTimezone: true })
41118
+ },
41119
+ (table) => ({
41120
+ courseEmailUnique: uniqueIndex("course_allowlist_course_email_unique").on(
41121
+ table.courseId,
41122
+ table.email
41123
+ ),
41124
+ courseIdIdx: index("course_allowlist_course_id_idx").on(table.courseId)
41125
+ // partial indexes (user_id IS NOT NULL / IS NULL) are created via raw SQL in the migration — see drizzle-kit $N gotcha in packages/database/AGENTS.md
41126
+ })
41127
+ );
41128
+ }
41129
+ });
41130
+
41131
+ // ../../packages/database/src/schema/idempotency.ts
41132
+ var pgBackendIdempotencyKeys;
41133
+ var init_idempotency = __esm({
41134
+ "../../packages/database/src/schema/idempotency.ts"() {
41135
+ "use strict";
41136
+ init_drizzle_orm();
41137
+ init_pg_core();
41138
+ pgBackendIdempotencyKeys = pgTable(
41139
+ "pg_backend_idempotency_keys",
41140
+ {
41141
+ reservationId: uuid("reservation_id").primaryKey().defaultRandom(),
41142
+ idempotencyKey: text("idempotency_key").notNull(),
41143
+ resource: text("resource").notNull(),
41144
+ operation: text("operation").notNull(),
41145
+ fingerprint: text("fingerprint").notNull(),
41146
+ scopeKey: text("scope_key"),
41147
+ status: text("status").notNull(),
41148
+ response: jsonb("response"),
41149
+ reservedAt: timestamp("reserved_at", { withTimezone: true }).notNull().defaultNow(),
41150
+ committedAt: timestamp("committed_at", { withTimezone: true }),
41151
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
41152
+ },
41153
+ (t) => [
41154
+ uniqueIndex("pg_backend_idempotency_unique").on(
41155
+ t.idempotencyKey,
41156
+ t.resource,
41157
+ t.operation,
41158
+ sql`COALESCE(${t.scopeKey}, '')`
41159
+ ),
41160
+ index("pg_backend_idempotency_expires").on(t.expiresAt),
41161
+ index("pg_backend_idempotency_pending").on(t.status, t.reservedAt)
41162
+ ]
41163
+ );
41164
+ }
41165
+ });
41166
+
40899
41167
  // ../../packages/database/src/schema/index.ts
40900
41168
  var schema_exports = {};
40901
41169
  __export(schema_exports, {
@@ -40943,6 +41211,7 @@ __export(schema_exports, {
40943
41211
  challengeType: () => challengeType,
40944
41212
  challenges: () => challenges,
40945
41213
  challengesRelations: () => challengesRelations,
41214
+ cohorts: () => cohorts,
40946
41215
  communityComments: () => communityComments2,
40947
41216
  communityCommentsRelations: () => communityCommentsRelations2,
40948
41217
  communityLikes: () => communityLikes2,
@@ -40968,6 +41237,7 @@ __export(schema_exports, {
40968
41237
  couponUsagesRelations: () => couponUsagesRelations2,
40969
41238
  coupons: () => coupons2,
40970
41239
  couponsRelations: () => couponsRelations2,
41240
+ courseAllowlist: () => courseAllowlist,
40971
41241
  courseAssets: () => courseAssets,
40972
41242
  courseAssetsRelations: () => courseAssetsRelations,
40973
41243
  courseCategories: () => courseCategories,
@@ -41000,6 +41270,7 @@ __export(schema_exports, {
41000
41270
  courseValidations: () => courseValidations,
41001
41271
  courseVideoPrompts: () => courseVideoPrompts,
41002
41272
  courseVideoPromptsRelations: () => courseVideoPromptsRelations,
41273
+ courseVisibilityEnum: () => courseVisibilityEnum,
41003
41274
  courses: () => courses,
41004
41275
  coursesRelations: () => coursesRelations2,
41005
41276
  creatorApplications: () => creatorApplications2,
@@ -41158,6 +41429,7 @@ __export(schema_exports, {
41158
41429
  onboardingAnalytics: () => onboardingAnalytics2,
41159
41430
  onboardingAnalyticsRelations: () => onboardingAnalyticsRelations2,
41160
41431
  optimizerRoleEnum: () => optimizerRoleEnum,
41432
+ parisAuthorizedCreators: () => parisAuthorizedCreators,
41161
41433
  parseSettingValue: () => parseSettingValue,
41162
41434
  participantStatusEnum: () => participantStatusEnum,
41163
41435
  pathCourses: () => pathCourses2,
@@ -41177,6 +41449,7 @@ __export(schema_exports, {
41177
41449
  payoutWithdrawals: () => payoutWithdrawals,
41178
41450
  payouts: () => payouts2,
41179
41451
  payoutsRelations: () => payoutsRelations2,
41452
+ pgBackendIdempotencyKeys: () => pgBackendIdempotencyKeys,
41180
41453
  pixKeyTypeEnum: () => pixKeyTypeEnum,
41181
41454
  platformConfig: () => platformConfig,
41182
41455
  platformFeeTypeEnum: () => platformFeeTypeEnum,
@@ -41308,6 +41581,7 @@ __export(schema_exports, {
41308
41581
  syncLogsRelations: () => syncLogsRelations2,
41309
41582
  systemConfigs: () => systemConfigs,
41310
41583
  systemConfigsRelations: () => systemConfigsRelations,
41584
+ testimonialPlacementEnum: () => testimonialPlacementEnum,
41311
41585
  testimonialStatusEnum: () => testimonialStatusEnum,
41312
41586
  threadStatusEnum: () => threadStatusEnum,
41313
41587
  toneEnum: () => toneEnum,
@@ -41367,6 +41641,8 @@ var init_schema3 = __esm({
41367
41641
  "use strict";
41368
41642
  init_auth_index();
41369
41643
  init_course_index();
41644
+ init_cohorts();
41645
+ init_paris_authorized_creators();
41370
41646
  init_enrollment_channel_changes();
41371
41647
  init_badges();
41372
41648
  init_user_badges();
@@ -41457,6 +41733,9 @@ var init_schema3 = __esm({
41457
41733
  init_announcements();
41458
41734
  init_creator_brainstorm();
41459
41735
  init_brainstorm_knowledge_bases();
41736
+ init_course_visibility();
41737
+ init_course_allowlist();
41738
+ init_idempotency();
41460
41739
  }
41461
41740
  });
41462
41741
 
@@ -41469,6 +41748,14 @@ var init_check_module_unlocked = __esm({
41469
41748
  }
41470
41749
  });
41471
41750
 
41751
+ // ../../packages/database/src/utils/courses-filter.ts
41752
+ var init_courses_filter = __esm({
41753
+ "../../packages/database/src/utils/courses-filter.ts"() {
41754
+ "use strict";
41755
+ init_courses3();
41756
+ }
41757
+ });
41758
+
41472
41759
  // ../../packages/database/src/utils/date-helpers.ts
41473
41760
  var init_date_helpers = __esm({
41474
41761
  "../../packages/database/src/utils/date-helpers.ts"() {
@@ -41494,6 +41781,7 @@ var init_src4 = __esm({
41494
41781
  init_schema3();
41495
41782
  init_schema3();
41496
41783
  init_check_module_unlocked();
41784
+ init_courses_filter();
41497
41785
  init_date_helpers();
41498
41786
  init_lessons();
41499
41787
  init_mentorship_services();
@@ -41791,6 +42079,10 @@ async function runStart(opts, deps = defaultDeps3) {
41791
42079
  process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
41792
42080
  process.exit(1);
41793
42081
  }
42082
+ if (isCourseArchivedError(err)) {
42083
+ deps.stderrWrite(getErrors().courseArchived);
42084
+ process.exit(1);
42085
+ }
41794
42086
  const msg = err instanceof Error ? err.message : String(err);
41795
42087
  deps.error(msg);
41796
42088
  }
@@ -41807,6 +42099,7 @@ var init_start = __esm({
41807
42099
  init_workspace_state();
41808
42100
  init_status();
41809
42101
  init_formatter();
42102
+ init_errors();
41810
42103
  logger9 = createLogger("cli:start");
41811
42104
  defaultDeps3 = {
41812
42105
  requireSession,
@@ -41842,6 +42135,7 @@ var init_start_next = __esm({
41842
42135
  init_course_state();
41843
42136
  init_workspace_state();
41844
42137
  init_formatter();
42138
+ init_errors();
41845
42139
  logger10 = createLogger("cli:start-next");
41846
42140
  startNextCommand = new Command9("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
41847
42141
  try {
@@ -41869,6 +42163,10 @@ var init_start_next = __esm({
41869
42163
  process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
41870
42164
  process.exit(0);
41871
42165
  }
42166
+ if (isCourseArchivedError(err)) {
42167
+ process.stderr.write(getErrors().courseArchived);
42168
+ process.exit(1);
42169
+ }
41872
42170
  const msg = err instanceof Error ? err.message : String(err);
41873
42171
  if (opts.json) jsonError(msg);
41874
42172
  error(msg);
@@ -41890,6 +42188,7 @@ var init_next = __esm({
41890
42188
  init_course_state();
41891
42189
  init_workspace_state();
41892
42190
  init_formatter();
42191
+ init_errors();
41893
42192
  logger11 = createLogger("cli:next");
41894
42193
  nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
41895
42194
  try {
@@ -41915,6 +42214,12 @@ var init_next = __esm({
41915
42214
  output(formatLesson(lessonData), { json: false });
41916
42215
  }
41917
42216
  } catch (err) {
42217
+ if (isCourseArchivedError(err)) {
42218
+ const friendly = getErrors().courseArchived;
42219
+ if (opts.json) jsonError("course_archived", { message: friendly });
42220
+ process.stderr.write(friendly);
42221
+ process.exit(1);
42222
+ }
41918
42223
  if (err instanceof CliApiError) {
41919
42224
  if (err.status === 403 && err.message === "CHANNEL_MISMATCH") {
41920
42225
  process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
@@ -41991,13 +42296,18 @@ var init_lesson = __esm({
41991
42296
  "use strict";
41992
42297
  init_src();
41993
42298
  init_lessons2();
42299
+ init_courses();
41994
42300
  init_http();
41995
42301
  init_guards();
41996
42302
  init_course_state();
41997
42303
  init_formatter();
41998
42304
  init_resolve();
42305
+ init_errors();
41999
42306
  logger12 = createLogger("cli:lesson");
42000
- lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
42307
+ lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").option(
42308
+ "--read <lessonId>",
42309
+ "Read a specific lesson by id (works on archived / read-only courses)"
42310
+ ).action(async (opts) => {
42001
42311
  try {
42002
42312
  const session = await requireSession();
42003
42313
  const activeCourse = await requireActiveCourse();
@@ -42005,10 +42315,21 @@ var init_lesson = __esm({
42005
42315
  if (driftWarning) process.stderr.write(driftWarning + "\n");
42006
42316
  const data = createHttpProvider(session.apiUrl, session.token);
42007
42317
  const deps = { data, logger: logger12 };
42008
- const lessonId = activeCourse.currentLessonId;
42318
+ let lessonId = opts.read ?? activeCourse.currentLessonId;
42009
42319
  if (!lessonId) {
42010
- if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
42011
- error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
42320
+ try {
42321
+ const prog = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
42322
+ lessonId = prog.currentLesson?.id || void 0;
42323
+ if (prog.isArchived) {
42324
+ process.stderr.write("\u2139 Curso arquivado \u2014 modo leitura.\n");
42325
+ }
42326
+ } catch {
42327
+ }
42328
+ }
42329
+ if (!lessonId) {
42330
+ if (opts.json)
42331
+ jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o encontrada para leitura" });
42332
+ error("Nenhuma li\xE7\xE3o encontrada para leitura. Rode: tostudy start ou tostudy next");
42012
42333
  }
42013
42334
  const content = await getContent({ lessonId, enrollmentId: activeCourse.enrollmentId }, deps);
42014
42335
  if (opts.json) {
@@ -42029,6 +42350,12 @@ var init_lesson = __esm({
42029
42350
  }
42030
42351
  }
42031
42352
  } catch (err) {
42353
+ if (isInsufficientCreditsError(err)) {
42354
+ const friendly = getErrors().insufficientCredits;
42355
+ if (opts.json) jsonError("insufficient_credits", { message: friendly });
42356
+ error(friendly);
42357
+ return;
42358
+ }
42032
42359
  const msg = err instanceof Error ? err.message : String(err);
42033
42360
  if (opts.json) jsonError(msg);
42034
42361
  error(msg);
@@ -42049,6 +42376,7 @@ var init_hint = __esm({
42049
42376
  init_guards();
42050
42377
  init_course_state();
42051
42378
  init_formatter();
42379
+ init_errors();
42052
42380
  logger13 = createLogger("cli:hint");
42053
42381
  hintCommand = new Command12("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
42054
42382
  try {
@@ -42068,6 +42396,18 @@ var init_hint = __esm({
42068
42396
  output(formatHint(hint), { json: false });
42069
42397
  }
42070
42398
  } catch (err) {
42399
+ if (isCourseArchivedError(err)) {
42400
+ const friendly = getErrors().courseArchived;
42401
+ if (opts.json) jsonError("course_archived", { message: friendly });
42402
+ error(friendly);
42403
+ return;
42404
+ }
42405
+ if (isInsufficientCreditsError(err)) {
42406
+ const friendly = getErrors().insufficientCredits;
42407
+ if (opts.json) jsonError("insufficient_credits", { message: friendly });
42408
+ error(friendly);
42409
+ return;
42410
+ }
42071
42411
  const msg = err instanceof Error ? err.message : String(err);
42072
42412
  if (opts.json) jsonError(msg);
42073
42413
  error(msg);
@@ -42404,6 +42744,7 @@ var init_validate = __esm({
42404
42744
  init_course_state();
42405
42745
  init_formatter();
42406
42746
  init_init_template();
42747
+ init_errors();
42407
42748
  logger14 = createLogger("cli:validate");
42408
42749
  validateCommand = new Command13("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) => {
42409
42750
  try {
@@ -42485,6 +42826,18 @@ var init_validate = __esm({
42485
42826
  }
42486
42827
  process.exit(result.passed ? 0 : 1);
42487
42828
  } catch (err) {
42829
+ if (isCourseArchivedError(err)) {
42830
+ const friendly = getErrors().courseArchived;
42831
+ if (opts.json) jsonError("course_archived", { message: friendly });
42832
+ error(friendly);
42833
+ return;
42834
+ }
42835
+ if (isInsufficientCreditsError(err)) {
42836
+ const friendly = getErrors().insufficientCredits;
42837
+ if (opts.json) jsonError("insufficient_credits", { message: friendly });
42838
+ error(friendly);
42839
+ return;
42840
+ }
42488
42841
  const msg = err instanceof Error ? err.message : String(err);
42489
42842
  if (opts.json) jsonError(msg);
42490
42843
  error(msg);
@@ -44286,6 +44639,7 @@ var init_theory = __esm({
44286
44639
  init_src();
44287
44640
  init_guards();
44288
44641
  init_formatter();
44642
+ init_errors();
44289
44643
  logger27 = createLogger("cli:theory");
44290
44644
  EXERCISE_LEVELS2 = ["L0", "L1", "L2", "L3", "L4"];
44291
44645
  theoryCommand = new Command28("theory").description("Request more theory on the current lesson (escape hatch)").option("--focus <text>", "Optional focus area in PT-BR (max 500 chars)").option("--level <L0|L1|L2|L3|L4>", "Override current exercise level (otherwise auto-detect)").option("--lesson <uuid>", "Override lesson ID (defaults to current workspace lesson)").option("--json", "Output structured JSON").action(async (opts) => {
@@ -44352,6 +44706,12 @@ var init_theory = __esm({
44352
44706
  output(formatMoreTheory(data), { json: false });
44353
44707
  }
44354
44708
  } catch (err) {
44709
+ if (isInsufficientCreditsError(err)) {
44710
+ const friendly = getErrors().insufficientCredits;
44711
+ if (opts.json) jsonError("insufficient_credits", { message: friendly });
44712
+ error(friendly);
44713
+ return;
44714
+ }
44355
44715
  const msg = err instanceof Error ? err.message : String(err);
44356
44716
  if (opts.json) jsonError("unexpected_error", { message: msg });
44357
44717
  error(msg);