@tostudy-ai/cli 0.11.1 → 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
@@ -1310,10 +1310,19 @@ function formatProgress(data) {
1310
1310
  `Li\xE7\xF5es conclu\xEDdas: ${data.completedLessons}`,
1311
1311
  `Tempo restante estimado: ~${data.estimatedMinutesRemaining} min`,
1312
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",
1313
- "",
1314
- "\u2192 tostudy next para avan\xE7ar para a pr\xF3xima li\xE7\xE3o",
1315
- "\u2192 tostudy lesson para ver o conte\xFAdo da li\xE7\xE3o atual"
1313
+ ""
1316
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
+ }
1317
1326
  return lines.join("\n");
1318
1327
  }
1319
1328
  function formatLesson(data) {
@@ -2323,7 +2332,8 @@ var init_errors_pt_br = __esm({
2323
2332
  logoutSuccess: "\n Deslogado com sucesso.\n",
2324
2333
  workspaceCommandDescription: "Gerenciar workspace de estudo local",
2325
2334
  workspaceSetupDescription: "Criar estrutura do workspace para o curso ativo",
2326
- 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"
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"
2327
2337
  };
2328
2338
  }
2329
2339
  });
@@ -2341,7 +2351,8 @@ var init_errors_en_us = __esm({
2341
2351
  logoutSuccess: "\n Logged out successfully.\n",
2342
2352
  workspaceCommandDescription: "Manage local study workspace",
2343
2353
  workspaceSetupDescription: "Create the workspace structure for the active course",
2344
- 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"
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"
2345
2356
  };
2346
2357
  }
2347
2358
  });
@@ -2373,6 +2384,10 @@ function isInsufficientCreditsError(err) {
2373
2384
  const msg = err instanceof Error ? err.message : String(err);
2374
2385
  return msg.includes("INSUFFICIENT_CREDITS");
2375
2386
  }
2387
+ function isCourseArchivedError(err) {
2388
+ const msg = err instanceof Error ? err.message : String(err);
2389
+ return msg.includes("COURSE_ARCHIVED");
2390
+ }
2376
2391
  var BUNDLES, _cachedLocale;
2377
2392
  var init_errors = __esm({
2378
2393
  "src/errors/index.ts"() {
@@ -2569,7 +2584,7 @@ var CLI_VERSION;
2569
2584
  var init_version = __esm({
2570
2585
  "src/version.ts"() {
2571
2586
  "use strict";
2572
- CLI_VERSION = true ? "0.11.1" : "0.7.1";
2587
+ CLI_VERSION = true ? "0.11.2" : "0.7.1";
2573
2588
  }
2574
2589
  });
2575
2590
 
@@ -37113,7 +37128,7 @@ var init_credit_movement_audit_log = __esm({
37113
37128
  });
37114
37129
 
37115
37130
  // ../../packages/database/src/schema/marketing.ts
37116
- var testimonialStatusEnum, marketingContentTypeEnum, creatorTestimonials2, marketingContent2, newsletterSubscribers, contactSubmissions;
37131
+ var testimonialStatusEnum, testimonialPlacementEnum, marketingContentTypeEnum, creatorTestimonials2, marketingContent2, newsletterSubscribers, contactSubmissions;
37117
37132
  var init_marketing = __esm({
37118
37133
  "../../packages/database/src/schema/marketing.ts"() {
37119
37134
  "use strict";
@@ -37124,6 +37139,11 @@ var init_marketing = __esm({
37124
37139
  "published",
37125
37140
  "archived"
37126
37141
  ]);
37142
+ testimonialPlacementEnum = pgEnum("testimonial_placement", [
37143
+ "homepage",
37144
+ "creators",
37145
+ "both"
37146
+ ]);
37127
37147
  marketingContentTypeEnum = pgEnum("marketing_content_type", [
37128
37148
  "text",
37129
37149
  // Simple text content
@@ -37158,6 +37178,7 @@ var init_marketing = __esm({
37158
37178
  featured: boolean("featured").default(false).notNull(),
37159
37179
  // Show in carousel
37160
37180
  displayOrder: integer("display_order").default(0).notNull(),
37181
+ placement: testimonialPlacementEnum("placement").default("creators").notNull(),
37161
37182
  // Localization support (null = applies to all locales)
37162
37183
  locale: varchar("locale", { length: 10 }),
37163
37184
  // Metadata
@@ -37170,7 +37191,8 @@ var init_marketing = __esm({
37170
37191
  statusIdx: index("creator_testimonials_status_idx").on(table.status),
37171
37192
  featuredIdx: index("creator_testimonials_featured_idx").on(table.featured),
37172
37193
  displayOrderIdx: index("creator_testimonials_display_order_idx").on(table.displayOrder),
37173
- 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)
37174
37196
  })
37175
37197
  );
37176
37198
  marketingContent2 = pgTable(
@@ -41106,44 +41128,39 @@ var init_course_allowlist = __esm({
41106
41128
  }
41107
41129
  });
41108
41130
 
41109
- // ../../node_modules/@parisgroup-ai/pg-backend/dist/idempotency-schema.mjs
41110
- var pgBackendIdempotencyKeys;
41111
- var init_idempotency_schema = __esm({
41112
- "../../node_modules/@parisgroup-ai/pg-backend/dist/idempotency-schema.mjs"() {
41113
- init_drizzle_orm();
41114
- init_pg_core();
41115
- pgBackendIdempotencyKeys = pgTable("pg_backend_idempotency_keys", {
41116
- reservationId: uuid("reservation_id").primaryKey().defaultRandom(),
41117
- idempotencyKey: text("idempotency_key").notNull(),
41118
- resource: text("resource").notNull(),
41119
- operation: text("operation").notNull(),
41120
- fingerprint: text("fingerprint").notNull(),
41121
- scopeKey: text("scope_key"),
41122
- status: text("status").notNull(),
41123
- response: jsonb("response"),
41124
- reservedAt: timestamp("reserved_at", { withTimezone: true }).notNull().defaultNow(),
41125
- committedAt: timestamp("committed_at", { withTimezone: true }),
41126
- expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
41127
- }, (t) => ({
41128
- uniqueKey: uniqueIndex("pg_backend_idempotency_unique").on(t.idempotencyKey, t.resource, t.operation, sql`COALESCE(${t.scopeKey}, '')`),
41129
- expiresIdx: index("pg_backend_idempotency_expires").on(t.expiresAt),
41130
- pendingIdx: index("pg_backend_idempotency_pending").on(t.status, t.reservedAt)
41131
- }));
41132
- }
41133
- });
41134
-
41135
- // ../../node_modules/@parisgroup-ai/pg-backend/dist/index.mjs
41136
- var init_dist2 = __esm({
41137
- "../../node_modules/@parisgroup-ai/pg-backend/dist/index.mjs"() {
41138
- init_idempotency_schema();
41139
- }
41140
- });
41141
-
41142
41131
  // ../../packages/database/src/schema/idempotency.ts
41132
+ var pgBackendIdempotencyKeys;
41143
41133
  var init_idempotency = __esm({
41144
41134
  "../../packages/database/src/schema/idempotency.ts"() {
41145
41135
  "use strict";
41146
- init_dist2();
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
+ );
41147
41164
  }
41148
41165
  });
41149
41166
 
@@ -41564,6 +41581,7 @@ __export(schema_exports, {
41564
41581
  syncLogsRelations: () => syncLogsRelations2,
41565
41582
  systemConfigs: () => systemConfigs,
41566
41583
  systemConfigsRelations: () => systemConfigsRelations,
41584
+ testimonialPlacementEnum: () => testimonialPlacementEnum,
41567
41585
  testimonialStatusEnum: () => testimonialStatusEnum,
41568
41586
  threadStatusEnum: () => threadStatusEnum,
41569
41587
  toneEnum: () => toneEnum,
@@ -42061,9 +42079,8 @@ async function runStart(opts, deps = defaultDeps3) {
42061
42079
  process.stderr.write("Acesse tostudy.ai para estudar este curso pelo ChatStudy.\n");
42062
42080
  process.exit(1);
42063
42081
  }
42064
- if (err instanceof CliApiError && err.status === 403 && err.message === "COURSE_ARCHIVED") {
42065
- process.stderr.write("Este curso foi arquivado e est\xE1 dispon\xEDvel somente para leitura.\n");
42066
- process.stderr.write("Use `tostudy lesson` para revisar o conte\xFAdo j\xE1 estudado.\n");
42082
+ if (isCourseArchivedError(err)) {
42083
+ deps.stderrWrite(getErrors().courseArchived);
42067
42084
  process.exit(1);
42068
42085
  }
42069
42086
  const msg = err instanceof Error ? err.message : String(err);
@@ -42082,6 +42099,7 @@ var init_start = __esm({
42082
42099
  init_workspace_state();
42083
42100
  init_status();
42084
42101
  init_formatter();
42102
+ init_errors();
42085
42103
  logger9 = createLogger("cli:start");
42086
42104
  defaultDeps3 = {
42087
42105
  requireSession,
@@ -42117,6 +42135,7 @@ var init_start_next = __esm({
42117
42135
  init_course_state();
42118
42136
  init_workspace_state();
42119
42137
  init_formatter();
42138
+ init_errors();
42120
42139
  logger10 = createLogger("cli:start-next");
42121
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) => {
42122
42141
  try {
@@ -42144,9 +42163,8 @@ var init_start_next = __esm({
42144
42163
  process.stderr.write("\u2192 tostudy courses para ver outros cursos\n");
42145
42164
  process.exit(0);
42146
42165
  }
42147
- if (err instanceof CliApiError && err.status === 403 && err.message === "COURSE_ARCHIVED") {
42148
- process.stderr.write("Este curso foi arquivado e est\xE1 dispon\xEDvel somente para leitura.\n");
42149
- process.stderr.write("Use `tostudy lesson` para revisar o conte\xFAdo j\xE1 estudado.\n");
42166
+ if (isCourseArchivedError(err)) {
42167
+ process.stderr.write(getErrors().courseArchived);
42150
42168
  process.exit(1);
42151
42169
  }
42152
42170
  const msg = err instanceof Error ? err.message : String(err);
@@ -42170,6 +42188,7 @@ var init_next = __esm({
42170
42188
  init_course_state();
42171
42189
  init_workspace_state();
42172
42190
  init_formatter();
42191
+ init_errors();
42173
42192
  logger11 = createLogger("cli:next");
42174
42193
  nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
42175
42194
  try {
@@ -42195,6 +42214,12 @@ var init_next = __esm({
42195
42214
  output(formatLesson(lessonData), { json: false });
42196
42215
  }
42197
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
+ }
42198
42223
  if (err instanceof CliApiError) {
42199
42224
  if (err.status === 403 && err.message === "CHANNEL_MISMATCH") {
42200
42225
  process.stderr.write("Este curso n\xE3o est\xE1 dispon\xEDvel via CLI.\n");
@@ -42271,6 +42296,7 @@ var init_lesson = __esm({
42271
42296
  "use strict";
42272
42297
  init_src();
42273
42298
  init_lessons2();
42299
+ init_courses();
42274
42300
  init_http();
42275
42301
  init_guards();
42276
42302
  init_course_state();
@@ -42278,7 +42304,10 @@ var init_lesson = __esm({
42278
42304
  init_resolve();
42279
42305
  init_errors();
42280
42306
  logger12 = createLogger("cli:lesson");
42281
- 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) => {
42282
42311
  try {
42283
42312
  const session = await requireSession();
42284
42313
  const activeCourse = await requireActiveCourse();
@@ -42286,10 +42315,21 @@ var init_lesson = __esm({
42286
42315
  if (driftWarning) process.stderr.write(driftWarning + "\n");
42287
42316
  const data = createHttpProvider(session.apiUrl, session.token);
42288
42317
  const deps = { data, logger: logger12 };
42289
- const lessonId = activeCourse.currentLessonId;
42318
+ let lessonId = opts.read ?? activeCourse.currentLessonId;
42290
42319
  if (!lessonId) {
42291
- if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
42292
- 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");
42293
42333
  }
42294
42334
  const content = await getContent({ lessonId, enrollmentId: activeCourse.enrollmentId }, deps);
42295
42335
  if (opts.json) {
@@ -42356,6 +42396,12 @@ var init_hint = __esm({
42356
42396
  output(formatHint(hint), { json: false });
42357
42397
  }
42358
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
+ }
42359
42405
  if (isInsufficientCreditsError(err)) {
42360
42406
  const friendly = getErrors().insufficientCredits;
42361
42407
  if (opts.json) jsonError("insufficient_credits", { message: friendly });
@@ -42780,6 +42826,12 @@ var init_validate = __esm({
42780
42826
  }
42781
42827
  process.exit(result.passed ? 0 : 1);
42782
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
+ }
42783
42835
  if (isInsufficientCreditsError(err)) {
42784
42836
  const friendly = getErrors().insufficientCredits;
42785
42837
  if (opts.json) jsonError("insufficient_credits", { message: friendly });