@tostudy-ai/cli 0.16.5 → 0.16.6

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.6" : "0.7.1";
3374
3387
  }
3375
3388
  });
3376
3389
 
@@ -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
  {