@tostudy-ai/cli 0.16.11 → 0.16.12

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
@@ -1058,8 +1058,9 @@ async function cliApiFetch(url2, token, init) {
1058
1058
  if (!response.ok) {
1059
1059
  const code = typeof body.error === "string" && body.error.length > 0 ? body.error : void 0;
1060
1060
  const friendly = typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
1061
+ const unauthenticated = response.status === 401 ? "Sess\xE3o inv\xE1lida ou expirada. Rode: tostudy login" : void 0;
1061
1062
  throw new CliApiError(
1062
- friendly ?? code ?? `API error ${response.status}`,
1063
+ friendly ?? unauthenticated ?? code ?? `API error ${response.status}`,
1063
1064
  response.status,
1064
1065
  code
1065
1066
  );
@@ -2617,11 +2618,18 @@ var init_login = __esm({
2617
2618
  }
2618
2619
  if (opts.manual) {
2619
2620
  console.log(`
2620
- Acesse este endere\xE7o no seu browser:`);
2621
- console.log(` ${apiUrl}/api/cli/auth/authorize?port=${PORT}
2621
+ Login sem browser nesta m\xE1quina:`);
2622
+ console.log(` 1. Em um computador com browser, acesse:`);
2623
+ console.log(` ${apiUrl}/api/cli/auth/authorize`);
2624
+ console.log(
2625
+ ` 2. Ap\xF3s autorizar, o browser tenta abrir http://localhost:9876/callback?code=...`
2626
+ );
2627
+ console.log(
2628
+ ` e falha (n\xE3o h\xE1 CLI nesse computador). Copie o valor de "code" da barra de endere\xE7o.`
2629
+ );
2630
+ console.log(` 3. Em at\xE9 5 minutos, volte aqui e rode:`);
2631
+ console.log(` tostudy login --code <code>
2622
2632
  `);
2623
- console.log(" Ap\xF3s autorizar, o browser ser\xE1 redirecionado e o login ser\xE1 conclu\xEDdo.");
2624
- console.log(" Certifique-se de rodar este comando sem --manual para o fluxo autom\xE1tico.\n");
2625
2633
  return;
2626
2634
  }
2627
2635
  console.log("\n Abrindo browser para autentica\xE7\xE3o...\n");
@@ -3224,30 +3232,43 @@ var init_resolve = __esm({
3224
3232
  });
3225
3233
 
3226
3234
  // src/auth/guards.ts
3235
+ var guards_exports = {};
3236
+ __export(guards_exports, {
3237
+ requireActiveCourse: () => requireActiveCourse,
3238
+ requireSession: () => requireSession
3239
+ });
3227
3240
  async function refreshCliSession(session, configDir) {
3241
+ let response;
3228
3242
  try {
3229
- const response = await fetch(`${session.apiUrl}/api/cli/auth/refresh`, {
3243
+ response = await fetch(`${session.apiUrl}/api/cli/auth/refresh`, {
3230
3244
  method: "POST",
3231
3245
  headers: { "Content-Type": "application/json" },
3232
3246
  body: JSON.stringify({ refreshToken: session.refreshToken })
3233
3247
  });
3234
- if (!response.ok) {
3235
- await clearSessionArtifacts(configDir);
3236
- return null;
3237
- }
3238
- const data = await response.json();
3239
- const refreshed = {
3240
- ...session,
3241
- token: data.token,
3242
- refreshToken: data.refreshToken,
3243
- expiresAt: data.expiresAt
3244
- };
3245
- await saveSession(refreshed, configDir);
3246
- return refreshed;
3247
- } catch {
3248
+ } catch (err) {
3249
+ return { status: "unavailable", reason: err instanceof Error ? err.message : String(err) };
3250
+ }
3251
+ if (response.status === 401 || response.status === 403) {
3248
3252
  await clearSessionArtifacts(configDir);
3249
- return null;
3253
+ return { status: "rejected" };
3250
3254
  }
3255
+ if (!response.ok) {
3256
+ return { status: "unavailable", reason: `HTTP ${response.status}` };
3257
+ }
3258
+ let data;
3259
+ try {
3260
+ data = await response.json();
3261
+ } catch (err) {
3262
+ return { status: "unavailable", reason: err instanceof Error ? err.message : String(err) };
3263
+ }
3264
+ const refreshed = {
3265
+ ...session,
3266
+ token: data.token,
3267
+ refreshToken: data.refreshToken,
3268
+ expiresAt: data.expiresAt
3269
+ };
3270
+ await saveSession(refreshed, configDir);
3271
+ return { status: "ok", session: refreshed };
3251
3272
  }
3252
3273
  async function requireSession(configDir) {
3253
3274
  const session = await getSession(configDir);
@@ -3257,12 +3278,17 @@ async function requireSession(configDir) {
3257
3278
  }
3258
3279
  const expiresAt = new Date(session.expiresAt);
3259
3280
  if (expiresAt < /* @__PURE__ */ new Date()) {
3260
- const refreshed = await refreshCliSession(session, configDir);
3261
- if (!refreshed) {
3281
+ const outcome = await refreshCliSession(session, configDir);
3282
+ if (outcome.status === "ok") return outcome.session;
3283
+ if (outcome.status === "rejected") {
3262
3284
  process.stderr.write("Erro: Sess\xE3o expirada. Rode: tostudy login\n");
3263
3285
  process.exit(2);
3264
3286
  }
3265
- return refreshed;
3287
+ process.stderr.write(
3288
+ `Erro: N\xE3o foi poss\xEDvel renovar a sess\xE3o (${outcome.reason}). Sua sess\xE3o foi mantida \u2014 verifique a conex\xE3o e tente de novo.
3289
+ `
3290
+ );
3291
+ process.exit(4);
3266
3292
  }
3267
3293
  return session;
3268
3294
  }
@@ -3413,7 +3439,7 @@ var CLI_VERSION;
3413
3439
  var init_version = __esm({
3414
3440
  "src/version.ts"() {
3415
3441
  "use strict";
3416
- CLI_VERSION = true ? "0.16.11" : "0.7.1";
3442
+ CLI_VERSION = true ? "0.16.12" : "0.7.1";
3417
3443
  }
3418
3444
  });
3419
3445
 
@@ -4372,6 +4398,7 @@ var init_select = __esm({
4372
4398
  interactive: isInteractiveTerminal(opts.json)
4373
4399
  });
4374
4400
  let pipelineResult = null;
4401
+ let pipelineError = null;
4375
4402
  try {
4376
4403
  pipelineResult = await resolveAndGenerate(
4377
4404
  { courseId: detail.courseId, enrollmentId },
@@ -4382,9 +4409,8 @@ var init_select = __esm({
4382
4409
  }
4383
4410
  );
4384
4411
  } catch (err) {
4385
- logger8.warn("Failed to run instruction pipeline", {
4386
- error: err instanceof Error ? err.message : String(err)
4387
- });
4412
+ pipelineError = err instanceof Error ? err.message : String(err);
4413
+ logger8.warn("Failed to run instruction pipeline", { error: pipelineError });
4388
4414
  }
4389
4415
  const slugMatch = pipelineResult?.wroteFiles.find((f) => f.startsWith(".claude/commands/"))?.match(/tostudy-(.+)\.md$/);
4390
4416
  const courseSlug2 = slugMatch?.[1] ?? "";
@@ -4402,7 +4428,8 @@ var init_select = __esm({
4402
4428
  usedT0: pipelineResult?.usedT0 ?? false,
4403
4429
  usedT1: pipelineResult?.usedT1 ?? false,
4404
4430
  usedT2: pipelineResult?.usedT2 ?? false,
4405
- instruction: pipelineResult?.renderedInstruction ?? null
4431
+ instruction: pipelineResult?.renderedInstruction ?? null,
4432
+ pipelineError
4406
4433
  },
4407
4434
  { json: true }
4408
4435
  );
@@ -4426,7 +4453,8 @@ var init_select = __esm({
4426
4453
  ` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
4427
4454
  wsLine,
4428
4455
  "",
4429
- " Arquivos de contexto criados para seu assistente AI."
4456
+ pipelineError ? `\u26A0 Arquivos de contexto N\xC3O foram criados (${pipelineError}).
4457
+ Rode \`tostudy select\` de novo com a conex\xE3o ativa.` : " Arquivos de contexto criados para seu assistente AI."
4430
4458
  ];
4431
4459
  if (pipelineResult && !pipelineResult.usedT0) {
4432
4460
  lines.push(" (sem persona do creator \u2014 usando tutor neutro)");
@@ -20427,15 +20455,27 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
20427
20455
  progressData,
20428
20456
  learnerProfile
20429
20457
  );
20430
- await deps.saveRemoteEnrollmentOnboarding({
20431
- apiUrl: session.apiUrl,
20432
- token: session.token,
20433
- enrollmentId: activeCourse.enrollmentId,
20434
- learnerBrief: artifacts.learnerBrief,
20435
- learnerProfile,
20436
- source: "cli"
20437
- });
20438
20458
  await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
20459
+ try {
20460
+ await deps.saveRemoteEnrollmentOnboarding({
20461
+ apiUrl: session.apiUrl,
20462
+ token: session.token,
20463
+ enrollmentId: activeCourse.enrollmentId,
20464
+ learnerBrief: artifacts.learnerBrief,
20465
+ learnerProfile,
20466
+ source: "cli"
20467
+ });
20468
+ } catch (err) {
20469
+ const reason = err instanceof Error ? err.message : String(err);
20470
+ deps.logger.warn("Failed to save enrollment onboarding remotely", { error: reason });
20471
+ if (!flags.json) {
20472
+ process.stderr.write(
20473
+ `\u26A0 Suas respostas foram salvas neste computador, mas n\xE3o chegaram ao servidor (${reason}).
20474
+ Rode \`tostudy init\` de novo com a conex\xE3o ativa para sincronizar.
20475
+ `
20476
+ );
20477
+ }
20478
+ }
20439
20479
  try {
20440
20480
  const rootAgents = await resolveRootAgentsConsent({
20441
20481
  cwd: process.cwd(),
@@ -20493,7 +20533,13 @@ var init_init = __esm({
20493
20533
  init_pipeline_deps();
20494
20534
  logger17 = createLogger("cli:init");
20495
20535
  defaultDeps4 = {
20496
- getSession,
20536
+ // GH #1419 item 3 — `init` used the raw session, so an expired access
20537
+ // token met the POST as a 401. Refresh through the guard when one exists.
20538
+ getSession: async () => {
20539
+ if (!await getSession()) return null;
20540
+ const { requireSession: requireSession2 } = await Promise.resolve().then(() => (init_guards(), guards_exports));
20541
+ return requireSession2();
20542
+ },
20497
20543
  findWorkspaceState,
20498
20544
  listCourses,
20499
20545
  getProgress,
@@ -22957,8 +23003,17 @@ __export(cli_exports, {
22957
23003
  import { Command as Command35 } from "commander";
22958
23004
  function createProgram() {
22959
23005
  const program2 = new Command35();
22960
- program2.name("tostudy").description("ToStudy CLI \u2014 study courses from the terminal").version(CLI_VERSION).option("--verbose", "Enable debug output").option("--course <id>", "Override active course ID").option("--locale <code>", "Output locale (pt-BR | en-US); defaults to LANG env then pt-BR");
22961
- program2.addCommand(setupCommand);
23006
+ program2.name("tostudy").description("ToStudy CLI \u2014 study courses from the terminal").version(CLI_VERSION).option("--verbose", "Enable debug output").option("--course <id>", "Override active course ID").option("--locale <code>", "Output locale (pt-BR | en-US); defaults to LANG env then pt-BR").addHelpText(
23007
+ "before",
23008
+ [
23009
+ "",
23010
+ " Come\xE7ando: login \u2192 courses \u2192 select <n> \u2192 start",
23011
+ " Estudando: next \xB7 lesson \xB7 hint \xB7 validate \xB7 theory \xB7 progress",
23012
+ " Com d\xFAvida: tostudy menu (mostra onde voc\xEA est\xE1 e o que fazer)",
23013
+ ""
23014
+ ].join("\n")
23015
+ );
23016
+ program2.addCommand(setupCommand, { hidden: true });
22962
23017
  program2.addCommand(doctorCommand);
22963
23018
  program2.addCommand(initCommand);
22964
23019
  program2.addCommand(loginCommand);
@@ -23134,6 +23189,13 @@ if (!process.env["TOSTUDY_LOCALE"]) {
23134
23189
  var { createProgram: createProgram2, CLI_VERSION: CLI_VERSION2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
23135
23190
  var { checkForUpdates: checkForUpdates2 } = await Promise.resolve().then(() => (init_update_checker(), update_checker_exports));
23136
23191
  var { maybeAutoUpdate: maybeAutoUpdate2 } = await Promise.resolve().then(() => (init_auto_updater(), auto_updater_exports));
23192
+ process.on("unhandledRejection", (reason) => {
23193
+ const msg = reason instanceof Error ? reason.message : String(reason);
23194
+ process.stderr.write(`Erro: ${msg}
23195
+ Se o problema continuar, rode: tostudy doctor
23196
+ `);
23197
+ process.exit(1);
23198
+ });
23137
23199
  var program = createProgram2();
23138
23200
  program.parse();
23139
23201
  checkForUpdates2(CLI_VERSION2);