@tostudy-ai/cli 0.16.5 → 0.16.7

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
@@ -1366,7 +1366,7 @@ async function loadStoredSessionSecrets(configDir) {
1366
1366
  const account = getStoreKey(configDir);
1367
1367
  if (process.platform === "darwin") {
1368
1368
  try {
1369
- const { stdout } = await execFileAsync("security", [
1369
+ const { stdout: stdout2 } = await execFileAsync("security", [
1370
1370
  "find-generic-password",
1371
1371
  "-s",
1372
1372
  "tostudy-cli-session",
@@ -1374,21 +1374,21 @@ async function loadStoredSessionSecrets(configDir) {
1374
1374
  account,
1375
1375
  "-w"
1376
1376
  ]);
1377
- return JSON.parse(stdout.trim());
1377
+ return JSON.parse(stdout2.trim());
1378
1378
  } catch {
1379
1379
  return null;
1380
1380
  }
1381
1381
  }
1382
1382
  if (process.platform === "linux" && await hasCommand("secret-tool")) {
1383
1383
  try {
1384
- const { stdout } = await execFileAsync("secret-tool", [
1384
+ const { stdout: stdout2 } = await execFileAsync("secret-tool", [
1385
1385
  "lookup",
1386
1386
  "service",
1387
1387
  "tostudy-cli-session",
1388
1388
  "account",
1389
1389
  account
1390
1390
  ]);
1391
- return JSON.parse(stdout.trim());
1391
+ return JSON.parse(stdout2.trim());
1392
1392
  } catch {
1393
1393
  return null;
1394
1394
  }
@@ -2218,6 +2218,7 @@ var runtime_registry_exports = {};
2218
2218
  __export(runtime_registry_exports, {
2219
2219
  RUNTIMES: () => RUNTIMES,
2220
2220
  detectedSlashHints: () => detectedSlashHints,
2221
+ needsRootAgentsConsent: () => needsRootAgentsConsent,
2221
2222
  upsertRootAgentsBlock: () => upsertRootAgentsBlock
2222
2223
  });
2223
2224
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -2256,6 +2257,15 @@ ${content}`;
2256
2257
  function detectedSlashHints(slug, home, cwd) {
2257
2258
  return RUNTIMES.filter((r) => r.slashHint && r.detect(home, cwd)).map((r) => r.slashHint(slug));
2258
2259
  }
2260
+ function needsRootAgentsConsent(cwd) {
2261
+ const filePath = join2(cwd, "AGENTS.md");
2262
+ if (!existsSync2(filePath)) return false;
2263
+ try {
2264
+ return !TOSTUDY_BLOCK_PATTERN.test(readFileSync(filePath, "utf-8"));
2265
+ } catch {
2266
+ return false;
2267
+ }
2268
+ }
2259
2269
  function upsertRootAgentsBlock(cwd, ctx) {
2260
2270
  const slug = slugifyTitle(ctx.courseTitle);
2261
2271
  const block = [
@@ -2434,9 +2444,10 @@ ${content}`;
2434
2444
  name: "Generic (AGENTS.md)",
2435
2445
  // Windsurf is the remaining consumer of the generic universal install.
2436
2446
  detect: (home) => existsSync2(join2(home, ".codeium", "windsurf")),
2437
- writePerCourse({ cwd, slug, ctx, content }) {
2447
+ writePerCourse({ cwd, slug, ctx, content, rootAgentsConsent }) {
2438
2448
  writeFile2(join2(cwd, ".tostudy", "courses", slug), "AGENTS.md", content);
2439
2449
  const written = [`.tostudy/courses/${slug}/AGENTS.md`];
2450
+ if (needsRootAgentsConsent(cwd) && rootAgentsConsent !== true) return written;
2440
2451
  const rootAgents = upsertRootAgentsBlock(cwd, ctx);
2441
2452
  if (rootAgents) written.push(rootAgents);
2442
2453
  return written;
@@ -2457,6 +2468,7 @@ ${content}`;
2457
2468
  var instruction_files_exports = {};
2458
2469
  __export(instruction_files_exports, {
2459
2470
  installUniversalCommand: () => installUniversalCommand,
2471
+ needsRootAgentsConsent: () => needsRootAgentsConsent,
2460
2472
  slugify: () => slugify,
2461
2473
  upsertRootAgentsBlock: () => upsertRootAgentsBlock,
2462
2474
  writeInstructionFiles: () => writeInstructionFiles
@@ -2466,8 +2478,9 @@ function writeInstructionFiles(cwd, ctx, content, opts = {}) {
2466
2478
  const home = opts.home ?? homedir();
2467
2479
  const force = opts.allRuntimes ?? false;
2468
2480
  const slug = slugify(ctx.courseTitle);
2481
+ const rootAgentsConsent = opts.rootAgentsConsent ?? false;
2469
2482
  const written = RUNTIMES.flatMap(
2470
- (r) => r.writePerCourse?.({ cwd, home, slug, ctx, content, force }) ?? []
2483
+ (r) => r.writePerCourse?.({ cwd, home, slug, ctx, content, force, rootAgentsConsent }) ?? []
2471
2484
  );
2472
2485
  logger3.info("Generated instruction files (v3)", { slug, files: written });
2473
2486
  return written;
@@ -3370,7 +3383,7 @@ var CLI_VERSION;
3370
3383
  var init_version = __esm({
3371
3384
  "src/version.ts"() {
3372
3385
  "use strict";
3373
- CLI_VERSION = true ? "0.16.5" : "0.7.1";
3386
+ CLI_VERSION = true ? "0.16.7" : "0.7.1";
3374
3387
  }
3375
3388
  });
3376
3389
 
@@ -3840,11 +3853,11 @@ import { stdin as processStdin, stdout as processStdout } from "node:process";
3840
3853
  function composeBriefFromAnswers(answers) {
3841
3854
  const levelLabel = {
3842
3855
  beginner: "iniciante",
3843
- intermediate: "intermediario",
3844
- advanced: "avancado"
3856
+ intermediate: "intermedi\xE1rio",
3857
+ advanced: "avan\xE7ado"
3845
3858
  }[answers.yourLevel];
3846
3859
  const paragraphOne = [answers.whoYouAre, answers.whereYouWork, answers.whatYouDo].map((s) => s.trim()).filter(Boolean).join(". ");
3847
- const paragraphTwo = `Meu nivel neste assunto e ${levelLabel}. ${answers.yourGoals.trim()}`;
3860
+ const paragraphTwo = `Meu n\xEDvel neste assunto \xE9 ${levelLabel}. ${answers.yourGoals.trim()}`;
3848
3861
  const paragraphThree = `Contexto real do meu dia a dia: ${answers.realContext.trim()}`;
3849
3862
  const text = [paragraphOne, paragraphTwo, paragraphThree].map((p) => p.replace(/\.\.+/g, ".")).join("\n\n");
3850
3863
  return text.slice(0, 5e3);
@@ -3857,7 +3870,7 @@ async function askNonEmpty(question, deps) {
3857
3870
  }
3858
3871
  async function askLevel(deps) {
3859
3872
  while (true) {
3860
- const raw = (await deps.ask(" Seu nivel geral neste assunto (beginner/intermediate/advanced): ")).trim().toLowerCase();
3873
+ const raw = (await deps.ask(" Seu n\xEDvel geral neste assunto (beginner/intermediate/advanced): ")).trim().toLowerCase();
3861
3874
  if (raw === "beginner" || raw === "intermediate" || raw === "advanced") {
3862
3875
  return raw;
3863
3876
  }
@@ -3867,15 +3880,15 @@ async function collectBootstrapAnswersWithDeps(input2, deps) {
3867
3880
  deps.write(
3868
3881
  [
3869
3882
  "",
3870
- `Ola ${input2.userName}, vamos criar seu brief base (1-2 minutos).`,
3871
- "Ele ajuda o tutor a te conhecer para adaptar o ensino a voce.",
3872
- "Voce pode editar depois em https://tostudy.ai/student/settings/learner-brief.",
3883
+ `Ol\xE1 ${input2.userName}, vamos criar seu brief base (1-2 minutos).`,
3884
+ "Ele ajuda o tutor a te conhecer para adaptar o ensino a voc\xEA.",
3885
+ "Voc\xEA pode editar depois em https://tostudy.ai/student/settings/learner-brief.",
3873
3886
  ""
3874
3887
  ].join("\n")
3875
3888
  );
3876
- const whoYouAre = await askNonEmpty(" Quem voce e (profissao/cargo/area): ", deps);
3889
+ const whoYouAre = await askNonEmpty(" Quem voc\xEA \xE9 (profiss\xE3o/cargo/\xE1rea): ", deps);
3877
3890
  const whereYouWork = await askNonEmpty(" Onde trabalha (empresa/setor): ", deps);
3878
- const whatYouDo = await askNonEmpty(" O que voce faz (responsabilidades/projetos): ", deps);
3891
+ const whatYouDo = await askNonEmpty(" O que voc\xEA faz (responsabilidades/projetos): ", deps);
3879
3892
  const yourLevel = await askLevel(deps);
3880
3893
  const yourGoals = await askNonEmpty(" Seu objetivo de aprendizado: ", deps);
3881
3894
  const realContext = await askNonEmpty(
@@ -4204,7 +4217,7 @@ async function writeFilesAdapter(cwd, courseMeta, content, opts = {}) {
4204
4217
  courseDescription: courseMeta.courseDescription
4205
4218
  },
4206
4219
  content,
4207
- { allRuntimes: opts.allRuntimes }
4220
+ { allRuntimes: opts.allRuntimes, rootAgentsConsent: opts.rootAgentsConsent }
4208
4221
  );
4209
4222
  }
4210
4223
  function buildPipelineDeps(opts = {}) {
@@ -4227,6 +4240,43 @@ var init_pipeline_deps = __esm({
4227
4240
  }
4228
4241
  });
4229
4242
 
4243
+ // src/commands/_shared/root-agents-consent.ts
4244
+ import readline2 from "node:readline/promises";
4245
+ import { stdin, stdout } from "node:process";
4246
+ function rootAgentsSkippedHint() {
4247
+ return " AGENTS.md desta pasta n\xE3o foi alterado \u2014 rode `tostudy sync --root-agents` para adicionar o bloco do ToStudy.";
4248
+ }
4249
+ async function askViaReadline(question) {
4250
+ const rl = readline2.createInterface({ input: stdin, output: stdout });
4251
+ try {
4252
+ return await rl.question(question);
4253
+ } finally {
4254
+ rl.close();
4255
+ }
4256
+ }
4257
+ async function resolveRootAgentsConsent(input2) {
4258
+ const needs = (input2.needsConsent ?? needsRootAgentsConsent)(input2.cwd);
4259
+ if (!needs) return { consent: true, asked: false };
4260
+ if (input2.flag === true) return { consent: true, asked: true };
4261
+ if (!input2.interactive) return { consent: false, asked: true };
4262
+ const answer = (await (input2.ask ?? askViaReadline)(ROOT_AGENTS_QUESTION)).trim().toLowerCase();
4263
+ return {
4264
+ consent: answer === "s" || answer === "sim" || answer === "y" || answer === "yes",
4265
+ asked: true
4266
+ };
4267
+ }
4268
+ function isInteractiveTerminal(json2) {
4269
+ return Boolean(stdin.isTTY && stdout.isTTY) && json2 !== true;
4270
+ }
4271
+ var ROOT_AGENTS_QUESTION;
4272
+ var init_root_agents_consent = __esm({
4273
+ "src/commands/_shared/root-agents-consent.ts"() {
4274
+ "use strict";
4275
+ init_runtime_registry();
4276
+ ROOT_AGENTS_QUESTION = "Esta pasta j\xE1 tem um AGENTS.md. Adicionar o bloco do ToStudy nele? [s/N] ";
4277
+ }
4278
+ });
4279
+
4230
4280
  // src/commands/select.ts
4231
4281
  import { Command as Command6 } from "commander";
4232
4282
  import os7 from "node:os";
@@ -4246,8 +4296,12 @@ var init_select = __esm({
4246
4296
  init_status();
4247
4297
  init_instruction_pipeline();
4248
4298
  init_pipeline_deps();
4299
+ init_root_agents_consent();
4249
4300
  logger8 = createLogger("cli:select");
4250
- selectCommand = new Command6("select").description("Activate a course by ID or list index number").argument("<course>", "Course ID (UUID) or index number from `tostudy courses`").option("--json", "Output structured JSON").action(async (course, opts) => {
4301
+ selectCommand = new Command6("select").description("Activate a course by ID or list index number").argument("<course>", "Course ID (UUID) or index number from `tostudy courses`").option("--json", "Output structured JSON").option(
4302
+ "--root-agents",
4303
+ "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
4304
+ ).action(async (course, opts) => {
4251
4305
  try {
4252
4306
  const session = await requireSession();
4253
4307
  const data = createHttpProvider(session.apiUrl, session.token);
@@ -4282,12 +4336,20 @@ var init_select = __esm({
4282
4336
  enrollmentId
4283
4337
  };
4284
4338
  const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
4339
+ const rootAgents = await resolveRootAgentsConsent({
4340
+ cwd: process.cwd(),
4341
+ flag: opts.rootAgents,
4342
+ interactive: isInteractiveTerminal(opts.json)
4343
+ });
4285
4344
  let pipelineResult = null;
4286
4345
  try {
4287
4346
  pipelineResult = await resolveAndGenerate(
4288
4347
  { courseId: detail.courseId, enrollmentId },
4289
4348
  { forceRefresh: false, collectT1Interactive: false, collectT2Interactive: false },
4290
- { cwd: process.cwd(), deps: buildPipelineDeps() }
4349
+ {
4350
+ cwd: process.cwd(),
4351
+ deps: buildPipelineDeps({ rootAgentsConsent: rootAgents.consent })
4352
+ }
4291
4353
  );
4292
4354
  } catch (err) {
4293
4355
  logger8.warn("Failed to run instruction pipeline", {
@@ -4305,6 +4367,7 @@ var init_select = __esm({
4305
4367
  workspacePath: onboarding.workspacePath,
4306
4368
  workspaceSource: onboarding.workspaceSource,
4307
4369
  wroteFiles: pipelineResult?.wroteFiles ?? [],
4370
+ rootAgentsSkipped: rootAgents.asked && !rootAgents.consent,
4308
4371
  markerPath: pipelineResult?.markerPath ?? null,
4309
4372
  usedT0: pipelineResult?.usedT0 ?? false,
4310
4373
  usedT1: pipelineResult?.usedT1 ?? false,
@@ -4341,6 +4404,9 @@ var init_select = __esm({
4341
4404
  if (pipelineResult && !pipelineResult.usedT1) {
4342
4405
  lines.push(" (sem brief base \u2014 rode `tostudy init` para configurar)");
4343
4406
  }
4407
+ if (rootAgents.asked && !rootAgents.consent) {
4408
+ lines.push(rootAgentsSkippedHint());
4409
+ }
4344
4410
  lines.push("", "\u2192 Abra sua plataforma (claude, codex, cursor...)");
4345
4411
  const hints = courseSlug2 ? detectedSlashHints(courseSlug2, home, cwd) : [];
4346
4412
  if (hints.length > 0) {
@@ -20100,7 +20166,7 @@ var init_menu = __esm({
20100
20166
  });
20101
20167
 
20102
20168
  // src/onboarding/learner-context.ts
20103
- import readline2 from "node:readline/promises";
20169
+ import readline3 from "node:readline/promises";
20104
20170
  import { stdin as input, stdout as output2 } from "node:process";
20105
20171
  async function askNonEmpty2(question, deps, defaultValue) {
20106
20172
  while (true) {
@@ -20118,7 +20184,7 @@ async function askChoice(question, choices, deps, defaultChoice) {
20118
20184
  }
20119
20185
  }
20120
20186
  function createPromptDeps() {
20121
- const rl = readline2.createInterface({ input, output: output2 });
20187
+ const rl = readline3.createInterface({ input, output: output2 });
20122
20188
  return {
20123
20189
  ask: (question) => rl.question(question),
20124
20190
  write: (content) => output2.write(content),
@@ -20341,10 +20407,18 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
20341
20407
  });
20342
20408
  await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
20343
20409
  try {
20410
+ const rootAgents = await resolveRootAgentsConsent({
20411
+ cwd: process.cwd(),
20412
+ flag: flags.rootAgents,
20413
+ interactive: isInteractiveTerminal(flags.json)
20414
+ });
20344
20415
  const pipelineResult = await deps.resolveAndGenerate(
20345
20416
  { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
20346
20417
  { forceRefresh: false, collectT1Interactive: true, collectT2Interactive: false },
20347
- { cwd: process.cwd(), deps: deps.buildPipelineDeps() }
20418
+ {
20419
+ cwd: process.cwd(),
20420
+ deps: deps.buildPipelineDeps({ rootAgentsConsent: rootAgents.consent })
20421
+ }
20348
20422
  );
20349
20423
  deps.logger.info("Instruction files regenerated via pipeline", {
20350
20424
  wroteFiles: pipelineResult.wroteFiles,
@@ -20385,6 +20459,7 @@ var init_init = __esm({
20385
20459
  init_learner_context();
20386
20460
  init_api3();
20387
20461
  init_instruction_pipeline();
20462
+ init_root_agents_consent();
20388
20463
  init_pipeline_deps();
20389
20464
  logger17 = createLogger("cli:init");
20390
20465
  defaultDeps4 = {
@@ -20405,7 +20480,10 @@ var init_init = __esm({
20405
20480
  resolveAndGenerate,
20406
20481
  buildPipelineDeps
20407
20482
  };
20408
- initCommand = new Command16("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option("--json", "Output structured JSON").action(async (opts) => {
20483
+ initCommand = new Command16("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option(
20484
+ "--root-agents",
20485
+ "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
20486
+ ).option("--json", "Output structured JSON").action(async (opts) => {
20409
20487
  await runInit(defaultDeps4, opts);
20410
20488
  });
20411
20489
  }
@@ -21407,20 +21485,38 @@ var init_sync = __esm({
21407
21485
  init_guards();
21408
21486
  init_formatter();
21409
21487
  init_instruction_pipeline();
21488
+ init_root_agents_consent();
21410
21489
  init_pipeline_deps();
21411
21490
  logger22 = createLogger("cli:sync");
21412
21491
  syncCommand = new Command22("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").option(
21413
21492
  "--all-runtimes",
21414
21493
  "Write instruction files for every supported runtime, even undetected ones"
21494
+ ).option(
21495
+ "--root-agents",
21496
+ "Add the ToStudy block to an existing AGENTS.md in this folder without asking"
21415
21497
  ).action(async (opts) => {
21416
21498
  try {
21417
21499
  await requireSession();
21418
21500
  const activeCourse = await requireActiveCourse();
21501
+ const rootAgents = await resolveRootAgentsConsent({
21502
+ cwd: process.cwd(),
21503
+ flag: opts.rootAgents,
21504
+ interactive: isInteractiveTerminal(opts.json)
21505
+ });
21419
21506
  const pipelineResult = await resolveAndGenerate(
21420
21507
  { courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
21421
21508
  { forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
21422
- { cwd: process.cwd(), deps: buildPipelineDeps({ allRuntimes: opts.allRuntimes }) }
21509
+ {
21510
+ cwd: process.cwd(),
21511
+ deps: buildPipelineDeps({
21512
+ allRuntimes: opts.allRuntimes,
21513
+ rootAgentsConsent: rootAgents.consent
21514
+ })
21515
+ }
21423
21516
  );
21517
+ if (rootAgents.asked && !rootAgents.consent && !opts.json) {
21518
+ output(rootAgentsSkippedHint(), { json: false });
21519
+ }
21424
21520
  if (opts.json) {
21425
21521
  output(
21426
21522
  {
@@ -21490,7 +21586,7 @@ var init_brief = __esm({
21490
21586
  }
21491
21587
  if (!remote) {
21492
21588
  const lines2 = [
21493
- "Voce ainda nao tem um brief base preenchido.",
21589
+ "Voc\xEA ainda n\xE3o tem um brief base preenchido.",
21494
21590
  "Para criar: `tostudy brief-create` (terminal) ou `tostudy brief-open` (portal web)."
21495
21591
  ];
21496
21592
  output(lines2.join("\n"), { json: false });
@@ -21498,7 +21594,7 @@ var init_brief = __esm({
21498
21594
  }
21499
21595
  const lines = [`Brief base (atualizado em ${remote.updatedAt}):`, "", remote.text, ""];
21500
21596
  if (cached2 && cached2.brief && cached2.brief.updatedAt !== remote.updatedAt) {
21501
- lines.push("Aviso: cache local esta desatualizado. Rode `tostudy sync` para atualizar.");
21597
+ lines.push("Aviso: cache local est\xE1 desatualizado. Rode `tostudy sync` para atualizar.");
21502
21598
  }
21503
21599
  output(lines.join("\n"), { json: false });
21504
21600
  } catch (err) {
@@ -22976,10 +23072,12 @@ if (!process.env.LOG_LEVEL) {
22976
23072
  var MIN_NODE_MAJOR = 18;
22977
23073
  var nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
22978
23074
  if (Number.isFinite(nodeMajor) && nodeMajor < MIN_NODE_MAJOR) {
22979
- const pt = !(process.env["LANG"] ?? "").toLowerCase().startsWith("en");
23075
+ const localeFlagIdx = process.argv.indexOf("--locale");
23076
+ const localeFlag = localeFlagIdx !== -1 && localeFlagIdx + 1 < process.argv.length ? process.argv[localeFlagIdx + 1] : void 0;
23077
+ const pt = localeFlag === "pt-BR" || localeFlag === "en-US" ? localeFlag === "pt-BR" : !(process.env["LANG"] ?? "").toLowerCase().startsWith("en");
22980
23078
  process.stderr.write(
22981
23079
  pt ? `Erro: o ToStudy CLI precisa do Node.js ${MIN_NODE_MAJOR} ou superior.
22982
- Voce esta no Node.js ${process.versions.node}.
23080
+ Voc\xEA est\xE1 no Node.js ${process.versions.node}.
22983
23081
  Atualize em https://nodejs.org e rode o comando de novo.
22984
23082
  ` : `Error: the ToStudy CLI requires Node.js ${MIN_NODE_MAJOR} or newer.
22985
23083
  You are on Node.js ${process.versions.node}.