@tostudy-ai/cli 0.9.0 → 0.10.0

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.
Files changed (3) hide show
  1. package/dist/cli.js +1094 -949
  2. package/dist/cli.js.map +4 -4
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1305,7 +1305,7 @@ async function readWorkspaceMarker(workspacePath) {
1305
1305
  try {
1306
1306
  const raw = readFileSync(filePath, "utf-8");
1307
1307
  const parsed = JSON.parse(raw);
1308
- if (parsed.version !== CURRENT_VERSION) return null;
1308
+ if (!SUPPORTED_VERSIONS.has(parsed.version)) return null;
1309
1309
  if (typeof parsed.courseId !== "string" || typeof parsed.enrollmentId !== "string" || typeof parsed.slug !== "string" || typeof parsed.courseTitle !== "string" || typeof parsed.createdAt !== "string" || typeof parsed.updatedAt !== "string") {
1310
1310
  return null;
1311
1311
  }
@@ -1341,154 +1341,90 @@ async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
1341
1341
  writeFileSync2(filePath, JSON.stringify(marker, null, 2), { mode: 384 });
1342
1342
  return filePath;
1343
1343
  }
1344
- var CURRENT_VERSION;
1344
+ var CURRENT_VERSION, SUPPORTED_VERSIONS;
1345
1345
  var init_workspace_marker = __esm({
1346
1346
  "src/workspace/workspace-marker.ts"() {
1347
1347
  "use strict";
1348
1348
  CURRENT_VERSION = 1;
1349
+ SUPPORTED_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
1349
1350
  }
1350
1351
  });
1351
1352
 
1352
- // src/workspace/resolve.ts
1353
- var resolve_exports = {};
1354
- __export(resolve_exports, {
1355
- courseSlug: () => courseSlug,
1356
- findCwdWorkspaceUpwards: () => findCwdWorkspaceUpwards,
1357
- findExistingVault: () => findExistingVault,
1358
- isCwdWorkspace: () => isCwdWorkspace,
1359
- resolveActiveCourseWithOverride: () => resolveActiveCourseWithOverride,
1360
- resolveCwdWorkspacePath: () => resolveCwdWorkspacePath,
1361
- resolveEffectiveWorkspace: () => resolveEffectiveWorkspace,
1362
- resolveVaultPath: () => resolveVaultPath,
1363
- resolveWorkspace: () => resolveWorkspace
1364
- });
1365
- import fs2 from "node:fs/promises";
1366
- import path3 from "node:path";
1353
+ // src/workspace/workspace-state.ts
1354
+ var workspace_state_exports = {};
1355
+ __export(workspace_state_exports, {
1356
+ findWorkspaceState: () => findWorkspaceState,
1357
+ readWorkspaceState: () => readWorkspaceState,
1358
+ updateWorkspaceState: () => updateWorkspaceState
1359
+ });
1360
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
1367
1361
  import os3 from "node:os";
1368
- function courseSlug(title) {
1369
- return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
1370
- }
1371
- async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
1372
- const slug = courseSlug(courseTitle);
1373
- const candidate = path3.join(basePath, slug);
1374
- try {
1375
- await fs2.access(path3.join(candidate, ".ana-config.json"));
1376
- return { found: true, workspacePath: candidate, source: "default" };
1377
- } catch {
1378
- return { found: false, workspacePath: null };
1379
- }
1380
- }
1381
- async function isCwdWorkspace(cwd = process.cwd()) {
1382
- return await resolveCwdWorkspacePath(cwd) !== null;
1383
- }
1384
- async function resolveCwdWorkspacePath(cwd = process.cwd()) {
1385
- const tostudyDir = path3.join(cwd, ".tostudy");
1386
- try {
1387
- const stat = await fs2.stat(tostudyDir);
1388
- if (stat.isDirectory()) return tostudyDir;
1389
- } catch {
1390
- }
1362
+ import path3 from "node:path";
1363
+ async function readWorkspaceState(workspacePath) {
1364
+ const marker = await readWorkspaceMarker(workspacePath);
1365
+ if (!marker) return null;
1366
+ const filePath = resolveMarkerPath(workspacePath);
1391
1367
  try {
1392
- await fs2.access(path3.join(cwd, ".ana-config.json"));
1393
- return cwd;
1368
+ const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
1369
+ const state = {
1370
+ version: 2,
1371
+ courseId: marker.courseId,
1372
+ enrollmentId: marker.enrollmentId,
1373
+ slug: marker.slug,
1374
+ courseTitle: marker.courseTitle,
1375
+ createdAt: marker.createdAt,
1376
+ updatedAt: marker.updatedAt
1377
+ };
1378
+ if (typeof raw.currentLessonId === "string") state.currentLessonId = raw.currentLessonId;
1379
+ if (typeof raw.currentModuleId === "string") state.currentModuleId = raw.currentModuleId;
1380
+ if (Array.isArray(raw.courseTags)) state.courseTags = raw.courseTags;
1381
+ if (raw.courseLevel === "beginner" || raw.courseLevel === "intermediate" || raw.courseLevel === "advanced" || raw.courseLevel === null) {
1382
+ state.courseLevel = raw.courseLevel;
1383
+ }
1384
+ if (typeof raw.lastInitCourseId === "string") state.lastInitCourseId = raw.lastInitCourseId;
1385
+ return state;
1394
1386
  } catch {
1395
1387
  return null;
1396
1388
  }
1397
1389
  }
1398
- async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os3.homedir()) {
1390
+ async function findWorkspaceState(startCwd = process.cwd(), homeDir = os3.homedir()) {
1399
1391
  const home = path3.resolve(homeDir);
1400
1392
  let current = path3.resolve(startCwd);
1401
1393
  while (true) {
1402
1394
  if (current === home) return null;
1403
- const direct = await resolveCwdWorkspacePath(current);
1404
- if (direct) return direct;
1395
+ const state = await readWorkspaceState(current);
1396
+ if (state) return { state, workspacePath: current };
1405
1397
  const parent = path3.dirname(current);
1406
1398
  if (parent === current) return null;
1407
1399
  current = parent;
1408
1400
  }
1409
1401
  }
1410
- function resolveVaultPath(workspacePath, slug) {
1411
- const base = path3.basename(workspacePath) === ".tostudy" ? path3.dirname(workspacePath) : workspacePath;
1412
- return path3.join(base, `vault-${slug}`);
1413
- }
1414
- async function findExistingVault(workspacePath, slug) {
1415
- const slugged = resolveVaultPath(workspacePath, slug);
1416
- try {
1417
- await fs2.access(path3.join(slugged, ".ana-vault.json"));
1418
- return slugged;
1419
- } catch {
1420
- }
1421
- const legacy = path3.join(workspacePath, "vault");
1422
- try {
1423
- await fs2.access(path3.join(legacy, ".ana-vault.json"));
1424
- return legacy;
1425
- } catch {
1426
- return null;
1427
- }
1428
- }
1429
- async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE, homeDir = os3.homedir()) {
1430
- const cwdWorkspace = await findCwdWorkspaceUpwards(cwd, homeDir);
1431
- if (cwdWorkspace) {
1432
- return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
1433
- }
1434
- if (storedPath) {
1435
- try {
1436
- await fs2.access(path3.join(storedPath, ".ana-config.json"));
1437
- return { found: true, workspacePath: storedPath, source: "stored" };
1438
- } catch {
1439
- }
1402
+ async function updateWorkspaceState(workspacePath, update) {
1403
+ const existing = await readWorkspaceState(workspacePath);
1404
+ if (!existing) {
1405
+ throw new Error(`No workspace marker found at ${workspacePath}. Run 'tostudy select' first.`);
1440
1406
  }
1441
- const result = await resolveWorkspace(courseTitle, defaultBasePath);
1442
- if (result.found) {
1443
- return { ...result, source: "default" };
1444
- }
1445
- return { found: false, workspacePath: null };
1446
- }
1447
- async function findWorkspaceMarkerUpwards(startCwd, homeDir) {
1448
- const home = path3.resolve(homeDir);
1449
- let current = path3.resolve(startCwd);
1450
- while (true) {
1451
- if (current === home) return null;
1452
- const marker = await readWorkspaceMarker(current);
1453
- if (marker) return marker;
1454
- const parent = path3.dirname(current);
1455
- if (parent === current) return null;
1456
- current = parent;
1457
- }
1458
- }
1459
- async function resolveActiveCourseWithOverride(options = {}) {
1460
- const cwd = options.cwd ?? process.cwd();
1461
- const homeDir = options.homeDir ?? os3.homedir();
1462
- const marker = await findWorkspaceMarkerUpwards(cwd, homeDir);
1463
- if (marker) {
1464
- return {
1465
- courseId: marker.courseId,
1466
- enrollmentId: marker.enrollmentId,
1467
- courseTitle: marker.courseTitle,
1468
- source: "cwd-marker"
1469
- };
1470
- }
1471
- const global = await getActiveCourse(options.configDir);
1472
- if (!global) return null;
1473
- return {
1474
- courseId: global.courseId,
1475
- enrollmentId: global.enrollmentId,
1476
- courseTitle: global.courseTitle,
1477
- source: "global"
1407
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1408
+ const merged = {
1409
+ ...existing,
1410
+ ...update,
1411
+ version: 2,
1412
+ createdAt: existing.createdAt,
1413
+ updatedAt: now
1478
1414
  };
1415
+ const filePath = resolveMarkerPath(workspacePath);
1416
+ writeFileSync3(filePath, JSON.stringify(merged, null, 2), { mode: 384 });
1417
+ return merged;
1479
1418
  }
1480
- var DEFAULT_BASE;
1481
- var init_resolve = __esm({
1482
- "src/workspace/resolve.ts"() {
1419
+ var init_workspace_state = __esm({
1420
+ "src/workspace/workspace-state.ts"() {
1483
1421
  "use strict";
1484
1422
  init_workspace_marker();
1485
- init_session();
1486
- DEFAULT_BASE = path3.join(os3.homedir(), "study");
1487
1423
  }
1488
1424
  });
1489
1425
 
1490
1426
  // src/auth/session.ts
1491
- import fs3 from "node:fs";
1427
+ import fs2 from "node:fs";
1492
1428
  import path4 from "node:path";
1493
1429
  import os4 from "node:os";
1494
1430
  function getConfigDir(override) {
@@ -1503,13 +1439,13 @@ function getCourseOnboardingPath(configDir) {
1503
1439
  }
1504
1440
  function readCourseOnboardingState(configDir) {
1505
1441
  const onboardingPath = getCourseOnboardingPath(configDir);
1506
- if (!fs3.existsSync(onboardingPath)) return {};
1507
- return JSON.parse(fs3.readFileSync(onboardingPath, "utf-8"));
1442
+ if (!fs2.existsSync(onboardingPath)) return {};
1443
+ return JSON.parse(fs2.readFileSync(onboardingPath, "utf-8"));
1508
1444
  }
1509
1445
  function writeCourseOnboardingState(state, configDir) {
1510
1446
  const dir = getConfigDir(configDir);
1511
- fs3.mkdirSync(dir, { recursive: true });
1512
- fs3.writeFileSync(getCourseOnboardingPath(configDir), JSON.stringify(state, null, 2), {
1447
+ fs2.mkdirSync(dir, { recursive: true });
1448
+ fs2.writeFileSync(getCourseOnboardingPath(configDir), JSON.stringify(state, null, 2), {
1513
1449
  mode: 384
1514
1450
  });
1515
1451
  }
@@ -1545,23 +1481,23 @@ function getUserProfilePath(configDir) {
1545
1481
  }
1546
1482
  async function getUserProfile(configDir) {
1547
1483
  const profilePath = getUserProfilePath(configDir);
1548
- if (!fs3.existsSync(profilePath)) return null;
1484
+ if (!fs2.existsSync(profilePath)) return null;
1549
1485
  try {
1550
- return JSON.parse(fs3.readFileSync(profilePath, "utf-8"));
1486
+ return JSON.parse(fs2.readFileSync(profilePath, "utf-8"));
1551
1487
  } catch {
1552
1488
  return null;
1553
1489
  }
1554
1490
  }
1555
1491
  async function saveUserProfile(profile, configDir) {
1556
1492
  const dir = getConfigDir(configDir);
1557
- fs3.mkdirSync(dir, { recursive: true });
1558
- fs3.writeFileSync(getUserProfilePath(configDir), JSON.stringify(profile, null, 2), {
1493
+ fs2.mkdirSync(dir, { recursive: true });
1494
+ fs2.writeFileSync(getUserProfilePath(configDir), JSON.stringify(profile, null, 2), {
1559
1495
  mode: 384
1560
1496
  });
1561
1497
  }
1562
1498
  async function saveSession(session, configDir) {
1563
1499
  const dir = getConfigDir(configDir);
1564
- fs3.mkdirSync(dir, { recursive: true });
1500
+ fs2.mkdirSync(dir, { recursive: true });
1565
1501
  await saveSessionSecrets(
1566
1502
  {
1567
1503
  accessToken: session.token,
@@ -1576,15 +1512,15 @@ async function saveSession(session, configDir) {
1576
1512
  apiUrl: session.apiUrl,
1577
1513
  sessionId: session.sessionId
1578
1514
  };
1579
- fs3.writeFileSync(path4.join(dir, "config.json"), JSON.stringify(stored, null, 2), {
1515
+ fs2.writeFileSync(path4.join(dir, "config.json"), JSON.stringify(stored, null, 2), {
1580
1516
  mode: 384
1581
1517
  });
1582
1518
  }
1583
1519
  async function getSession(configDir) {
1584
1520
  const dir = getConfigDir(configDir);
1585
1521
  const p = path4.join(dir, "config.json");
1586
- if (!fs3.existsSync(p)) return null;
1587
- const stored = JSON.parse(fs3.readFileSync(p, "utf-8"));
1522
+ if (!fs2.existsSync(p)) return null;
1523
+ const stored = JSON.parse(fs2.readFileSync(p, "utf-8"));
1588
1524
  const secrets = await loadStoredSessionSecrets(dir);
1589
1525
  if (!secrets) return null;
1590
1526
  return {
@@ -1600,28 +1536,11 @@ async function getSession(configDir) {
1600
1536
  async function clearSession(configDir) {
1601
1537
  const dir = getConfigDir(configDir);
1602
1538
  const p = path4.join(dir, "config.json");
1603
- if (fs3.existsSync(p)) fs3.unlinkSync(p);
1604
- const ap = path4.join(dir, "active-course.json");
1605
- if (fs3.existsSync(ap)) fs3.unlinkSync(ap);
1539
+ if (fs2.existsSync(p)) fs2.unlinkSync(p);
1606
1540
  const onboardingPath = getCourseOnboardingPath(configDir);
1607
- if (fs3.existsSync(onboardingPath)) fs3.unlinkSync(onboardingPath);
1541
+ if (fs2.existsSync(onboardingPath)) fs2.unlinkSync(onboardingPath);
1608
1542
  await deleteStoredSessionSecrets(dir);
1609
1543
  }
1610
- async function getActiveCourse(configDir) {
1611
- const dir = getConfigDir(configDir);
1612
- const p = path4.join(dir, "active-course.json");
1613
- if (!fs3.existsSync(p)) return null;
1614
- return JSON.parse(fs3.readFileSync(p, "utf-8"));
1615
- }
1616
- async function setActiveCourse(course, configDir) {
1617
- const dir = getConfigDir(configDir);
1618
- fs3.mkdirSync(dir, { recursive: true });
1619
- const onboardingState = await getCourseOnboardingState(course.courseId, configDir);
1620
- const persistedCourse = onboardingState?.initCompletedAt && !course.lastInitCourseId ? { ...course, lastInitCourseId: course.courseId } : course;
1621
- fs3.writeFileSync(path4.join(dir, "active-course.json"), JSON.stringify(persistedCourse, null, 2), {
1622
- mode: 384
1623
- });
1624
- }
1625
1544
  async function refreshCliSession(session, configDir) {
1626
1545
  try {
1627
1546
  const response = await fetch(`${session.apiUrl}/api/cli/auth/refresh`, {
@@ -1664,43 +1583,31 @@ async function requireSession(configDir) {
1664
1583
  }
1665
1584
  return session;
1666
1585
  }
1667
- async function requireActiveCourse(configDir, options = {}) {
1668
- if (options.respectWorkspace) {
1669
- const { resolveActiveCourseWithOverride: resolveActiveCourseWithOverride2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
1670
- const resolved = await resolveActiveCourseWithOverride2({ configDir });
1671
- if (resolved) {
1672
- return {
1673
- courseId: resolved.courseId,
1674
- enrollmentId: resolved.enrollmentId,
1675
- courseTitle: resolved.courseTitle
1676
- };
1677
- }
1678
- } else {
1679
- const course = await getActiveCourse(configDir);
1680
- if (course) return course;
1681
- }
1682
- process.stderr.write("Erro: Nenhum curso ativo. Rode: tostudy select <curso>\n");
1586
+ async function requireActiveCourse() {
1587
+ const { findWorkspaceState: findWorkspaceState2 } = await Promise.resolve().then(() => (init_workspace_state(), workspace_state_exports));
1588
+ const result = await findWorkspaceState2();
1589
+ if (result) return result.state;
1590
+ process.stderr.write("Erro: Nenhum workspace configurado. Rode: tostudy setup\n");
1683
1591
  process.exit(3);
1684
1592
  }
1685
- async function setLastInitCourseId(courseId, configDir) {
1686
- const existing = await getActiveCourse(configDir);
1687
- if (!existing) return;
1688
- await setActiveCourse({ ...existing, lastInitCourseId: courseId }, configDir);
1689
- await updateCourseOnboardingState(
1690
- courseId,
1691
- {
1692
- initCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
1693
- },
1694
- configDir
1695
- );
1593
+ async function setLastInitCourseId(courseId, _configDir) {
1594
+ const { findWorkspaceState: findWorkspaceState2, updateWorkspaceState: updateWorkspaceState2 } = await Promise.resolve().then(() => (init_workspace_state(), workspace_state_exports));
1595
+ const found = await findWorkspaceState2();
1596
+ if (!found) return;
1597
+ await updateWorkspaceState2(found.workspacePath, { lastInitCourseId: courseId });
1598
+ await updateCourseOnboardingState(courseId, {
1599
+ initCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
1600
+ });
1696
1601
  }
1697
- async function checkCourseDrift(configDir) {
1698
- const course = await getActiveCourse(configDir);
1699
- if (!course) return null;
1700
- if (!course.lastInitCourseId) return null;
1701
- if (course.lastInitCourseId === course.courseId) return null;
1602
+ async function checkCourseDrift() {
1603
+ const { findWorkspaceState: findWorkspaceState2 } = await Promise.resolve().then(() => (init_workspace_state(), workspace_state_exports));
1604
+ const result = await findWorkspaceState2();
1605
+ if (!result) return null;
1606
+ const state = result.state;
1607
+ if (!state.lastInitCourseId) return null;
1608
+ if (state.lastInitCourseId === state.courseId) return null;
1702
1609
  return [
1703
- `\u26A0\uFE0F Curso ativo mudou para "${course.courseTitle}".`,
1610
+ `\u26A0\uFE0F Curso ativo mudou para "${state.courseTitle}".`,
1704
1611
  ` Rode \`tostudy init\` para atualizar o contexto do assistente.`,
1705
1612
  ""
1706
1613
  ].join("\n");
@@ -1712,295 +1619,6 @@ var init_session = __esm({
1712
1619
  }
1713
1620
  });
1714
1621
 
1715
- // src/onboarding/api.ts
1716
- async function apiFetch2(url2, token2, init) {
1717
- const response = await fetch(url2, {
1718
- ...init,
1719
- headers: {
1720
- "Content-Type": "application/json",
1721
- Authorization: `Bearer ${token2}`,
1722
- ...init?.headers
1723
- }
1724
- });
1725
- const body = await response.json();
1726
- if (!response.ok) {
1727
- throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
1728
- }
1729
- return body;
1730
- }
1731
- async function getRemoteEnrollmentOnboarding(input2) {
1732
- const url2 = new URL(`${input2.apiUrl}/api/cli/onboarding`);
1733
- url2.searchParams.set("enrollmentId", input2.enrollmentId);
1734
- const response = await apiFetch2(
1735
- url2.toString(),
1736
- input2.token
1737
- );
1738
- return response.onboarding;
1739
- }
1740
- async function saveRemoteEnrollmentOnboarding(input2) {
1741
- const response = await apiFetch2(
1742
- `${input2.apiUrl}/api/cli/onboarding`,
1743
- input2.token,
1744
- {
1745
- method: "POST",
1746
- body: JSON.stringify({
1747
- enrollmentId: input2.enrollmentId,
1748
- learnerBrief: input2.learnerBrief,
1749
- learnerProfile: input2.learnerProfile
1750
- })
1751
- }
1752
- );
1753
- return response.onboarding;
1754
- }
1755
- var init_api = __esm({
1756
- "src/onboarding/api.ts"() {
1757
- "use strict";
1758
- init_http2();
1759
- }
1760
- });
1761
-
1762
- // src/output/init-template.ts
1763
- function detectTechFromTags(tags) {
1764
- const found = /* @__PURE__ */ new Set();
1765
- const keywords = Object.keys(LANGUAGE_TAGS);
1766
- const joined = tags.map((t) => t.toLowerCase()).join(" ");
1767
- for (const kw of keywords) {
1768
- const pattern = new RegExp(`\\b${kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
1769
- if (pattern.test(joined)) {
1770
- found.add(kw);
1771
- }
1772
- }
1773
- return [...found];
1774
- }
1775
- function formatCourseLanguage(tags) {
1776
- if (!tags || tags.length === 0) return "Programa\xE7\xE3o";
1777
- const direct = tags.map((t) => LANGUAGE_TAGS[t.toLowerCase()]).filter(Boolean);
1778
- if (direct.length > 0) return direct.slice(0, 3).join(" / ");
1779
- const detected = detectTechFromTags(tags);
1780
- const mapped = detected.map((t) => LANGUAGE_TAGS[t]).filter(Boolean);
1781
- return mapped.length > 0 ? mapped.slice(0, 3).join(" / ") : "Programa\xE7\xE3o";
1782
- }
1783
- function formatCourseLevel(level) {
1784
- const map2 = {
1785
- beginner: "Iniciante",
1786
- intermediate: "Intermedi\xE1rio",
1787
- advanced: "Avan\xE7ado"
1788
- };
1789
- return map2[level ?? "intermediate"] ?? "Intermedi\xE1rio";
1790
- }
1791
- function formatCourseMethodology(approach) {
1792
- const map2 = {
1793
- hybrid: "Teoria e pr\xE1tica",
1794
- structured: "Estruturado com exerc\xEDcios",
1795
- exploratory: "Explorat\xF3rio",
1796
- mentored: "Mentoria guiada",
1797
- "ai-exploration": "Explora\xE7\xE3o com IA"
1798
- };
1799
- return map2[approach] ?? "Teoria e pr\xE1tica";
1800
- }
1801
- function formatCourseObjective(description) {
1802
- if (!description || description.trim() === "") return null;
1803
- const firstSentence = description.split(/[.!?]/)[0]?.trim();
1804
- if (!firstSentence) return null;
1805
- if (firstSentence.length > 200) return firstSentence.slice(0, 200) + "...";
1806
- return firstSentence;
1807
- }
1808
- function buildInitTemplate(userName, course, progress3) {
1809
- const lines = [];
1810
- lines.push("# ToStudy CLI \u2014 Instru\xE7\xF5es para o Assistente AI");
1811
- lines.push("");
1812
- lines.push("Voc\xEA \xE9 um tutor de programa\xE7\xE3o auxiliando um aluno via terminal.");
1813
- lines.push("");
1814
- lines.push("## 1. Estado Atual");
1815
- lines.push(`- Usu\xE1rio: ${userName}`);
1816
- lines.push(`- Curso: "${course.title}" (${progress3?.coursePercent ?? course.progress}%)`);
1817
- if (progress3) {
1818
- const mod = progress3.currentModule;
1819
- const les = progress3.currentLesson;
1820
- lines.push(`- M\xF3dulo ${mod.order}/${mod.totalModules}: "${mod.title}"`);
1821
- lines.push(`- Li\xE7\xE3o ${les.order}/${les.totalLessons}: "${les.title}"`);
1822
- } else {
1823
- lines.push("- Nenhuma li\xE7\xE3o iniciada. Rode `tostudy start` para come\xE7ar.");
1824
- }
1825
- const language = formatCourseLanguage(course.tags);
1826
- const level = formatCourseLevel(course.level);
1827
- const methodology = formatCourseMethodology(course.teachingApproach);
1828
- const objective = formatCourseObjective(course.description);
1829
- lines.push("");
1830
- lines.push("## 2. Contexto do Curso");
1831
- lines.push(`- Linguagem: ${language}`);
1832
- lines.push(`- N\xEDvel: ${level}`);
1833
- lines.push(`- Metodologia: ${methodology}`);
1834
- if (objective) lines.push(`- Objetivo: ${objective}`);
1835
- lines.push("");
1836
- lines.push("Adapte explica\xE7\xF5es e exemplos para esta stack.");
1837
- lines.push("Exerc\xEDcios envolvem arquivos de c\xF3digo no workspace do aluno.");
1838
- lines.push("");
1839
- lines.push("## 3. Comandos");
1840
- lines.push("| Comando | Uso |");
1841
- lines.push("|----------------------------------|----------------------------------|");
1842
- lines.push("| `tostudy lesson` | Ver conte\xFAdo da li\xE7\xE3o atual |");
1843
- lines.push("| `tostudy next` | Avan\xE7ar para pr\xF3xima li\xE7\xE3o |");
1844
- lines.push("| `tostudy hint` | Dica progressiva |");
1845
- lines.push("| `tostudy validate <arquivo>` | Validar solu\xE7\xE3o do exerc\xEDcio |");
1846
- lines.push("| `tostudy progress` | Ver progresso detalhado |");
1847
- lines.push("| `tostudy start` | Iniciar/retomar m\xF3dulo |");
1848
- lines.push("| `tostudy start-next` | Avan\xE7ar para pr\xF3ximo m\xF3dulo |");
1849
- lines.push("| `tostudy courses` | Listar cursos matriculados |");
1850
- lines.push("| `tostudy select <n>` | Trocar curso ativo |");
1851
- lines.push("| `tostudy doctor` | Diagn\xF3stico de problemas |");
1852
- lines.push("");
1853
- lines.push("Adicione `--json` para output estruturado quando precisar parsear.");
1854
- lines.push("");
1855
- lines.push("## 4. Regras");
1856
- lines.push("1. Comece rodando `tostudy lesson` para carregar o conte\xFAdo");
1857
- lines.push("2. N\xE3o d\xEA respostas diretas \u2014 use `tostudy hint` para dicas progressivas");
1858
- lines.push("3. Ap\xF3s `tostudy validate`, analise cada crit\xE9rio e guie a corre\xE7\xE3o");
1859
- lines.push("4. N\xE3o avance com `tostudy next` sem o aluno completar o exerc\xEDcio");
1860
- lines.push('5. Se algum comando retornar aviso de "curso ativo mudou", rode `tostudy init`');
1861
- lines.push("");
1862
- lines.push("## 5. Coexist\xEAncia");
1863
- lines.push("Este workspace pode ter suas pr\xF3prias instru\xE7\xF5es (CLAUDE.md, AGENTS.md).");
1864
- lines.push("- Instru\xE7\xF5es do projeto \u2192 decis\xF5es de c\xF3digo (estilo, lint, arquitetura)");
1865
- lines.push("- Instru\xE7\xF5es do ToStudy \u2192 fluxo de estudo (navega\xE7\xE3o, exerc\xEDcios, valida\xE7\xE3o)");
1866
- return lines.join("\n");
1867
- }
1868
- function resolveTutorMode(level) {
1869
- if (level === "beginner") return "guided";
1870
- if (level === "advanced") return "direct";
1871
- return "balanced";
1872
- }
1873
- function formatTutorMode(mode) {
1874
- const map2 = {
1875
- guided: "Guiado",
1876
- balanced: "Equilibrado",
1877
- direct: "Direto"
1878
- };
1879
- return map2[mode];
1880
- }
1881
- function buildTutorPersonalizationSection(course, learnerProfile) {
1882
- const mode = resolveTutorMode(course.level);
1883
- const lines = [];
1884
- lines.push("## 6. Personaliza\xE7\xE3o do Tutor");
1885
- lines.push(`- Modo inicial do tutor: ${formatTutorMode(mode)}`);
1886
- lines.push(
1887
- "- Na primeira intera\xE7\xE3o, apresente a escolha entre cen\xE1rio fict\xEDcio do curso e adapta\xE7\xE3o ao contexto real do aluno."
1888
- );
1889
- lines.push(
1890
- `- O aluno atual j\xE1 optou por: ${learnerProfile.adaptToRealContext ? "Adaptar para o contexto real" : "Manter o cen\xE1rio fict\xEDcio"}`
1891
- );
1892
- lines.push(`- Segmento: ${learnerProfile.segment}`);
1893
- lines.push(`- Empresa: ${learnerProfile.company}`);
1894
- lines.push(`- Produtos/servi\xE7os: ${learnerProfile.productsOrServices}`);
1895
- lines.push(`- Regi\xE3o: ${learnerProfile.region}`);
1896
- lines.push(`- Equipe: ${learnerProfile.team}`);
1897
- lines.push(`- Objetivo: ${learnerProfile.goal}`);
1898
- lines.push(`- N\xEDvel declarado do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
1899
- if (learnerProfile.adaptToRealContext) {
1900
- lines.push(
1901
- "- Use o contexto do aluno como padr\xE3o para explica\xE7\xF5es, exemplos, exerc\xEDcios e framing."
1902
- );
1903
- lines.push(
1904
- "- Troque nomes, entreg\xE1veis e restri\xE7\xF5es do cen\xE1rio fict\xEDcio por equivalentes reais."
1905
- );
1906
- lines.push(
1907
- "- Preserve objetivos pedag\xF3gicos, crit\xE9rios de valida\xE7\xE3o e progress\xE3o de dificuldade."
1908
- );
1909
- } else {
1910
- lines.push(
1911
- "- Preserve o cen\xE1rio fict\xEDcio como padr\xE3o e conecte os conceitos ao neg\xF3cio real quando isso ajudar."
1912
- );
1913
- lines.push(
1914
- "- Se o aluno quiser migrar para o contexto real depois, oriente a rodar `tostudy init` novamente."
1915
- );
1916
- }
1917
- if (mode === "guided") {
1918
- lines.push(
1919
- "- No modo guiado, explique o porqu\xEA de cada passo, proponha checkpoints curtos e confirme entendimento antes de avan\xE7ar."
1920
- );
1921
- } else if (mode === "direct") {
1922
- lines.push(
1923
- "- Mesmo no modo mais direto, continue sem dar a resposta pronta e preserve a progress\xE3o pedag\xF3gica."
1924
- );
1925
- } else {
1926
- lines.push(
1927
- "- No modo equilibrado, combine explica\xE7\xF5es curtas com autonomia entre checkpoints."
1928
- );
1929
- }
1930
- return lines.join("\n");
1931
- }
1932
- function buildLearnerBrief(userName, course, learnerProfile) {
1933
- const lines = [];
1934
- lines.push("# ToStudy CLI \u2014 Brief do Aluno");
1935
- lines.push("");
1936
- lines.push("Este documento resume o contexto real do aluno para orientar exemplos e exercicios.");
1937
- lines.push("");
1938
- lines.push("## 1. Identificacao");
1939
- lines.push(`- Aluno: ${userName}`);
1940
- lines.push(`- Curso: "${course.title}"`);
1941
- lines.push(`- Segmento: ${learnerProfile.segment}`);
1942
- lines.push(`- Empresa: ${learnerProfile.company}`);
1943
- lines.push(`- Produtos/servicos: ${learnerProfile.productsOrServices}`);
1944
- lines.push(`- Regiao: ${learnerProfile.region}`);
1945
- lines.push(`- Equipe: ${learnerProfile.team}`);
1946
- lines.push("");
1947
- lines.push("## 2. Objetivo e Contexto");
1948
- lines.push(`- Objetivo principal: ${learnerProfile.goal}`);
1949
- lines.push(`- Nivel do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
1950
- lines.push(
1951
- `- Adaptar curso ao contexto real: ${learnerProfile.adaptToRealContext ? "Sim" : "Nao"}`
1952
- );
1953
- lines.push("");
1954
- lines.push("## 3. Como usar este brief");
1955
- lines.push("- Reutilize este contexto para adaptar exemplos, cenarios e exercicios.");
1956
- lines.push("- Preserve a proposta pedagogica do curso antes de customizar o contexto.");
1957
- lines.push(
1958
- "- Se algo mudar no negocio do aluno, rode `tostudy init` novamente para atualizar o brief."
1959
- );
1960
- return lines.join("\n");
1961
- }
1962
- function buildInitArtifacts(userName, course, progress3, learnerProfile) {
1963
- return {
1964
- tutorInstructions: [
1965
- buildInitTemplate(userName, course, progress3),
1966
- buildTutorPersonalizationSection(course, learnerProfile)
1967
- ].join("\n\n"),
1968
- learnerBrief: buildLearnerBrief(userName, course, learnerProfile)
1969
- };
1970
- }
1971
- var LANGUAGE_TAGS;
1972
- var init_init_template = __esm({
1973
- "src/output/init-template.ts"() {
1974
- "use strict";
1975
- LANGUAGE_TAGS = {
1976
- javascript: "JavaScript",
1977
- typescript: "TypeScript",
1978
- python: "Python",
1979
- react: "React",
1980
- "react-native": "React Native",
1981
- node: "Node.js",
1982
- nodejs: "Node.js",
1983
- go: "Go",
1984
- rust: "Rust",
1985
- java: "Java",
1986
- csharp: "C#",
1987
- "c#": "C#",
1988
- cpp: "C++",
1989
- ruby: "Ruby",
1990
- php: "PHP",
1991
- swift: "Swift",
1992
- kotlin: "Kotlin",
1993
- html: "HTML",
1994
- css: "CSS",
1995
- sql: "SQL",
1996
- vue: "Vue.js",
1997
- angular: "Angular",
1998
- nextjs: "Next.js",
1999
- svelte: "Svelte"
2000
- };
2001
- }
2002
- });
2003
-
2004
1622
  // src/output/formatter.ts
2005
1623
  function output(data, opts) {
2006
1624
  if (opts.json) {
@@ -2016,6 +1634,13 @@ function error(msg, code = 1) {
2016
1634
  `);
2017
1635
  process.exit(code);
2018
1636
  }
1637
+ function jsonError(key, opts) {
1638
+ const code = opts?.code ?? 1;
1639
+ process.stdout.write(
1640
+ JSON.stringify({ error: true, key, message: opts?.message ?? key, code }) + "\n"
1641
+ );
1642
+ process.exit(code);
1643
+ }
2019
1644
  function progressBar(percent, width = 20) {
2020
1645
  const clamped = Math.max(0, Math.min(100, percent));
2021
1646
  const filled = Math.round(clamped / 100 * width);
@@ -2203,169 +1828,17 @@ var init_formatter = __esm({
2203
1828
  }
2204
1829
  });
2205
1830
 
2206
- // src/commands/login.ts
2207
- import { Command } from "commander";
2208
- import { execFile as execFile2 } from "node:child_process";
2209
- async function syncRemoteProfile(apiUrl, token2, activeCourse, userName, courseData) {
2210
- try {
2211
- const remote = await getRemoteEnrollmentOnboarding({
2212
- apiUrl,
2213
- token: token2,
2214
- enrollmentId: activeCourse.enrollmentId
2215
- });
2216
- if (remote?.learnerProfile) {
2217
- const artifacts = buildInitArtifacts(userName, courseData, null, remote.learnerProfile);
2218
- await saveCourseLearnerProfile(activeCourse, remote.learnerProfile, artifacts);
2219
- console.log(` \u2713 Perfil de aprendizagem sincronizado (${remote.source})`);
2220
- }
2221
- } catch (err) {
2222
- logger2.warn("Failed to sync remote profile", {
2223
- error: err instanceof Error ? err.message : String(err)
2224
- });
2225
- }
2226
- }
2227
- var logger2, DEFAULT_API_URL, PORT, loginCommand;
2228
- var init_login = __esm({
2229
- "src/commands/login.ts"() {
2230
- "use strict";
2231
- init_src();
2232
- init_courses();
2233
- init_http2();
2234
- init_oauth_server();
2235
- init_session();
2236
- init_api();
2237
- init_init_template();
2238
- init_formatter();
2239
- logger2 = createLogger("cli:login");
2240
- DEFAULT_API_URL = "https://tostudy.ai";
2241
- PORT = 9876;
2242
- loginCommand = new Command("login").description("Autentica no ToStudy via browser").option("--manual", "Login manual (sem browser)").option("--api-url <url>", "API URL override", DEFAULT_API_URL).action(async (opts) => {
2243
- const apiUrl = opts.apiUrl;
2244
- if (opts.manual) {
2245
- console.log(`
2246
- Acesse este endere\xE7o no seu browser:`);
2247
- console.log(` ${apiUrl}/api/cli/auth/authorize?port=${PORT}
2248
- `);
2249
- console.log(" Ap\xF3s autorizar, o browser ser\xE1 redirecionado e o login ser\xE1 conclu\xEDdo.");
2250
- console.log(" Certifique-se de rodar este comando sem --manual para o fluxo autom\xE1tico.\n");
2251
- return;
2252
- }
2253
- console.log("\n Abrindo browser para autentica\xE7\xE3o...\n");
2254
- const serverPromise = startCallbackServer(PORT);
2255
- const authUrl = `${apiUrl}/api/cli/auth/authorize?port=${PORT}`;
2256
- const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
2257
- execFile2(openCmd, [authUrl], (err) => {
2258
- if (err) {
2259
- console.log(` N\xE3o foi poss\xEDvel abrir o browser automaticamente.`);
2260
- console.log(` Abra manualmente: ${authUrl}
2261
- `);
2262
- }
2263
- });
2264
- try {
2265
- const { code } = await serverPromise;
2266
- const res = await fetch(`${apiUrl}/api/cli/auth/exchange`, {
2267
- method: "POST",
2268
- headers: { "Content-Type": "application/json" },
2269
- body: JSON.stringify({ code })
2270
- });
2271
- if (!res.ok) {
2272
- const body = await res.json();
2273
- error(body.error ?? "Falha na autentica\xE7\xE3o");
2274
- }
2275
- const { token: token2, refreshToken, sessionId, userId, userName, expiresAt } = await res.json();
2276
- await saveSession({ token: token2, refreshToken, sessionId, userId, userName, expiresAt, apiUrl });
2277
- console.log(`
2278
- \u2713 Logado como ${userName}`);
2279
- try {
2280
- const data = createHttpProvider(apiUrl, token2);
2281
- const courses3 = await listCourses({ userId }, { data, logger: logger2 });
2282
- if (courses3.length > 0) {
2283
- console.log(` \u2713 ${courses3.length} curso(s) matriculado(s)`);
2284
- const activeCourse = await getActiveCourse();
2285
- const targetCourse = activeCourse && courses3.find((c) => c.courseId === activeCourse.courseId) ? activeCourse : null;
2286
- if (!targetCourse && courses3.length === 1) {
2287
- const only = courses3[0];
2288
- const newActive = {
2289
- courseId: only.courseId,
2290
- courseTitle: only.title,
2291
- enrollmentId: only.enrollmentId,
2292
- courseTags: only.tags,
2293
- courseLevel: only.level
2294
- };
2295
- await setActiveCourse(newActive);
2296
- console.log(` \u2713 Curso ativo: ${only.title} (${only.progress}%)`);
2297
- await syncRemoteProfile(apiUrl, token2, newActive, userName, courses3[0]);
2298
- } else if (targetCourse) {
2299
- const matched = courses3.find((c) => c.courseId === targetCourse.courseId);
2300
- if (matched) {
2301
- await syncRemoteProfile(apiUrl, token2, targetCourse, userName, matched);
2302
- }
2303
- } else {
2304
- console.log(` \u2192 Rode \`tostudy setup\` para configurar seu ambiente`);
2305
- }
2306
- } else {
2307
- console.log(` \u2192 Nenhum curso matriculado. Acesse tostudy.ai para se matricular.`);
2308
- }
2309
- } catch (syncErr) {
2310
- logger2.warn("Post-login sync failed", {
2311
- error: syncErr instanceof Error ? syncErr.message : String(syncErr)
2312
- });
2313
- }
2314
- console.log("");
2315
- } catch (err) {
2316
- const msg = err instanceof Error ? err.message : "Login falhou";
2317
- error(msg);
2318
- }
2319
- });
2320
- }
2321
- });
2322
-
2323
- // src/commands/logout.ts
2324
- import { Command as Command2 } from "commander";
2325
- var logoutCommand;
2326
- var init_logout = __esm({
2327
- "src/commands/logout.ts"() {
2328
- "use strict";
2329
- init_session();
2330
- logoutCommand = new Command2("logout").description("Remove token local").action(async () => {
2331
- await clearSession();
2332
- console.log("\n Deslogado com sucesso.\n");
2333
- });
2334
- }
1831
+ // src/installer/ide-detector.ts
1832
+ var ide_detector_exports = {};
1833
+ __export(ide_detector_exports, {
1834
+ detectIDEs: () => detectIDEs
2335
1835
  });
2336
-
2337
- // src/installer/node-detector.ts
2338
1836
  import { execFileSync } from "node:child_process";
2339
- function detectNode() {
2340
- try {
2341
- const version3 = execFileSync("node", ["--version"], {
2342
- encoding: "utf-8"
2343
- }).trim();
2344
- let nodePath = null;
2345
- try {
2346
- const whichCmd = process.platform === "win32" ? "where" : "which";
2347
- nodePath = execFileSync(whichCmd, ["node"], { encoding: "utf-8" }).trim();
2348
- } catch {
2349
- }
2350
- const major = parseInt(version3.replace("v", ""), 10);
2351
- return { installed: true, version: version3, meetsMinimum: major >= 18, path: nodePath };
2352
- } catch {
2353
- return { installed: false, version: null, meetsMinimum: false, path: null };
2354
- }
2355
- }
2356
- var init_node_detector = __esm({
2357
- "src/installer/node-detector.ts"() {
2358
- "use strict";
2359
- }
2360
- });
2361
-
2362
- // src/installer/ide-detector.ts
2363
- import { execFileSync as execFileSync2 } from "node:child_process";
2364
- import fs4 from "node:fs";
1837
+ import fs3 from "node:fs";
2365
1838
  import path5 from "node:path";
2366
1839
  import os5 from "node:os";
2367
1840
  function checkExists(p) {
2368
- return fs4.existsSync(p);
1841
+ return fs3.existsSync(p);
2369
1842
  }
2370
1843
  function detectIDEs() {
2371
1844
  const home = os5.homedir();
@@ -2431,7 +1904,7 @@ function detectIDEs() {
2431
1904
  const claudeIde = ides.find((ide) => ide.id === "claude-code");
2432
1905
  if (claudeIde) {
2433
1906
  try {
2434
- execFileSync2("which", ["claude"], { encoding: "utf-8" });
1907
+ execFileSync("which", ["claude"], { encoding: "utf-8" });
2435
1908
  claudeIde.detected = true;
2436
1909
  } catch {
2437
1910
  }
@@ -2444,47 +1917,6 @@ var init_ide_detector = __esm({
2444
1917
  }
2445
1918
  });
2446
1919
 
2447
- // src/installer/mcp-setup.ts
2448
- import { spawn } from "node:child_process";
2449
- async function exchangeCliSessionForMcpToken(session, fetchImpl = fetch) {
2450
- const res = await fetchImpl(`${session.apiUrl}/api/mcp/token`, {
2451
- method: "POST",
2452
- headers: {
2453
- Authorization: `Bearer ${session.token}`
2454
- }
2455
- });
2456
- if (!res.ok) {
2457
- const body = await res.json().catch(() => ({}));
2458
- throw new Error(body.error ?? `Falha ao obter token MCP (${res.status})`);
2459
- }
2460
- return res.json();
2461
- }
2462
- async function runMcpSetup(session, token2, spawnImpl = spawn) {
2463
- const command = process.platform === "win32" ? "npx.cmd" : "npx";
2464
- await new Promise((resolve, reject) => {
2465
- const child = spawnImpl(command, ["-y", "@tostudy-ai/mcp-setup", "--url", session.apiUrl], {
2466
- stdio: "inherit",
2467
- env: {
2468
- ...process.env,
2469
- TOSTUDY_API_KEY: token2
2470
- }
2471
- });
2472
- child.once("error", reject);
2473
- child.once("exit", (code) => {
2474
- if (code === 0) {
2475
- resolve();
2476
- return;
2477
- }
2478
- reject(new Error(`MCP setup failed with exit code ${code ?? "unknown"}`));
2479
- });
2480
- });
2481
- }
2482
- var init_mcp_setup = __esm({
2483
- "src/installer/mcp-setup.ts"() {
2484
- "use strict";
2485
- }
2486
- });
2487
-
2488
1920
  // src/output/instruction-template-v3.ts
2489
1921
  function renderCourseInstruction(input2) {
2490
1922
  const sections = [];
@@ -2511,26 +1943,47 @@ Voce e o tutor ToStudy. Fale com o aluno em **primeira pessoa**. Apresente o con
2511
1943
  sections.push(renderVoiceRules());
2512
1944
  sections.push(`## Primeira Coisa (SEMPRE)
2513
1945
 
2514
- Rode \`tostudy progress --json\` em silencio para descobrir o estado do aluno.
1946
+ Rode \`tostudy context --json\` em silencio para descobrir o estado do aluno.
1947
+
1948
+ ### Se erro "no_workspace" (aluno ainda nao configurou):
2515
1949
 
2516
- **Se erro "Nenhum curso ativo":**
1950
+ 1. Pergunte ao aluno: "Em qual pasta voce quer estudar? A pasta atual e \`{cwd}\`. Usar esta mesma?"
1951
+ 2. Se o aluno confirmar, rode \`tostudy courses --json\` em silencio.
1952
+ 3. Mostre a lista de cursos de forma amigavel.
1953
+ 4. Se o aluno tiver apenas 1 curso, ative direto. Se tiver mais, pergunte qual quer estudar.
1954
+ 5. Rode \`tostudy select <numero> --json\` em silencio.
1955
+ 6. O resultado contem o campo \`instruction\` \u2014 **leia e siga essas instrucoes como sua nova persona**.
1956
+
1957
+ ### Se workspace existe mas sem curso ativo:
2517
1958
 
2518
1959
  1. Rode \`tostudy courses --json\` em silencio.
2519
1960
  2. Mostre a lista ao aluno de forma amigavel.
2520
- 3. Pergunte: "Qual curso quer estudar?".
2521
- 4. Rode \`tostudy select <numero>\` com a escolha.
2522
- 5. Volte ao inicio.
2523
-
2524
- **Se curso ativo mas sem brief do aluno:**
1961
+ 3. Pergunte: "Qual curso quer estudar?"
1962
+ 4. Rode \`tostudy select <numero> --json\` e siga o campo \`instruction\`.
2525
1963
 
2526
- Nao interrompa. Comece a aula normalmente. Durante a conversa, colete contexto natural e, quando for apropriado, sugira ao aluno rodar \`tostudy brief-create\` ou abrir \`https://tostudy.ai/student/settings/learner-brief\`.
1964
+ ### Se tudo pronto (workspace + curso + contexto):
2527
1965
 
2528
- **Se tudo pronto:** Siga "Como Conduzir a Aula" abaixo.`);
1966
+ 1. Leia os \`moduleSummaries\` do resultado para saber o que o aluno ja estudou.
1967
+ 2. Se houver summaries, mencione brevemente: "Da ultima vez paramos em {ultimoModulo}. Pronto pra continuar?"
1968
+ 3. Siga "Como Conduzir a Aula" abaixo.`);
2529
1969
  sections.push(renderHowToConduct());
2530
1970
  sections.push(renderSilentTools());
1971
+ sections.push(`## Compactacao de Contexto (Automatico)
1972
+
1973
+ Quando \`tostudy next --json\` retornar \`MODULE_COMPLETE\`:
1974
+
1975
+ 1. **Pare e resuma** o modulo que acabou de ser concluido:
1976
+ - O que foi coberto (topicos e licoes)
1977
+ - Onde o aluno travou ou precisou de dicas
1978
+ - Insights ou conexoes que o aluno fez com seu trabalho real
1979
+ - Nivel demonstrado (iniciante/intermediario/avancado neste topico)
1980
+ 2. Salve o resumo: envie via \`echo "conteudo do resumo" | tostudy compact --module-id {id} --module-title "{titulo}" --json\`
1981
+ 3. Continue normalmente para o proximo modulo.
1982
+
1983
+ Isso garante continuidade entre sessoes e evita estouro de contexto em sessoes longas.`);
2531
1984
  sections.push(renderHandlingSituations());
2532
1985
  sections.push(renderTechnicalReference());
2533
- sections.push(`<!-- tostudy-template-version: 3 -->`);
1986
+ sections.push(`<!-- tostudy-template-version: 4 -->`);
2534
1987
  return sections.join("\n\n") + "\n";
2535
1988
  }
2536
1989
  function renderTitle(input2) {
@@ -2759,7 +2212,13 @@ var init_instruction_template_v3 = __esm({
2759
2212
  });
2760
2213
 
2761
2214
  // src/workspace/instruction-files.ts
2762
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
2215
+ var instruction_files_exports = {};
2216
+ __export(instruction_files_exports, {
2217
+ installUniversalCommand: () => installUniversalCommand,
2218
+ slugify: () => slugify,
2219
+ writeInstructionFiles: () => writeInstructionFiles
2220
+ });
2221
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
2763
2222
  import { join as join2 } from "node:path";
2764
2223
  function slugify(title) {
2765
2224
  return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
@@ -2772,7 +2231,7 @@ function writeInstructionFiles(cwd, ctx, content) {
2772
2231
  mkdirSync3(claudeDir, { recursive: true });
2773
2232
  }
2774
2233
  const claudeFile = `tostudy-${slug}.md`;
2775
- writeFileSync3(join2(claudeDir, claudeFile), content);
2234
+ writeFileSync4(join2(claudeDir, claudeFile), content);
2776
2235
  written.push(`.claude/commands/${claudeFile}`);
2777
2236
  const cursorDir = join2(cwd, ".cursor", "rules");
2778
2237
  if (existsSync3(join2(cwd, ".cursor")) || existsSync3(cursorDir)) {
@@ -2787,16 +2246,16 @@ alwaysApply: true
2787
2246
 
2788
2247
  ${content}`;
2789
2248
  const cursorFile = `tostudy-${slug}.mdc`;
2790
- writeFileSync3(join2(cursorDir, cursorFile), mdcContent);
2249
+ writeFileSync4(join2(cursorDir, cursorFile), mdcContent);
2791
2250
  written.push(`.cursor/rules/${cursorFile}`);
2792
2251
  }
2793
2252
  const courseDir = join2(cwd, ".tostudy", "courses", slug);
2794
2253
  if (!existsSync3(courseDir)) {
2795
2254
  mkdirSync3(courseDir, { recursive: true });
2796
2255
  }
2797
- writeFileSync3(join2(courseDir, "AGENTS.md"), content);
2256
+ writeFileSync4(join2(courseDir, "AGENTS.md"), content);
2798
2257
  written.push(`.tostudy/courses/${slug}/AGENTS.md`);
2799
- logger3.info("Generated instruction files (v3)", {
2258
+ logger2.info("Generated instruction files (v3)", {
2800
2259
  slug,
2801
2260
  files: written
2802
2261
  });
@@ -2808,7 +2267,7 @@ function installUniversalCommand(platform2, cwd = process.cwd()) {
2808
2267
  if (platform2 === "claude" || platform2 === "codex") {
2809
2268
  const dir = join2(cwd, ".claude", "commands");
2810
2269
  if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
2811
- writeFileSync3(join2(dir, "tostudy.md"), content);
2270
+ writeFileSync4(join2(dir, "tostudy.md"), content);
2812
2271
  written.push(".claude/commands/tostudy.md");
2813
2272
  }
2814
2273
  if (platform2 === "cursor") {
@@ -2821,32 +2280,31 @@ alwaysApply: true
2821
2280
  ---
2822
2281
 
2823
2282
  ${content}`;
2824
- writeFileSync3(join2(dir, "tostudy.mdc"), mdcContent);
2283
+ writeFileSync4(join2(dir, "tostudy.mdc"), mdcContent);
2825
2284
  written.push(".cursor/rules/tostudy.mdc");
2826
2285
  }
2827
2286
  if (platform2 === "generic") {
2828
2287
  const dir = join2(cwd, ".tostudy");
2829
2288
  if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
2830
- writeFileSync3(join2(dir, "AGENTS.md"), content);
2289
+ writeFileSync4(join2(dir, "AGENTS.md"), content);
2831
2290
  written.push(".tostudy/AGENTS.md");
2832
2291
  }
2833
- logger3.info("Installed universal /tostudy command", { platform: platform2, files: written });
2292
+ logger2.info("Installed universal /tostudy command", { platform: platform2, files: written });
2834
2293
  return written;
2835
2294
  }
2836
- var logger3;
2295
+ var logger2;
2837
2296
  var init_instruction_files = __esm({
2838
2297
  "src/workspace/instruction-files.ts"() {
2839
2298
  "use strict";
2840
2299
  init_src();
2841
2300
  init_instruction_template_v3();
2842
- logger3 = createLogger("cli:instruction-files");
2301
+ logger2 = createLogger("cli:instruction-files");
2843
2302
  }
2844
2303
  });
2845
2304
 
2846
- // src/commands/setup.ts
2847
- import { Command as Command3 } from "commander";
2848
- import { existsSync as existsSync4 } from "node:fs";
2849
- import { join as join3 } from "node:path";
2305
+ // src/commands/login.ts
2306
+ import { Command } from "commander";
2307
+ import { execFile as execFile2 } from "node:child_process";
2850
2308
  function ideToPlatform(ideName) {
2851
2309
  const lower = ideName.toLowerCase();
2852
2310
  if (lower.includes("claude")) return "claude";
@@ -2856,107 +2314,168 @@ function ideToPlatform(ideName) {
2856
2314
  return "generic";
2857
2315
  return null;
2858
2316
  }
2859
- async function runSetup(opts, deps = defaultDeps) {
2860
- deps.log("\n ToStudy Setup\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
2861
- deps.log(" 1. Verificando Node.js...");
2862
- const node = deps.detectNode();
2863
- if (!node.installed) {
2864
- deps.log(" \u2717 Node.js n\xE3o encontrado");
2865
- deps.log(" \u2192 Instale via: https://nodejs.org/ ou `nvm install --lts`\n");
2866
- deps.exit(1);
2867
- }
2868
- if (!node.meetsMinimum) {
2869
- deps.log(` \u2717 Node.js ${node.version} \xE9 muito antigo (m\xEDnimo: v18)`);
2870
- deps.log(" \u2192 Atualize via: `nvm install --lts`\n");
2871
- deps.exit(1);
2872
- }
2873
- deps.log(` \u2713 Node.js ${node.version}
2317
+ var logger3, DEFAULT_API_URL, PORT, loginCommand;
2318
+ var init_login = __esm({
2319
+ "src/commands/login.ts"() {
2320
+ "use strict";
2321
+ init_src();
2322
+ init_courses();
2323
+ init_http2();
2324
+ init_oauth_server();
2325
+ init_session();
2326
+ init_formatter();
2327
+ logger3 = createLogger("cli:login");
2328
+ DEFAULT_API_URL = "https://tostudy.ai";
2329
+ PORT = 9876;
2330
+ loginCommand = new Command("login").description("Autentica no ToStudy via browser").option("--manual", "Login manual (sem browser)").option("--api-url <url>", "API URL override", DEFAULT_API_URL).action(async (opts) => {
2331
+ const apiUrl = opts.apiUrl;
2332
+ if (opts.manual) {
2333
+ console.log(`
2334
+ Acesse este endere\xE7o no seu browser:`);
2335
+ console.log(` ${apiUrl}/api/cli/auth/authorize?port=${PORT}
2874
2336
  `);
2875
- deps.log(" 2. Verificando autentica\xE7\xE3o...");
2876
- let session = null;
2877
- try {
2878
- session = await deps.getSession();
2879
- } catch {
2880
- }
2881
- if (!session) {
2882
- deps.log(" \u2717 N\xE3o autenticado");
2883
- deps.log(" \u2192 Rode: tostudy login\n");
2884
- deps.exit(1);
2885
- }
2886
- deps.log(` \u2713 Logado como ${session.userName}
2337
+ console.log(" Ap\xF3s autorizar, o browser ser\xE1 redirecionado e o login ser\xE1 conclu\xEDdo.");
2338
+ console.log(" Certifique-se de rodar este comando sem --manual para o fluxo autom\xE1tico.\n");
2339
+ return;
2340
+ }
2341
+ console.log("\n Abrindo browser para autentica\xE7\xE3o...\n");
2342
+ const serverPromise = startCallbackServer(PORT);
2343
+ const authUrl = `${apiUrl}/api/cli/auth/authorize?port=${PORT}`;
2344
+ const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
2345
+ execFile2(openCmd, [authUrl], (err) => {
2346
+ if (err) {
2347
+ console.log(` N\xE3o foi poss\xEDvel abrir o browser automaticamente.`);
2348
+ console.log(` Abra manualmente: ${authUrl}
2887
2349
  `);
2888
- deps.log(" 3. Detectando IDEs...");
2889
- let ides = [];
2890
- try {
2891
- ides = deps.detectIDEs();
2892
- } catch {
2893
- }
2894
- const detected = ides.filter((ide) => ide.detected);
2895
- if (detected.length === 0) {
2896
- deps.log(" \u25CB Nenhum IDE detectado");
2897
- deps.log(" \u2192 Use tostudy diretamente no terminal\n");
2898
- } else {
2899
- for (const ide of detected) {
2900
- deps.log(` \u2713 ${ide.name} detectado`);
2901
- }
2902
- deps.log("");
2903
- }
2904
- deps.log(" 4. Instalando /tostudy...");
2905
- const cwd = process.cwd();
2906
- const installedPlatforms = [];
2907
- for (const ide of detected) {
2908
- const platform2 = ideToPlatform(ide.name);
2909
- if (platform2) {
2910
- const files = deps.installUniversalCommand(platform2, cwd);
2911
- installedPlatforms.push(...files);
2912
- }
2913
- }
2914
- if (opts.ide) {
2915
- const platform2 = ideToPlatform(opts.ide) ?? opts.ide;
2916
- const files = deps.installUniversalCommand(platform2, cwd);
2917
- installedPlatforms.push(...files);
2918
- }
2919
- if (installedPlatforms.length === 0) {
2920
- const files = deps.installUniversalCommand("generic", cwd);
2921
- installedPlatforms.push(...files);
2350
+ }
2351
+ });
2352
+ try {
2353
+ const { code } = await serverPromise;
2354
+ const res = await fetch(`${apiUrl}/api/cli/auth/exchange`, {
2355
+ method: "POST",
2356
+ headers: { "Content-Type": "application/json" },
2357
+ body: JSON.stringify({ code })
2358
+ });
2359
+ if (!res.ok) {
2360
+ const body = await res.json();
2361
+ error(body.error ?? "Falha na autentica\xE7\xE3o");
2362
+ }
2363
+ const { token: token2, refreshToken, sessionId, userId, userName, expiresAt } = await res.json();
2364
+ await saveSession({ token: token2, refreshToken, sessionId, userId, userName, expiresAt, apiUrl });
2365
+ console.log(`
2366
+ \u2713 Logado como ${userName}`);
2367
+ try {
2368
+ const { detectIDEs: detectIDEs2 } = await Promise.resolve().then(() => (init_ide_detector(), ide_detector_exports));
2369
+ const { installUniversalCommand: installUniversalCommand2 } = await Promise.resolve().then(() => (init_instruction_files(), instruction_files_exports));
2370
+ const ides = detectIDEs2();
2371
+ const detected = ides.filter((ide) => ide.detected);
2372
+ if (detected.length > 0) {
2373
+ console.log("");
2374
+ for (const ide of detected) {
2375
+ console.log(` \u2713 ${ide.name} detectado`);
2376
+ }
2377
+ const cwd = process.cwd();
2378
+ const installedFiles = [];
2379
+ for (const ide of detected) {
2380
+ const platform2 = ideToPlatform(ide.name);
2381
+ if (platform2) {
2382
+ const files = installUniversalCommand2(platform2, cwd);
2383
+ installedFiles.push(...files);
2384
+ }
2385
+ }
2386
+ if (installedFiles.length > 0) {
2387
+ console.log("");
2388
+ for (const file2 of [...new Set(installedFiles)]) {
2389
+ console.log(` \u2713 Instalado: ${file2}`);
2390
+ }
2391
+ }
2392
+ }
2393
+ } catch {
2394
+ }
2395
+ try {
2396
+ const data = createHttpProvider(apiUrl, token2);
2397
+ const courses3 = await listCourses({ userId }, { data, logger: logger3 });
2398
+ if (courses3.length > 0) {
2399
+ console.log(`
2400
+ \u2713 ${courses3.length} curso(s) matriculado(s)`);
2401
+ } else {
2402
+ console.log(`
2403
+ \u2192 Nenhum curso matriculado. Acesse tostudy.ai`);
2404
+ }
2405
+ } catch {
2406
+ }
2407
+ console.log("");
2408
+ console.log(" \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");
2409
+ console.log(" Pronto! Abra seu IDE e digite /tostudy");
2410
+ console.log("");
2411
+ } catch (err) {
2412
+ const msg = err instanceof Error ? err.message : "Login falhou";
2413
+ error(msg);
2414
+ }
2415
+ });
2922
2416
  }
2923
- for (const file2 of [...new Set(installedPlatforms)]) {
2924
- deps.log(` \u2713 ${file2}`);
2417
+ });
2418
+
2419
+ // src/commands/logout.ts
2420
+ import { Command as Command2 } from "commander";
2421
+ var logoutCommand;
2422
+ var init_logout = __esm({
2423
+ "src/commands/logout.ts"() {
2424
+ "use strict";
2425
+ init_session();
2426
+ logoutCommand = new Command2("logout").description("Remove token local").action(async () => {
2427
+ await clearSession();
2428
+ console.log("\n Deslogado com sucesso.\n");
2429
+ });
2925
2430
  }
2926
- deps.log("");
2927
- if (opts.mcp) {
2928
- deps.log(" 5. Configurando MCP...");
2929
- try {
2930
- const { token: token2 } = await deps.exchangeCliSessionForMcpToken(session);
2931
- await deps.runMcpSetup(session, token2);
2932
- deps.log(" \u2713 MCP configurado\n");
2933
- } catch {
2934
- deps.log(" \u25CB MCP n\xE3o configurado (opcional)\n");
2431
+ });
2432
+
2433
+ // src/installer/mcp-setup.ts
2434
+ import { spawn } from "node:child_process";
2435
+ async function exchangeCliSessionForMcpToken(session, fetchImpl = fetch) {
2436
+ const res = await fetchImpl(`${session.apiUrl}/api/mcp/token`, {
2437
+ method: "POST",
2438
+ headers: {
2439
+ Authorization: `Bearer ${session.token}`
2935
2440
  }
2441
+ });
2442
+ if (!res.ok) {
2443
+ const body = await res.json().catch(() => ({}));
2444
+ throw new Error(body.error ?? `Falha ao obter token MCP (${res.status})`);
2936
2445
  }
2937
- const activeCourse = await deps.getActiveCourse();
2938
- const hasClaudeCmd = existsSync4(join3(cwd, ".claude", "commands", "tostudy.md"));
2939
- const hasCursorRule = existsSync4(join3(cwd, ".cursor", "rules", "tostudy.mdc"));
2940
- const hasMcpConfig = existsSync4(join3(cwd, ".claude", "claude_desktop_config.json")) || existsSync4(join3(cwd, ".mcp.json"));
2941
- const hasClaudeIDE = detected.some((ide) => ide.name.toLowerCase().includes("claude"));
2942
- deps.log(" \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");
2943
- deps.log(" Setup completo!\n");
2944
- if (activeCourse) {
2945
- deps.log(` Curso ativo: ${activeCourse.courseTitle}`);
2946
- } else {
2947
- deps.log(" Nenhum curso ativo \u2014 rode: tostudy courses");
2948
- }
2949
- if (hasClaudeCmd) {
2950
- deps.log(" \u2192 No Claude Code, digite: /tostudy");
2951
- } else if (hasCursorRule) {
2952
- deps.log(" \u2192 No Cursor, o tutor \xE9 ativado automaticamente");
2953
- } else {
2954
- deps.log(" \u2192 Abra seu IDE e o tutor estar\xE1 dispon\xEDvel");
2955
- }
2956
- if (hasClaudeIDE && !hasMcpConfig && !opts.mcp) {
2957
- deps.log("\n \u{1F4A1} Dica: rode tostudy setup --mcp para habilitar ferramentas avan\xE7adas");
2446
+ return res.json();
2447
+ }
2448
+ async function runMcpSetup(session, token2, spawnImpl = spawn) {
2449
+ const command = process.platform === "win32" ? "npx.cmd" : "npx";
2450
+ await new Promise((resolve, reject) => {
2451
+ const child = spawnImpl(command, ["-y", "@tostudy-ai/mcp-setup", "--url", session.apiUrl], {
2452
+ stdio: "inherit",
2453
+ env: {
2454
+ ...process.env,
2455
+ TOSTUDY_API_KEY: token2
2456
+ }
2457
+ });
2458
+ child.once("error", reject);
2459
+ child.once("exit", (code) => {
2460
+ if (code === 0) {
2461
+ resolve();
2462
+ return;
2463
+ }
2464
+ reject(new Error(`MCP setup failed with exit code ${code ?? "unknown"}`));
2465
+ });
2466
+ });
2467
+ }
2468
+ var init_mcp_setup = __esm({
2469
+ "src/installer/mcp-setup.ts"() {
2470
+ "use strict";
2958
2471
  }
2959
- deps.log("");
2472
+ });
2473
+
2474
+ // src/commands/setup.ts
2475
+ import { Command as Command3 } from "commander";
2476
+ async function runSetup(_opts, deps = defaultDeps) {
2477
+ deps.log("\n \u26A0\uFE0F `tostudy setup` foi incorporado ao `tostudy login`.");
2478
+ deps.log(" Rode `tostudy login` para configurar seu ambiente.\n");
2960
2479
  }
2961
2480
  async function runSetupMcpSubcommand() {
2962
2481
  console.log("\n ToStudy MCP Setup\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
@@ -2973,23 +2492,12 @@ var defaultDeps, setupCommand;
2973
2492
  var init_setup = __esm({
2974
2493
  "src/commands/setup.ts"() {
2975
2494
  "use strict";
2976
- init_node_detector();
2977
- init_ide_detector();
2978
2495
  init_session();
2979
2496
  init_mcp_setup();
2980
- init_instruction_files();
2981
2497
  defaultDeps = {
2982
- detectNode,
2983
- detectIDEs,
2984
- getSession,
2985
- getActiveCourse,
2986
- exchangeCliSessionForMcpToken,
2987
- runMcpSetup,
2988
- installUniversalCommand,
2989
2498
  log: (message = "") => {
2990
2499
  console.log(message);
2991
- },
2992
- exit: (code) => process.exit(code)
2500
+ }
2993
2501
  };
2994
2502
  setupCommand = new Command3("setup").description("Configura ambiente para estudar").option("--quick", "Setup r\xE1pido (sem wizard)").option("--ide <ide>", "Configura IDE espec\xEDfica").option("--mcp", "Configura o MCP nas IDEs detectadas usando a sess\xE3o atual do CLI").action(async (opts) => {
2995
2503
  await runSetup(opts);
@@ -3000,6 +2508,31 @@ var init_setup = __esm({
3000
2508
  }
3001
2509
  });
3002
2510
 
2511
+ // src/installer/node-detector.ts
2512
+ import { execFileSync as execFileSync2 } from "node:child_process";
2513
+ function detectNode() {
2514
+ try {
2515
+ const version3 = execFileSync2("node", ["--version"], {
2516
+ encoding: "utf-8"
2517
+ }).trim();
2518
+ let nodePath = null;
2519
+ try {
2520
+ const whichCmd = process.platform === "win32" ? "where" : "which";
2521
+ nodePath = execFileSync2(whichCmd, ["node"], { encoding: "utf-8" }).trim();
2522
+ } catch {
2523
+ }
2524
+ const major = parseInt(version3.replace("v", ""), 10);
2525
+ return { installed: true, version: version3, meetsMinimum: major >= 18, path: nodePath };
2526
+ } catch {
2527
+ return { installed: false, version: null, meetsMinimum: false, path: null };
2528
+ }
2529
+ }
2530
+ var init_node_detector = __esm({
2531
+ "src/installer/node-detector.ts"() {
2532
+ "use strict";
2533
+ }
2534
+ });
2535
+
3003
2536
  // src/update-checker.ts
3004
2537
  var update_checker_exports = {};
3005
2538
  __export(update_checker_exports, {
@@ -3008,7 +2541,7 @@ __export(update_checker_exports, {
3008
2541
  fetchLatestVersion: () => fetchLatestVersion,
3009
2542
  isNewerVersion: () => isNewerVersion
3010
2543
  });
3011
- import fs5 from "node:fs";
2544
+ import fs4 from "node:fs";
3012
2545
  import path6 from "node:path";
3013
2546
  import os6 from "node:os";
3014
2547
  function getConfigDir2() {
@@ -3020,8 +2553,8 @@ function getConfigDir2() {
3020
2553
  function readCache() {
3021
2554
  try {
3022
2555
  const p = path6.join(getConfigDir2(), CACHE_FILE);
3023
- if (!fs5.existsSync(p)) return null;
3024
- return JSON.parse(fs5.readFileSync(p, "utf-8"));
2556
+ if (!fs4.existsSync(p)) return null;
2557
+ return JSON.parse(fs4.readFileSync(p, "utf-8"));
3025
2558
  } catch {
3026
2559
  return null;
3027
2560
  }
@@ -3029,8 +2562,8 @@ function readCache() {
3029
2562
  function writeCache(cache) {
3030
2563
  try {
3031
2564
  const dir = getConfigDir2();
3032
- fs5.mkdirSync(dir, { recursive: true });
3033
- fs5.writeFileSync(path6.join(dir, CACHE_FILE), JSON.stringify(cache));
2565
+ fs4.mkdirSync(dir, { recursive: true });
2566
+ fs4.writeFileSync(path6.join(dir, CACHE_FILE), JSON.stringify(cache));
3034
2567
  } catch {
3035
2568
  }
3036
2569
  }
@@ -3099,7 +2632,7 @@ var init_update_checker = __esm({
3099
2632
  });
3100
2633
 
3101
2634
  // src/learner-brief/cache.ts
3102
- import fs6 from "node:fs";
2635
+ import fs5 from "node:fs";
3103
2636
  import os7 from "node:os";
3104
2637
  import path7 from "node:path";
3105
2638
  function getDefaultConfigDir() {
@@ -3114,9 +2647,9 @@ function resolveCachePath(configDir) {
3114
2647
  async function readBriefCache(configDir) {
3115
2648
  const dir = configDir ?? getDefaultConfigDir();
3116
2649
  const p = resolveCachePath(dir);
3117
- if (!fs6.existsSync(p)) return null;
2650
+ if (!fs5.existsSync(p)) return null;
3118
2651
  try {
3119
- const raw = fs6.readFileSync(p, "utf-8");
2652
+ const raw = fs5.readFileSync(p, "utf-8");
3120
2653
  return JSON.parse(raw);
3121
2654
  } catch {
3122
2655
  return null;
@@ -3124,8 +2657,8 @@ async function readBriefCache(configDir) {
3124
2657
  }
3125
2658
  async function writeBriefCache(configDir, cache) {
3126
2659
  const dir = configDir ?? getDefaultConfigDir();
3127
- fs6.mkdirSync(dir, { recursive: true });
3128
- fs6.writeFileSync(resolveCachePath(dir), JSON.stringify(cache, null, 2), { mode: 384 });
2660
+ fs5.mkdirSync(dir, { recursive: true });
2661
+ fs5.writeFileSync(resolveCachePath(dir), JSON.stringify(cache, null, 2), { mode: 384 });
3129
2662
  }
3130
2663
  var init_cache = __esm({
3131
2664
  "src/learner-brief/cache.ts"() {
@@ -3134,7 +2667,7 @@ var init_cache = __esm({
3134
2667
  });
3135
2668
 
3136
2669
  // src/tutor-persona/cache.ts
3137
- import fs7 from "node:fs";
2670
+ import fs6 from "node:fs";
3138
2671
  import os8 from "node:os";
3139
2672
  import path8 from "node:path";
3140
2673
  function getDefaultConfigDir2() {
@@ -3148,16 +2681,16 @@ function resolveCachePath2(configDir) {
3148
2681
  }
3149
2682
  function readAll(configDir) {
3150
2683
  const p = resolveCachePath2(configDir);
3151
- if (!fs7.existsSync(p)) return {};
2684
+ if (!fs6.existsSync(p)) return {};
3152
2685
  try {
3153
- return JSON.parse(fs7.readFileSync(p, "utf-8"));
2686
+ return JSON.parse(fs6.readFileSync(p, "utf-8"));
3154
2687
  } catch {
3155
2688
  return {};
3156
2689
  }
3157
2690
  }
3158
2691
  function writeAll(configDir, cache) {
3159
- fs7.mkdirSync(configDir, { recursive: true });
3160
- fs7.writeFileSync(resolveCachePath2(configDir), JSON.stringify(cache, null, 2), { mode: 384 });
2692
+ fs6.mkdirSync(configDir, { recursive: true });
2693
+ fs6.writeFileSync(resolveCachePath2(configDir), JSON.stringify(cache, null, 2), { mode: 384 });
3161
2694
  }
3162
2695
  async function readPersonaCacheForCourse(configDir, courseId) {
3163
2696
  const dir = configDir ?? getDefaultConfigDir2();
@@ -3185,6 +2718,7 @@ var init_doctor = __esm({
3185
2718
  init_node_detector();
3186
2719
  init_ide_detector();
3187
2720
  init_session();
2721
+ init_workspace_state();
3188
2722
  init_formatter();
3189
2723
  init_cli();
3190
2724
  init_update_checker();
@@ -3278,7 +2812,8 @@ var init_doctor = __esm({
3278
2812
  checks["briefCache"] = briefCacheCheck;
3279
2813
  let personaCacheCheck;
3280
2814
  try {
3281
- const active = await getActiveCourse();
2815
+ const wsResult = await findWorkspaceState();
2816
+ const active = wsResult?.state ?? null;
3282
2817
  if (active) {
3283
2818
  const personaEntry = await readPersonaCacheForCourse(void 0, active.courseId);
3284
2819
  if (personaEntry) {
@@ -3416,6 +2951,97 @@ var init_courses2 = __esm({
3416
2951
  }
3417
2952
  });
3418
2953
 
2954
+ // src/workspace/resolve.ts
2955
+ import fs7 from "node:fs/promises";
2956
+ import path9 from "node:path";
2957
+ import os9 from "node:os";
2958
+ function courseSlug(title) {
2959
+ return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
2960
+ }
2961
+ async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
2962
+ const slug = courseSlug(courseTitle);
2963
+ const candidate = path9.join(basePath, slug);
2964
+ try {
2965
+ await fs7.access(path9.join(candidate, ".ana-config.json"));
2966
+ return { found: true, workspacePath: candidate, source: "default" };
2967
+ } catch {
2968
+ return { found: false, workspacePath: null };
2969
+ }
2970
+ }
2971
+ async function isCwdWorkspace(cwd = process.cwd()) {
2972
+ return await resolveCwdWorkspacePath(cwd) !== null;
2973
+ }
2974
+ async function resolveCwdWorkspacePath(cwd = process.cwd()) {
2975
+ const tostudyDir = path9.join(cwd, ".tostudy");
2976
+ try {
2977
+ const stat = await fs7.stat(tostudyDir);
2978
+ if (stat.isDirectory()) return tostudyDir;
2979
+ } catch {
2980
+ }
2981
+ try {
2982
+ await fs7.access(path9.join(cwd, ".ana-config.json"));
2983
+ return cwd;
2984
+ } catch {
2985
+ return null;
2986
+ }
2987
+ }
2988
+ async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os9.homedir()) {
2989
+ const home = path9.resolve(homeDir);
2990
+ let current = path9.resolve(startCwd);
2991
+ while (true) {
2992
+ if (current === home) return null;
2993
+ const direct = await resolveCwdWorkspacePath(current);
2994
+ if (direct) return direct;
2995
+ const parent = path9.dirname(current);
2996
+ if (parent === current) return null;
2997
+ current = parent;
2998
+ }
2999
+ }
3000
+ function resolveVaultPath(workspacePath, slug) {
3001
+ const base = path9.basename(workspacePath) === ".tostudy" ? path9.dirname(workspacePath) : workspacePath;
3002
+ return path9.join(base, `vault-${slug}`);
3003
+ }
3004
+ async function findExistingVault(workspacePath, slug) {
3005
+ const slugged = resolveVaultPath(workspacePath, slug);
3006
+ try {
3007
+ await fs7.access(path9.join(slugged, ".ana-vault.json"));
3008
+ return slugged;
3009
+ } catch {
3010
+ }
3011
+ const legacy = path9.join(workspacePath, "vault");
3012
+ try {
3013
+ await fs7.access(path9.join(legacy, ".ana-vault.json"));
3014
+ return legacy;
3015
+ } catch {
3016
+ return null;
3017
+ }
3018
+ }
3019
+ async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE, homeDir = os9.homedir()) {
3020
+ const cwdWorkspace = await findCwdWorkspaceUpwards(cwd, homeDir);
3021
+ if (cwdWorkspace) {
3022
+ return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
3023
+ }
3024
+ if (storedPath) {
3025
+ try {
3026
+ await fs7.access(path9.join(storedPath, ".ana-config.json"));
3027
+ return { found: true, workspacePath: storedPath, source: "stored" };
3028
+ } catch {
3029
+ }
3030
+ }
3031
+ const result = await resolveWorkspace(courseTitle, defaultBasePath);
3032
+ if (result.found) {
3033
+ return { ...result, source: "default" };
3034
+ }
3035
+ return { found: false, workspacePath: null };
3036
+ }
3037
+ var DEFAULT_BASE;
3038
+ var init_resolve = __esm({
3039
+ "src/workspace/resolve.ts"() {
3040
+ "use strict";
3041
+ DEFAULT_BASE = path9.join(os9.homedir(), "study");
3042
+ }
3043
+ });
3044
+
3419
3045
  // src/onboarding/status.ts
3420
3046
  async function getCourseOnboardingStatus(activeCourse, configDir, cwd = process.cwd()) {
3421
3047
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
@@ -3441,7 +3067,7 @@ var init_status = __esm({
3441
3067
  });
3442
3068
 
3443
3069
  // src/learner-brief/api.ts
3444
- async function apiFetch3(url2, token2, init) {
3070
+ async function apiFetch2(url2, token2, init) {
3445
3071
  const response = await fetch(url2, {
3446
3072
  method: init?.method ?? "GET",
3447
3073
  body: init?.body,
@@ -3458,14 +3084,14 @@ async function apiFetch3(url2, token2, init) {
3458
3084
  return body;
3459
3085
  }
3460
3086
  async function fetchLearnerBrief(input2) {
3461
- const response = await apiFetch3(
3087
+ const response = await apiFetch2(
3462
3088
  `${input2.apiUrl}/api/cli/student/learner-brief`,
3463
3089
  input2.token
3464
3090
  );
3465
3091
  return response.brief;
3466
3092
  }
3467
3093
  async function upsertLearnerBrief(input2) {
3468
- const response = await apiFetch3(
3094
+ const response = await apiFetch2(
3469
3095
  `${input2.apiUrl}/api/cli/student/learner-brief`,
3470
3096
  input2.token,
3471
3097
  {
@@ -3475,7 +3101,7 @@ async function upsertLearnerBrief(input2) {
3475
3101
  );
3476
3102
  return response.brief;
3477
3103
  }
3478
- var init_api2 = __esm({
3104
+ var init_api = __esm({
3479
3105
  "src/learner-brief/api.ts"() {
3480
3106
  "use strict";
3481
3107
  init_http2();
@@ -3568,13 +3194,60 @@ async function fetchTutorPersonality(input2) {
3568
3194
  }
3569
3195
  return body.personality ?? null;
3570
3196
  }
3571
- var init_api3 = __esm({
3197
+ var init_api2 = __esm({
3572
3198
  "src/tutor-persona/api.ts"() {
3573
3199
  "use strict";
3574
3200
  init_http2();
3575
3201
  }
3576
3202
  });
3577
3203
 
3204
+ // src/onboarding/api.ts
3205
+ async function apiFetch3(url2, token2, init) {
3206
+ const response = await fetch(url2, {
3207
+ ...init,
3208
+ headers: {
3209
+ "Content-Type": "application/json",
3210
+ Authorization: `Bearer ${token2}`,
3211
+ ...init?.headers
3212
+ }
3213
+ });
3214
+ const body = await response.json();
3215
+ if (!response.ok) {
3216
+ throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
3217
+ }
3218
+ return body;
3219
+ }
3220
+ async function getRemoteEnrollmentOnboarding(input2) {
3221
+ const url2 = new URL(`${input2.apiUrl}/api/cli/onboarding`);
3222
+ url2.searchParams.set("enrollmentId", input2.enrollmentId);
3223
+ const response = await apiFetch3(
3224
+ url2.toString(),
3225
+ input2.token
3226
+ );
3227
+ return response.onboarding;
3228
+ }
3229
+ async function saveRemoteEnrollmentOnboarding(input2) {
3230
+ const response = await apiFetch3(
3231
+ `${input2.apiUrl}/api/cli/onboarding`,
3232
+ input2.token,
3233
+ {
3234
+ method: "POST",
3235
+ body: JSON.stringify({
3236
+ enrollmentId: input2.enrollmentId,
3237
+ learnerBrief: input2.learnerBrief,
3238
+ learnerProfile: input2.learnerProfile
3239
+ })
3240
+ }
3241
+ );
3242
+ return response.onboarding;
3243
+ }
3244
+ var init_api3 = __esm({
3245
+ "src/onboarding/api.ts"() {
3246
+ "use strict";
3247
+ init_http2();
3248
+ }
3249
+ });
3250
+
3578
3251
  // src/instruction-pipeline.ts
3579
3252
  async function defaultWriteFiles(_cwd, _courseMeta, _content) {
3580
3253
  throw new Error(
@@ -3702,13 +3375,27 @@ async function resolveAndGenerate(input2, options = {}, context) {
3702
3375
  } catch (err) {
3703
3376
  logger5.warn("Failed to write workspace marker", { err });
3704
3377
  }
3378
+ if (markerPath) {
3379
+ try {
3380
+ await updateWorkspaceState(context.cwd, {
3381
+ courseTags: courseMeta.courseTags,
3382
+ courseLevel: courseMeta.courseLevel,
3383
+ lastInitCourseId: input2.courseId
3384
+ });
3385
+ } catch (err) {
3386
+ logger5.warn("Failed to update workspace state v2 fields", {
3387
+ error: err instanceof Error ? err.message : String(err)
3388
+ });
3389
+ }
3390
+ }
3705
3391
  return {
3706
3392
  wroteFiles,
3707
3393
  markerPath,
3708
3394
  usedT0: tutorPersona !== null,
3709
3395
  usedT1: studentBrief !== null,
3710
3396
  usedT2: enrollmentProfile !== null,
3711
- bootstrapped
3397
+ bootstrapped,
3398
+ renderedInstruction: content
3712
3399
  };
3713
3400
  }
3714
3401
  var logger5, defaultDeps2;
@@ -3717,12 +3404,13 @@ var init_instruction_pipeline = __esm({
3717
3404
  "use strict";
3718
3405
  init_src();
3719
3406
  init_workspace_marker();
3720
- init_api2();
3407
+ init_workspace_state();
3408
+ init_api();
3721
3409
  init_cache();
3722
3410
  init_bootstrap();
3723
- init_api3();
3411
+ init_api2();
3724
3412
  init_cache2();
3725
- init_api();
3413
+ init_api3();
3726
3414
  init_session();
3727
3415
  init_instruction_template_v3();
3728
3416
  logger5 = createLogger("cli:instruction-pipeline");
@@ -3761,11 +3449,27 @@ async function fetchCourseMetaViaHttp(input2) {
3761
3449
  error: err instanceof Error ? err.message : String(err)
3762
3450
  });
3763
3451
  }
3452
+ let courseTags;
3453
+ let courseLevel;
3454
+ try {
3455
+ const courses3 = await listCourses({ userId: session.userId }, coreDeps);
3456
+ const match = courses3.find((c) => c.courseId === courseId);
3457
+ if (match) {
3458
+ courseTags = match.tags;
3459
+ courseLevel = match.level;
3460
+ }
3461
+ } catch (err) {
3462
+ logger6.debug("listCourses failed \u2014 skipping tags/level", {
3463
+ error: err instanceof Error ? err.message : String(err)
3464
+ });
3465
+ }
3764
3466
  return {
3765
3467
  courseId: detail.courseId,
3766
3468
  courseTitle: detail.courseTitle,
3767
3469
  slug: slugify(detail.courseTitle),
3768
3470
  courseDescription: detail.courseDescription,
3471
+ courseTags,
3472
+ courseLevel,
3769
3473
  progress: {
3770
3474
  percent: detail.progress,
3771
3475
  moduleCount: detail.moduleCount,
@@ -3813,7 +3517,7 @@ var init_pipeline_deps = __esm({
3813
3517
 
3814
3518
  // src/commands/select.ts
3815
3519
  import { Command as Command6 } from "commander";
3816
- import os9 from "node:os";
3520
+ import os10 from "node:os";
3817
3521
  var logger7, selectCommand;
3818
3522
  var init_select = __esm({
3819
3523
  "src/commands/select.ts"() {
@@ -3822,6 +3526,8 @@ var init_select = __esm({
3822
3526
  init_courses();
3823
3527
  init_http2();
3824
3528
  init_session();
3529
+ init_workspace_marker();
3530
+ init_resolve();
3825
3531
  init_formatter();
3826
3532
  init_status();
3827
3533
  init_instruction_pipeline();
@@ -3849,14 +3555,12 @@ var init_select = __esm({
3849
3555
  enrollmentId = found.enrollmentId;
3850
3556
  }
3851
3557
  }
3852
- const matched = courses3.find((c) => c.courseId === courseId);
3853
3558
  const detail = await selectCourse({ userId: session.userId, courseId }, deps);
3854
- await setActiveCourse({
3559
+ await writeWorkspaceMarker(process.cwd(), {
3855
3560
  courseId: detail.courseId,
3856
- courseTitle: detail.courseTitle,
3857
3561
  enrollmentId,
3858
- courseTags: matched?.tags,
3859
- courseLevel: matched?.level
3562
+ slug: courseSlug(detail.courseTitle),
3563
+ courseTitle: detail.courseTitle
3860
3564
  });
3861
3565
  const activeCourseForStatus = {
3862
3566
  courseId: detail.courseId,
@@ -3890,13 +3594,14 @@ var init_select = __esm({
3890
3594
  markerPath: pipelineResult?.markerPath ?? null,
3891
3595
  usedT0: pipelineResult?.usedT0 ?? false,
3892
3596
  usedT1: pipelineResult?.usedT1 ?? false,
3893
- usedT2: pipelineResult?.usedT2 ?? false
3597
+ usedT2: pipelineResult?.usedT2 ?? false,
3598
+ instruction: pipelineResult?.renderedInstruction ?? null
3894
3599
  },
3895
3600
  { json: true }
3896
3601
  );
3897
3602
  } else {
3898
3603
  const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
3899
- const home = os9.homedir();
3604
+ const home = os10.homedir();
3900
3605
  const cwd = process.cwd();
3901
3606
  const namespacedPath = `${cwd}/.tostudy`;
3902
3607
  let wsLine;
@@ -3953,7 +3658,7 @@ var init_progress = __esm({
3953
3658
  progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
3954
3659
  try {
3955
3660
  const session = await requireSession();
3956
- const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
3661
+ const activeCourse = await requireActiveCourse();
3957
3662
  const driftWarning = await checkCourseDrift();
3958
3663
  if (driftWarning) process.stderr.write(driftWarning + "\n");
3959
3664
  const data = createHttpProvider(session.apiUrl, session.token);
@@ -4775,7 +4480,7 @@ var init_subquery = __esm({
4775
4480
  var version;
4776
4481
  var init_version = __esm({
4777
4482
  "../../node_modules/drizzle-orm/version.js"() {
4778
- version = "0.45.1";
4483
+ version = "0.45.2";
4779
4484
  }
4780
4485
  });
4781
4486
 
@@ -5216,7 +4921,7 @@ var init_sql = __esm({
5216
4921
  return new SQL([new StringChunk(str)]);
5217
4922
  }
5218
4923
  sql2.raw = raw;
5219
- function join4(chunks, separator) {
4924
+ function join3(chunks, separator) {
5220
4925
  const result = [];
5221
4926
  for (const [i, chunk] of chunks.entries()) {
5222
4927
  if (i > 0 && separator !== void 0) {
@@ -5226,7 +4931,7 @@ var init_sql = __esm({
5226
4931
  }
5227
4932
  return new SQL(result);
5228
4933
  }
5229
- sql2.join = join4;
4934
+ sql2.join = join3;
5230
4935
  function identifier(value) {
5231
4936
  return new Name(value);
5232
4937
  }
@@ -5518,7 +5223,7 @@ var init_query_promise = __esm({
5518
5223
  function mapResultRow(columns, row, joinsNotNullableMap) {
5519
5224
  const nullifyMap = {};
5520
5225
  const result = columns.reduce(
5521
- (result2, { path: path17, field }, columnIndex) => {
5226
+ (result2, { path: path19, field }, columnIndex) => {
5522
5227
  let decoder;
5523
5228
  if (is(field, Column)) {
5524
5229
  decoder = field;
@@ -5530,8 +5235,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
5530
5235
  decoder = field.sql.decoder;
5531
5236
  }
5532
5237
  let node = result2;
5533
- for (const [pathChunkIndex, pathChunk] of path17.entries()) {
5534
- if (pathChunkIndex < path17.length - 1) {
5238
+ for (const [pathChunkIndex, pathChunk] of path19.entries()) {
5239
+ if (pathChunkIndex < path19.length - 1) {
5535
5240
  if (!(pathChunk in node)) {
5536
5241
  node[pathChunk] = {};
5537
5242
  }
@@ -5539,8 +5244,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
5539
5244
  } else {
5540
5245
  const rawValue = row[columnIndex];
5541
5246
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
5542
- if (joinsNotNullableMap && is(field, Column) && path17.length === 2) {
5543
- const objectName = path17[0];
5247
+ if (joinsNotNullableMap && is(field, Column) && path19.length === 2) {
5248
+ const objectName = path19[0];
5544
5249
  if (!(objectName in nullifyMap)) {
5545
5250
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
5546
5251
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -9487,13 +9192,13 @@ function Subscribe(postgres2, options) {
9487
9192
  }
9488
9193
  }
9489
9194
  function handle(a, b2) {
9490
- const path17 = b2.relation.schema + "." + b2.relation.table;
9195
+ const path19 = b2.relation.schema + "." + b2.relation.table;
9491
9196
  call("*", a, b2);
9492
- call("*:" + path17, a, b2);
9493
- b2.relation.keys.length && call("*:" + path17 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9197
+ call("*:" + path19, a, b2);
9198
+ b2.relation.keys.length && call("*:" + path19 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9494
9199
  call(b2.command, a, b2);
9495
- call(b2.command + ":" + path17, a, b2);
9496
- b2.relation.keys.length && call(b2.command + ":" + path17 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9200
+ call(b2.command + ":" + path19, a, b2);
9201
+ b2.relation.keys.length && call(b2.command + ":" + path19 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
9497
9202
  }
9498
9203
  function pong() {
9499
9204
  const x2 = Buffer.alloc(34);
@@ -9606,8 +9311,8 @@ function parseEvent(x) {
9606
9311
  const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
9607
9312
  if (!xs)
9608
9313
  throw new Error("Malformed subscribe pattern: " + x);
9609
- const [, command, path17, key] = xs;
9610
- return (command || "*") + (path17 ? ":" + (path17.indexOf(".") === -1 ? "public." + path17 : path17) : "") + (key ? "=" + key : "");
9314
+ const [, command, path19, key] = xs;
9315
+ return (command || "*") + (path19 ? ":" + (path19.indexOf(".") === -1 ? "public." + path19 : path19) : "") + (key ? "=" + key : "");
9611
9316
  }
9612
9317
  var noop2;
9613
9318
  var init_subscribe = __esm({
@@ -9688,7 +9393,7 @@ var init_large = __esm({
9688
9393
  });
9689
9394
 
9690
9395
  // ../../node_modules/postgres/src/index.js
9691
- import os10 from "os";
9396
+ import os11 from "os";
9692
9397
  import fs8 from "fs";
9693
9398
  function Postgres(a, b2) {
9694
9399
  const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
@@ -9745,10 +9450,10 @@ function Postgres(a, b2) {
9745
9450
  });
9746
9451
  return query;
9747
9452
  }
9748
- function file2(path17, args = [], options2 = {}) {
9453
+ function file2(path19, args = [], options2 = {}) {
9749
9454
  arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
9750
9455
  const query = new Query([], args, (query2) => {
9751
- fs8.readFile(path17, "utf8", (err, string4) => {
9456
+ fs8.readFile(path19, "utf8", (err, string4) => {
9752
9457
  if (err)
9753
9458
  return query2.reject(err);
9754
9459
  query2.strings = [string4];
@@ -10066,7 +9771,7 @@ function parseUrl(url2) {
10066
9771
  }
10067
9772
  function osUsername() {
10068
9773
  try {
10069
- return os10.userInfo().username;
9774
+ return os11.userInfo().username;
10070
9775
  } catch (_) {
10071
9776
  return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
10072
9777
  }
@@ -10544,7 +10249,7 @@ var init_dialect = __esm({
10544
10249
  });
10545
10250
  }
10546
10251
  escapeName(name) {
10547
- return `"${name}"`;
10252
+ return `"${name.replace(/"/g, '""')}"`;
10548
10253
  }
10549
10254
  escapeParam(num) {
10550
10255
  return `$${num + 1}`;
@@ -11758,7 +11463,7 @@ var init_select3 = __esm({
11758
11463
  const baseTableName = this.tableName;
11759
11464
  const tableName = getTableLikeName(table);
11760
11465
  for (const item of extractUsedTable(table)) this.usedTables.add(item);
11761
- if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
11466
+ if (typeof tableName === "string" && this.config.joins?.some((join3) => join3.alias === tableName)) {
11762
11467
  throw new Error(`Alias "${tableName}" is already used in this query`);
11763
11468
  }
11764
11469
  if (!this.isPartialSelect) {
@@ -13285,7 +12990,7 @@ var init_update = __esm({
13285
12990
  createJoin(joinType) {
13286
12991
  return (table, on) => {
13287
12992
  const tableName = getTableLikeName(table);
13288
- if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
12993
+ if (typeof tableName === "string" && this.config.joins.some((join3) => join3.alias === tableName)) {
13289
12994
  throw new Error(`Alias "${tableName}" is already used in this query`);
13290
12995
  }
13291
12996
  if (typeof on === "function") {
@@ -13381,10 +13086,10 @@ var init_update = __esm({
13381
13086
  const fromFields = this.getTableLikeFields(this.config.from);
13382
13087
  fields[tableName] = fromFields;
13383
13088
  }
13384
- for (const join4 of this.config.joins) {
13385
- const tableName2 = getTableLikeName(join4.table);
13386
- if (typeof tableName2 === "string" && !is(join4.table, SQL)) {
13387
- const fromFields = this.getTableLikeFields(join4.table);
13089
+ for (const join3 of this.config.joins) {
13090
+ const tableName2 = getTableLikeName(join3.table);
13091
+ if (typeof tableName2 === "string" && !is(join3.table, SQL)) {
13092
+ const fromFields = this.getTableLikeFields(join3.table);
13388
13093
  fields[tableName2] = fromFields;
13389
13094
  }
13390
13095
  }
@@ -14327,12 +14032,12 @@ var init_session3 = __esm({
14327
14032
  init_tracing();
14328
14033
  init_utils();
14329
14034
  PostgresJsPreparedQuery = class extends PgPreparedQuery {
14330
- constructor(client, queryString, params, logger23, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14035
+ constructor(client, queryString, params, logger26, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
14331
14036
  super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
14332
14037
  this.client = client;
14333
14038
  this.queryString = queryString;
14334
14039
  this.params = params;
14335
- this.logger = logger23;
14040
+ this.logger = logger26;
14336
14041
  this.fields = fields;
14337
14042
  this._isResponseInArrayMode = _isResponseInArrayMode;
14338
14043
  this.customResultMapper = customResultMapper;
@@ -14473,11 +14178,11 @@ function construct(client, config2 = {}) {
14473
14178
  client.options.serializers["114"] = transparentParser;
14474
14179
  client.options.serializers["3802"] = transparentParser;
14475
14180
  const dialect = new PgDialect({ casing: config2.casing });
14476
- let logger23;
14181
+ let logger26;
14477
14182
  if (config2.logger === true) {
14478
- logger23 = new DefaultLogger();
14183
+ logger26 = new DefaultLogger();
14479
14184
  } else if (config2.logger !== false) {
14480
- logger23 = config2.logger;
14185
+ logger26 = config2.logger;
14481
14186
  }
14482
14187
  let schema;
14483
14188
  if (config2.schema) {
@@ -14491,7 +14196,7 @@ function construct(client, config2 = {}) {
14491
14196
  tableNamesMap: tablesConfig.tableNamesMap
14492
14197
  };
14493
14198
  }
14494
- const session = new PostgresJsSession(client, dialect, schema, { logger: logger23, cache: config2.cache });
14199
+ const session = new PostgresJsSession(client, dialect, schema, { logger: logger26, cache: config2.cache });
14495
14200
  const db2 = new PostgresJsDatabase(dialect, session, schema);
14496
14201
  db2.$client = client;
14497
14202
  db2.$cache = config2.cache;
@@ -14806,10 +14511,10 @@ function mergeDefs(...defs) {
14806
14511
  function cloneDef(schema) {
14807
14512
  return mergeDefs(schema._zod.def);
14808
14513
  }
14809
- function getElementAtPath(obj, path17) {
14810
- if (!path17)
14514
+ function getElementAtPath(obj, path19) {
14515
+ if (!path19)
14811
14516
  return obj;
14812
- return path17.reduce((acc, key) => acc?.[key], obj);
14517
+ return path19.reduce((acc, key) => acc?.[key], obj);
14813
14518
  }
14814
14519
  function promiseAllObject(promisesObj) {
14815
14520
  const keys = Object.keys(promisesObj);
@@ -15121,11 +14826,11 @@ function aborted(x, startIndex = 0) {
15121
14826
  }
15122
14827
  return false;
15123
14828
  }
15124
- function prefixIssues(path17, issues) {
14829
+ function prefixIssues(path19, issues) {
15125
14830
  return issues.map((iss) => {
15126
14831
  var _a2;
15127
14832
  (_a2 = iss).path ?? (_a2.path = []);
15128
- iss.path.unshift(path17);
14833
+ iss.path.unshift(path19);
15129
14834
  return iss;
15130
14835
  });
15131
14836
  }
@@ -15367,7 +15072,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
15367
15072
  }
15368
15073
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
15369
15074
  const result = { errors: [] };
15370
- const processError = (error50, path17 = []) => {
15075
+ const processError = (error50, path19 = []) => {
15371
15076
  var _a2, _b;
15372
15077
  for (const issue2 of error50.issues) {
15373
15078
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -15377,7 +15082,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
15377
15082
  } else if (issue2.code === "invalid_element") {
15378
15083
  processError({ issues: issue2.issues }, issue2.path);
15379
15084
  } else {
15380
- const fullpath = [...path17, ...issue2.path];
15085
+ const fullpath = [...path19, ...issue2.path];
15381
15086
  if (fullpath.length === 0) {
15382
15087
  result.errors.push(mapper(issue2));
15383
15088
  continue;
@@ -15409,8 +15114,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
15409
15114
  }
15410
15115
  function toDotPath(_path) {
15411
15116
  const segs = [];
15412
- const path17 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15413
- for (const seg of path17) {
15117
+ const path19 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
15118
+ for (const seg of path19) {
15414
15119
  if (typeof seg === "number")
15415
15120
  segs.push(`[${seg}]`);
15416
15121
  else if (typeof seg === "symbol")
@@ -28104,13 +27809,13 @@ function resolveRef(ref, ctx) {
28104
27809
  if (!ref.startsWith("#")) {
28105
27810
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
28106
27811
  }
28107
- const path17 = ref.slice(1).split("/").filter(Boolean);
28108
- if (path17.length === 0) {
27812
+ const path19 = ref.slice(1).split("/").filter(Boolean);
27813
+ if (path19.length === 0) {
28109
27814
  return ctx.rootSchema;
28110
27815
  }
28111
27816
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
28112
- if (path17[0] === defsKey) {
28113
- const key = path17[1];
27817
+ if (path19[0] === defsKey) {
27818
+ const key = path19[1];
28114
27819
  if (!key || !ctx.defs[key]) {
28115
27820
  throw new Error(`Reference not found: ${ref}`);
28116
27821
  }
@@ -41647,7 +41352,7 @@ import { Command as Command8 } from "commander";
41647
41352
  async function runStart(opts, deps = defaultDeps3) {
41648
41353
  try {
41649
41354
  const session = await deps.requireSession();
41650
- const activeCourse = await deps.requireActiveCourse(void 0, { respectWorkspace: true });
41355
+ const activeCourse = await deps.requireActiveCourse();
41651
41356
  const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
41652
41357
  if (!onboarding.initReady) {
41653
41358
  deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
@@ -41660,7 +41365,12 @@ async function runStart(opts, deps = defaultDeps3) {
41660
41365
  { enrollmentId: activeCourse.enrollmentId },
41661
41366
  { data, logger: deps.logger }
41662
41367
  );
41663
- await deps.setActiveCourse({ ...activeCourse, currentLessonId: moduleData.firstLesson.id });
41368
+ const ws = await deps.findWorkspaceState();
41369
+ if (ws)
41370
+ await deps.updateWorkspaceState(ws.workspacePath, {
41371
+ currentLessonId: moduleData.firstLesson.id,
41372
+ currentModuleId: moduleData.module.id
41373
+ });
41664
41374
  if (opts.json) {
41665
41375
  deps.output(moduleData, { json: true });
41666
41376
  } else {
@@ -41684,6 +41394,7 @@ var init_start = __esm({
41684
41394
  init_lessons2();
41685
41395
  init_http2();
41686
41396
  init_session();
41397
+ init_workspace_state();
41687
41398
  init_status();
41688
41399
  init_formatter();
41689
41400
  logger9 = createLogger("cli:start");
@@ -41694,7 +41405,8 @@ var init_start = __esm({
41694
41405
  getCourseOnboardingStatus,
41695
41406
  createHttpProvider,
41696
41407
  startModule,
41697
- setActiveCourse,
41408
+ findWorkspaceState,
41409
+ updateWorkspaceState,
41698
41410
  formatModuleStart,
41699
41411
  output,
41700
41412
  error,
@@ -41717,6 +41429,7 @@ var init_start_next = __esm({
41717
41429
  init_lessons2();
41718
41430
  init_http2();
41719
41431
  init_session();
41432
+ init_workspace_state();
41720
41433
  init_formatter();
41721
41434
  logger10 = createLogger("cli:start-next");
41722
41435
  startNextCommand = new Command9("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
@@ -41728,7 +41441,12 @@ var init_start_next = __esm({
41728
41441
  const data = createHttpProvider(session.apiUrl, session.token);
41729
41442
  const deps = { data, logger: logger10 };
41730
41443
  const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
41731
- await setActiveCourse({ ...activeCourse, currentLessonId: moduleData.firstLesson.id });
41444
+ const ws = await findWorkspaceState();
41445
+ if (ws)
41446
+ await updateWorkspaceState(ws.workspacePath, {
41447
+ currentLessonId: moduleData.firstLesson.id,
41448
+ currentModuleId: moduleData.module.id
41449
+ });
41732
41450
  if (opts.json) {
41733
41451
  output(moduleData, { json: true });
41734
41452
  } else {
@@ -41741,6 +41459,7 @@ var init_start_next = __esm({
41741
41459
  process.exit(0);
41742
41460
  }
41743
41461
  const msg = err instanceof Error ? err.message : String(err);
41462
+ if (opts.json) jsonError(msg);
41744
41463
  error(msg);
41745
41464
  }
41746
41465
  });
@@ -41757,12 +41476,13 @@ var init_next = __esm({
41757
41476
  init_lessons2();
41758
41477
  init_http2();
41759
41478
  init_session();
41479
+ init_workspace_state();
41760
41480
  init_formatter();
41761
41481
  logger11 = createLogger("cli:next");
41762
41482
  nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
41763
41483
  try {
41764
41484
  const session = await requireSession();
41765
- const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41485
+ const activeCourse = await requireActiveCourse();
41766
41486
  const driftWarning = await checkCourseDrift();
41767
41487
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41768
41488
  const data = createHttpProvider(session.apiUrl, session.token);
@@ -41771,7 +41491,12 @@ var init_next = __esm({
41771
41491
  { enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
41772
41492
  deps
41773
41493
  );
41774
- await setActiveCourse({ ...activeCourse, currentLessonId: lessonData.lesson.id });
41494
+ const ws = await findWorkspaceState();
41495
+ if (ws)
41496
+ await updateWorkspaceState(ws.workspacePath, {
41497
+ currentLessonId: lessonData.lesson.id,
41498
+ ...lessonData.lesson.moduleId ? { currentModuleId: lessonData.lesson.moduleId } : {}
41499
+ });
41775
41500
  if (opts.json) {
41776
41501
  output(lessonData, { json: true });
41777
41502
  } else {
@@ -41801,6 +41526,7 @@ var init_next = __esm({
41801
41526
  }
41802
41527
  }
41803
41528
  const msg = err instanceof Error ? err.message : String(err);
41529
+ if (opts.json) jsonError(msg);
41804
41530
  error(msg);
41805
41531
  }
41806
41532
  });
@@ -41861,13 +41587,14 @@ var init_lesson = __esm({
41861
41587
  lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
41862
41588
  try {
41863
41589
  const session = await requireSession();
41864
- const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41590
+ const activeCourse = await requireActiveCourse();
41865
41591
  const driftWarning = await checkCourseDrift();
41866
41592
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41867
41593
  const data = createHttpProvider(session.apiUrl, session.token);
41868
41594
  const deps = { data, logger: logger12 };
41869
41595
  const lessonId = activeCourse.currentLessonId;
41870
41596
  if (!lessonId) {
41597
+ if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
41871
41598
  error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
41872
41599
  }
41873
41600
  const content = await getContent({ lessonId, enrollmentId: activeCourse.enrollmentId }, deps);
@@ -41890,6 +41617,7 @@ var init_lesson = __esm({
41890
41617
  }
41891
41618
  } catch (err) {
41892
41619
  const msg = err instanceof Error ? err.message : String(err);
41620
+ if (opts.json) jsonError(msg);
41893
41621
  error(msg);
41894
41622
  }
41895
41623
  });
@@ -41911,7 +41639,7 @@ var init_hint = __esm({
41911
41639
  hintCommand = new Command12("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
41912
41640
  try {
41913
41641
  const session = await requireSession();
41914
- const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41642
+ const activeCourse = await requireActiveCourse();
41915
41643
  const driftWarning = await checkCourseDrift();
41916
41644
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41917
41645
  const data = createHttpProvider(session.apiUrl, session.token);
@@ -41927,6 +41655,7 @@ var init_hint = __esm({
41927
41655
  }
41928
41656
  } catch (err) {
41929
41657
  const msg = err instanceof Error ? err.message : String(err);
41658
+ if (opts.json) jsonError(msg);
41930
41659
  error(msg);
41931
41660
  }
41932
41661
  });
@@ -41960,9 +41689,251 @@ var init_exercises = __esm({
41960
41689
  }
41961
41690
  });
41962
41691
 
41692
+ // src/output/init-template.ts
41693
+ function detectTechFromTags(tags) {
41694
+ const found = /* @__PURE__ */ new Set();
41695
+ const keywords = Object.keys(LANGUAGE_TAGS);
41696
+ const joined = tags.map((t) => t.toLowerCase()).join(" ");
41697
+ for (const kw of keywords) {
41698
+ const pattern = new RegExp(`\\b${kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
41699
+ if (pattern.test(joined)) {
41700
+ found.add(kw);
41701
+ }
41702
+ }
41703
+ return [...found];
41704
+ }
41705
+ function formatCourseLanguage(tags) {
41706
+ if (!tags || tags.length === 0) return "Programa\xE7\xE3o";
41707
+ const direct = tags.map((t) => LANGUAGE_TAGS[t.toLowerCase()]).filter(Boolean);
41708
+ if (direct.length > 0) return direct.slice(0, 3).join(" / ");
41709
+ const detected = detectTechFromTags(tags);
41710
+ const mapped = detected.map((t) => LANGUAGE_TAGS[t]).filter(Boolean);
41711
+ return mapped.length > 0 ? mapped.slice(0, 3).join(" / ") : "Programa\xE7\xE3o";
41712
+ }
41713
+ function formatCourseLevel(level) {
41714
+ const map2 = {
41715
+ beginner: "Iniciante",
41716
+ intermediate: "Intermedi\xE1rio",
41717
+ advanced: "Avan\xE7ado"
41718
+ };
41719
+ return map2[level ?? "intermediate"] ?? "Intermedi\xE1rio";
41720
+ }
41721
+ function formatCourseMethodology(approach) {
41722
+ const map2 = {
41723
+ hybrid: "Teoria e pr\xE1tica",
41724
+ structured: "Estruturado com exerc\xEDcios",
41725
+ exploratory: "Explorat\xF3rio",
41726
+ mentored: "Mentoria guiada",
41727
+ "ai-exploration": "Explora\xE7\xE3o com IA"
41728
+ };
41729
+ return map2[approach] ?? "Teoria e pr\xE1tica";
41730
+ }
41731
+ function formatCourseObjective(description) {
41732
+ if (!description || description.trim() === "") return null;
41733
+ const firstSentence = description.split(/[.!?]/)[0]?.trim();
41734
+ if (!firstSentence) return null;
41735
+ if (firstSentence.length > 200) return firstSentence.slice(0, 200) + "...";
41736
+ return firstSentence;
41737
+ }
41738
+ function buildInitTemplate(userName, course, progress3) {
41739
+ const lines = [];
41740
+ lines.push("# ToStudy CLI \u2014 Instru\xE7\xF5es para o Assistente AI");
41741
+ lines.push("");
41742
+ lines.push("Voc\xEA \xE9 um tutor de programa\xE7\xE3o auxiliando um aluno via terminal.");
41743
+ lines.push("");
41744
+ lines.push("## 1. Estado Atual");
41745
+ lines.push(`- Usu\xE1rio: ${userName}`);
41746
+ lines.push(`- Curso: "${course.title}" (${progress3?.coursePercent ?? course.progress}%)`);
41747
+ if (progress3) {
41748
+ const mod = progress3.currentModule;
41749
+ const les = progress3.currentLesson;
41750
+ lines.push(`- M\xF3dulo ${mod.order}/${mod.totalModules}: "${mod.title}"`);
41751
+ lines.push(`- Li\xE7\xE3o ${les.order}/${les.totalLessons}: "${les.title}"`);
41752
+ } else {
41753
+ lines.push("- Nenhuma li\xE7\xE3o iniciada. Rode `tostudy start` para come\xE7ar.");
41754
+ }
41755
+ const language = formatCourseLanguage(course.tags);
41756
+ const level = formatCourseLevel(course.level);
41757
+ const methodology = formatCourseMethodology(course.teachingApproach);
41758
+ const objective = formatCourseObjective(course.description);
41759
+ lines.push("");
41760
+ lines.push("## 2. Contexto do Curso");
41761
+ lines.push(`- Linguagem: ${language}`);
41762
+ lines.push(`- N\xEDvel: ${level}`);
41763
+ lines.push(`- Metodologia: ${methodology}`);
41764
+ if (objective) lines.push(`- Objetivo: ${objective}`);
41765
+ lines.push("");
41766
+ lines.push("Adapte explica\xE7\xF5es e exemplos para esta stack.");
41767
+ lines.push("Exerc\xEDcios envolvem arquivos de c\xF3digo no workspace do aluno.");
41768
+ lines.push("");
41769
+ lines.push("## 3. Comandos");
41770
+ lines.push("| Comando | Uso |");
41771
+ lines.push("|----------------------------------|----------------------------------|");
41772
+ lines.push("| `tostudy lesson` | Ver conte\xFAdo da li\xE7\xE3o atual |");
41773
+ lines.push("| `tostudy next` | Avan\xE7ar para pr\xF3xima li\xE7\xE3o |");
41774
+ lines.push("| `tostudy hint` | Dica progressiva |");
41775
+ lines.push("| `tostudy validate <arquivo>` | Validar solu\xE7\xE3o do exerc\xEDcio |");
41776
+ lines.push("| `tostudy progress` | Ver progresso detalhado |");
41777
+ lines.push("| `tostudy start` | Iniciar/retomar m\xF3dulo |");
41778
+ lines.push("| `tostudy start-next` | Avan\xE7ar para pr\xF3ximo m\xF3dulo |");
41779
+ lines.push("| `tostudy courses` | Listar cursos matriculados |");
41780
+ lines.push("| `tostudy select <n>` | Trocar curso ativo |");
41781
+ lines.push("| `tostudy doctor` | Diagn\xF3stico de problemas |");
41782
+ lines.push("");
41783
+ lines.push("Adicione `--json` para output estruturado quando precisar parsear.");
41784
+ lines.push("");
41785
+ lines.push("## 4. Regras");
41786
+ lines.push("1. Comece rodando `tostudy lesson` para carregar o conte\xFAdo");
41787
+ lines.push("2. N\xE3o d\xEA respostas diretas \u2014 use `tostudy hint` para dicas progressivas");
41788
+ lines.push("3. Ap\xF3s `tostudy validate`, analise cada crit\xE9rio e guie a corre\xE7\xE3o");
41789
+ lines.push("4. N\xE3o avance com `tostudy next` sem o aluno completar o exerc\xEDcio");
41790
+ lines.push('5. Se algum comando retornar aviso de "curso ativo mudou", rode `tostudy init`');
41791
+ lines.push("");
41792
+ lines.push("## 5. Coexist\xEAncia");
41793
+ lines.push("Este workspace pode ter suas pr\xF3prias instru\xE7\xF5es (CLAUDE.md, AGENTS.md).");
41794
+ lines.push("- Instru\xE7\xF5es do projeto \u2192 decis\xF5es de c\xF3digo (estilo, lint, arquitetura)");
41795
+ lines.push("- Instru\xE7\xF5es do ToStudy \u2192 fluxo de estudo (navega\xE7\xE3o, exerc\xEDcios, valida\xE7\xE3o)");
41796
+ return lines.join("\n");
41797
+ }
41798
+ function resolveTutorMode(level) {
41799
+ if (level === "beginner") return "guided";
41800
+ if (level === "advanced") return "direct";
41801
+ return "balanced";
41802
+ }
41803
+ function formatTutorMode(mode) {
41804
+ const map2 = {
41805
+ guided: "Guiado",
41806
+ balanced: "Equilibrado",
41807
+ direct: "Direto"
41808
+ };
41809
+ return map2[mode];
41810
+ }
41811
+ function buildTutorPersonalizationSection(course, learnerProfile) {
41812
+ const mode = resolveTutorMode(course.level);
41813
+ const lines = [];
41814
+ lines.push("## 6. Personaliza\xE7\xE3o do Tutor");
41815
+ lines.push(`- Modo inicial do tutor: ${formatTutorMode(mode)}`);
41816
+ lines.push(
41817
+ "- Na primeira intera\xE7\xE3o, apresente a escolha entre cen\xE1rio fict\xEDcio do curso e adapta\xE7\xE3o ao contexto real do aluno."
41818
+ );
41819
+ lines.push(
41820
+ `- O aluno atual j\xE1 optou por: ${learnerProfile.adaptToRealContext ? "Adaptar para o contexto real" : "Manter o cen\xE1rio fict\xEDcio"}`
41821
+ );
41822
+ lines.push(`- Segmento: ${learnerProfile.segment}`);
41823
+ lines.push(`- Empresa: ${learnerProfile.company}`);
41824
+ lines.push(`- Produtos/servi\xE7os: ${learnerProfile.productsOrServices}`);
41825
+ lines.push(`- Regi\xE3o: ${learnerProfile.region}`);
41826
+ lines.push(`- Equipe: ${learnerProfile.team}`);
41827
+ lines.push(`- Objetivo: ${learnerProfile.goal}`);
41828
+ lines.push(`- N\xEDvel declarado do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
41829
+ if (learnerProfile.adaptToRealContext) {
41830
+ lines.push(
41831
+ "- Use o contexto do aluno como padr\xE3o para explica\xE7\xF5es, exemplos, exerc\xEDcios e framing."
41832
+ );
41833
+ lines.push(
41834
+ "- Troque nomes, entreg\xE1veis e restri\xE7\xF5es do cen\xE1rio fict\xEDcio por equivalentes reais."
41835
+ );
41836
+ lines.push(
41837
+ "- Preserve objetivos pedag\xF3gicos, crit\xE9rios de valida\xE7\xE3o e progress\xE3o de dificuldade."
41838
+ );
41839
+ } else {
41840
+ lines.push(
41841
+ "- Preserve o cen\xE1rio fict\xEDcio como padr\xE3o e conecte os conceitos ao neg\xF3cio real quando isso ajudar."
41842
+ );
41843
+ lines.push(
41844
+ "- Se o aluno quiser migrar para o contexto real depois, oriente a rodar `tostudy init` novamente."
41845
+ );
41846
+ }
41847
+ if (mode === "guided") {
41848
+ lines.push(
41849
+ "- No modo guiado, explique o porqu\xEA de cada passo, proponha checkpoints curtos e confirme entendimento antes de avan\xE7ar."
41850
+ );
41851
+ } else if (mode === "direct") {
41852
+ lines.push(
41853
+ "- Mesmo no modo mais direto, continue sem dar a resposta pronta e preserve a progress\xE3o pedag\xF3gica."
41854
+ );
41855
+ } else {
41856
+ lines.push(
41857
+ "- No modo equilibrado, combine explica\xE7\xF5es curtas com autonomia entre checkpoints."
41858
+ );
41859
+ }
41860
+ return lines.join("\n");
41861
+ }
41862
+ function buildLearnerBrief(userName, course, learnerProfile) {
41863
+ const lines = [];
41864
+ lines.push("# ToStudy CLI \u2014 Brief do Aluno");
41865
+ lines.push("");
41866
+ lines.push("Este documento resume o contexto real do aluno para orientar exemplos e exercicios.");
41867
+ lines.push("");
41868
+ lines.push("## 1. Identificacao");
41869
+ lines.push(`- Aluno: ${userName}`);
41870
+ lines.push(`- Curso: "${course.title}"`);
41871
+ lines.push(`- Segmento: ${learnerProfile.segment}`);
41872
+ lines.push(`- Empresa: ${learnerProfile.company}`);
41873
+ lines.push(`- Produtos/servicos: ${learnerProfile.productsOrServices}`);
41874
+ lines.push(`- Regiao: ${learnerProfile.region}`);
41875
+ lines.push(`- Equipe: ${learnerProfile.team}`);
41876
+ lines.push("");
41877
+ lines.push("## 2. Objetivo e Contexto");
41878
+ lines.push(`- Objetivo principal: ${learnerProfile.goal}`);
41879
+ lines.push(`- Nivel do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
41880
+ lines.push(
41881
+ `- Adaptar curso ao contexto real: ${learnerProfile.adaptToRealContext ? "Sim" : "Nao"}`
41882
+ );
41883
+ lines.push("");
41884
+ lines.push("## 3. Como usar este brief");
41885
+ lines.push("- Reutilize este contexto para adaptar exemplos, cenarios e exercicios.");
41886
+ lines.push("- Preserve a proposta pedagogica do curso antes de customizar o contexto.");
41887
+ lines.push(
41888
+ "- Se algo mudar no negocio do aluno, rode `tostudy init` novamente para atualizar o brief."
41889
+ );
41890
+ return lines.join("\n");
41891
+ }
41892
+ function buildInitArtifacts(userName, course, progress3, learnerProfile) {
41893
+ return {
41894
+ tutorInstructions: [
41895
+ buildInitTemplate(userName, course, progress3),
41896
+ buildTutorPersonalizationSection(course, learnerProfile)
41897
+ ].join("\n\n"),
41898
+ learnerBrief: buildLearnerBrief(userName, course, learnerProfile)
41899
+ };
41900
+ }
41901
+ var LANGUAGE_TAGS;
41902
+ var init_init_template = __esm({
41903
+ "src/output/init-template.ts"() {
41904
+ "use strict";
41905
+ LANGUAGE_TAGS = {
41906
+ javascript: "JavaScript",
41907
+ typescript: "TypeScript",
41908
+ python: "Python",
41909
+ react: "React",
41910
+ "react-native": "React Native",
41911
+ node: "Node.js",
41912
+ nodejs: "Node.js",
41913
+ go: "Go",
41914
+ rust: "Rust",
41915
+ java: "Java",
41916
+ csharp: "C#",
41917
+ "c#": "C#",
41918
+ cpp: "C++",
41919
+ ruby: "Ruby",
41920
+ php: "PHP",
41921
+ swift: "Swift",
41922
+ kotlin: "Kotlin",
41923
+ html: "HTML",
41924
+ css: "CSS",
41925
+ sql: "SQL",
41926
+ vue: "Vue.js",
41927
+ angular: "Angular",
41928
+ nextjs: "Next.js",
41929
+ svelte: "Svelte"
41930
+ };
41931
+ }
41932
+ });
41933
+
41963
41934
  // src/commands/validate.ts
41964
41935
  import fs9 from "node:fs";
41965
- import path9 from "node:path";
41936
+ import path10 from "node:path";
41966
41937
  import { Command as Command13 } from "commander";
41967
41938
  var logger14, validateCommand;
41968
41939
  var init_validate = __esm({
@@ -41978,11 +41949,12 @@ var init_validate = __esm({
41978
41949
  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) => {
41979
41950
  try {
41980
41951
  const session = await requireSession();
41981
- const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
41952
+ const activeCourse = await requireActiveCourse();
41982
41953
  const driftWarning = await checkCourseDrift();
41983
41954
  if (driftWarning) process.stderr.write(driftWarning + "\n");
41984
41955
  const lessonId = activeCourse.currentLessonId;
41985
41956
  if (!lessonId) {
41957
+ if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
41986
41958
  error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
41987
41959
  }
41988
41960
  let solution;
@@ -41990,14 +41962,18 @@ var init_validate = __esm({
41990
41962
  solution = fs9.readFileSync("/dev/stdin", "utf-8");
41991
41963
  } else if (file2) {
41992
41964
  if (!fs9.existsSync(file2)) {
41965
+ if (opts.json)
41966
+ jsonError("file_not_found", { message: `Arquivo n\xE3o encontrado: ${file2}` });
41993
41967
  error(`Arquivo n\xE3o encontrado: ${file2}`);
41994
41968
  }
41995
41969
  solution = fs9.readFileSync(file2, "utf-8");
41996
41970
  } else {
41971
+ if (opts.json)
41972
+ jsonError("no_solution_provided", { message: "Forne\xE7a um arquivo ou use --stdin" });
41997
41973
  error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
41998
41974
  }
41999
41975
  if (file2 && activeCourse.courseTags?.length) {
42000
- const ext = path9.extname(file2).toLowerCase();
41976
+ const ext = path10.extname(file2).toLowerCase();
42001
41977
  const LANG_EXTENSIONS = {
42002
41978
  ".html": ["html", "html5"],
42003
41979
  ".css": ["css"],
@@ -42051,6 +42027,7 @@ var init_validate = __esm({
42051
42027
  process.exit(result.passed ? 0 : 1);
42052
42028
  } catch (err) {
42053
42029
  const msg = err instanceof Error ? err.message : String(err);
42030
+ if (opts.json) jsonError(msg);
42054
42031
  error(msg);
42055
42032
  }
42056
42033
  });
@@ -42064,10 +42041,12 @@ var init_menu = __esm({
42064
42041
  "src/commands/menu.ts"() {
42065
42042
  "use strict";
42066
42043
  init_session();
42044
+ init_workspace_state();
42067
42045
  init_formatter();
42068
42046
  menuCommand = new Command14("menu").description("Show available commands and current study context").action(async () => {
42069
42047
  const session = await getSession();
42070
- const activeCourse = session ? await getActiveCourse() : null;
42048
+ const wsResult = session ? await findWorkspaceState() : null;
42049
+ const activeCourse = wsResult?.state ?? null;
42071
42050
  const lines = [
42072
42051
  "\u250C\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
42073
42052
  "\u2502 ToStudy CLI \u2014 Menu \u2502",
@@ -42255,7 +42234,8 @@ async function runInit(deps = defaultDeps4, flags = {}) {
42255
42234
  }
42256
42235
  const data = deps.createHttpProvider(session.apiUrl, session.token);
42257
42236
  const apiDeps = { data, logger: deps.logger };
42258
- const activeCourse = await deps.getActiveCourse();
42237
+ const wsResult = await deps.findWorkspaceState();
42238
+ const activeCourse = wsResult?.state ?? null;
42259
42239
  if (!activeCourse) {
42260
42240
  try {
42261
42241
  const courses3 = await deps.listCourses({ userId: session.userId }, apiDeps);
@@ -42399,16 +42379,17 @@ var init_init = __esm({
42399
42379
  init_courses();
42400
42380
  init_http2();
42401
42381
  init_session();
42382
+ init_workspace_state();
42402
42383
  init_formatter();
42403
42384
  init_init_template();
42404
42385
  init_learner_context();
42405
- init_api();
42386
+ init_api3();
42406
42387
  init_instruction_pipeline();
42407
42388
  init_pipeline_deps();
42408
42389
  logger15 = createLogger("cli:init");
42409
42390
  defaultDeps4 = {
42410
42391
  getSession,
42411
- getActiveCourse,
42392
+ findWorkspaceState,
42412
42393
  listCourses,
42413
42394
  getProgress,
42414
42395
  getRemoteEnrollmentOnboarding,
@@ -42432,13 +42413,13 @@ var init_init = __esm({
42432
42413
 
42433
42414
  // ../../packages/tostudy-core/src/workspace/setup-workspace.ts
42434
42415
  import fs10 from "node:fs/promises";
42435
- import path10 from "node:path";
42416
+ import path11 from "node:path";
42436
42417
  async function setupWorkspace(input2) {
42437
- const workspacePath = path10.join(input2.basePath, input2.courseSlug);
42418
+ const workspacePath = path11.join(input2.basePath, input2.courseSlug);
42438
42419
  for (const dir of WORKSPACE_DIRS) {
42439
- await fs10.mkdir(path10.join(workspacePath, dir), { recursive: true });
42420
+ await fs10.mkdir(path11.join(workspacePath, dir), { recursive: true });
42440
42421
  }
42441
- const configPath = path10.join(workspacePath, ".ana-config.json");
42422
+ const configPath = path11.join(workspacePath, ".ana-config.json");
42442
42423
  const config2 = {
42443
42424
  courseId: input2.courseId,
42444
42425
  courseSlug: input2.courseSlug,
@@ -42473,7 +42454,7 @@ async function setupWorkspace(input2) {
42473
42454
  "tostudy vault sync # Sincronizar progresso",
42474
42455
  "```"
42475
42456
  ].join("\n");
42476
- await fs10.writeFile(path10.join(workspacePath, "README.md"), readme, "utf-8");
42457
+ await fs10.writeFile(path11.join(workspacePath, "README.md"), readme, "utf-8");
42477
42458
  return { workspacePath, directories: WORKSPACE_DIRS, configPath };
42478
42459
  }
42479
42460
  var WORKSPACE_DIRS;
@@ -42575,7 +42556,7 @@ var init_templates = __esm({
42575
42556
 
42576
42557
  // ../../packages/tostudy-core/src/workspace/extract-exercise.ts
42577
42558
  import fs11 from "node:fs/promises";
42578
- import path11 from "node:path";
42559
+ import path12 from "node:path";
42579
42560
  function padOrder(n) {
42580
42561
  return String(n).padStart(2, "0");
42581
42562
  }
@@ -42599,15 +42580,15 @@ async function extractExercise(input2) {
42599
42580
  const { lessonData, exerciseTier, workspacePath } = input2;
42600
42581
  const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
42601
42582
  const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
42602
- const exercisePath = path11.join(workspacePath, "exercises", moduleDir, lessonDir);
42583
+ const exercisePath = path12.join(workspacePath, "exercises", moduleDir, lessonDir);
42603
42584
  await fs11.mkdir(exercisePath, { recursive: true });
42604
42585
  const extractedFiles = [];
42605
42586
  let hasStarterCode = false;
42606
42587
  if (lessonData.sandpackConfig?.files) {
42607
42588
  for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
42608
42589
  const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
42609
- const fullPath = path11.join(exercisePath, cleanPath);
42610
- await fs11.mkdir(path11.dirname(fullPath), { recursive: true });
42590
+ const fullPath = path12.join(exercisePath, cleanPath);
42591
+ await fs11.mkdir(path12.dirname(fullPath), { recursive: true });
42611
42592
  await fs11.writeFile(fullPath, fileData.code, "utf-8");
42612
42593
  extractedFiles.push(cleanPath);
42613
42594
  hasStarterCode = true;
@@ -42616,13 +42597,13 @@ async function extractExercise(input2) {
42616
42597
  const tierData = getTierData(lessonData.structuredData, exerciseTier);
42617
42598
  const tierCode = tierData?.code;
42618
42599
  if (tierCode) {
42619
- await fs11.writeFile(path11.join(exercisePath, "exercise.js"), tierCode, "utf-8");
42600
+ await fs11.writeFile(path12.join(exercisePath, "exercise.js"), tierCode, "utf-8");
42620
42601
  extractedFiles.push("exercise.js");
42621
42602
  hasStarterCode = true;
42622
42603
  } else {
42623
42604
  const starter = getStarterCode(lessonData.structuredData);
42624
42605
  if (starter) {
42625
- await fs11.writeFile(path11.join(exercisePath, "exercise.js"), starter, "utf-8");
42606
+ await fs11.writeFile(path12.join(exercisePath, "exercise.js"), starter, "utf-8");
42626
42607
  extractedFiles.push("exercise.js");
42627
42608
  hasStarterCode = true;
42628
42609
  }
@@ -42641,7 +42622,7 @@ async function extractExercise(input2) {
42641
42622
  }
42642
42623
  };
42643
42624
  await fs11.writeFile(
42644
- path11.join(exercisePath, "package.json"),
42625
+ path12.join(exercisePath, "package.json"),
42645
42626
  JSON.stringify(pkgJson, null, 2),
42646
42627
  "utf-8"
42647
42628
  );
@@ -42653,19 +42634,19 @@ async function extractExercise(input2) {
42653
42634
  );
42654
42635
  for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
42655
42636
  if (!sandpackFileNames.has(configFile)) {
42656
- await fs11.writeFile(path11.join(exercisePath, configFile), configContent, "utf-8");
42637
+ await fs11.writeFile(path12.join(exercisePath, configFile), configContent, "utf-8");
42657
42638
  extractedFiles.push(configFile);
42658
42639
  }
42659
42640
  }
42660
42641
  const setupSh = `#!/bin/sh
42661
42642
  ${scaffold.setupScript}
42662
42643
  `;
42663
- await fs11.writeFile(path11.join(exercisePath, "setup.sh"), setupSh, "utf-8");
42644
+ await fs11.writeFile(path12.join(exercisePath, "setup.sh"), setupSh, "utf-8");
42664
42645
  extractedFiles.push("setup.sh");
42665
42646
  }
42666
42647
  }
42667
42648
  const readme = generateReadme(lessonData, exerciseTier);
42668
- const readmePath = path11.join(exercisePath, "README.md");
42649
+ const readmePath = path12.join(exercisePath, "README.md");
42669
42650
  await fs11.writeFile(readmePath, readme, "utf-8");
42670
42651
  extractedFiles.push("README.md");
42671
42652
  return {
@@ -42738,8 +42719,8 @@ var init_workspace = __esm({
42738
42719
 
42739
42720
  // src/commands/workspace.ts
42740
42721
  import { Command as Command16 } from "commander";
42741
- import path12 from "node:path";
42742
- import os11 from "node:os";
42722
+ import path13 from "node:path";
42723
+ import os12 from "node:os";
42743
42724
  import fs12 from "node:fs/promises";
42744
42725
  var logger16, workspaceCommand;
42745
42726
  var init_workspace2 = __esm({
@@ -42762,13 +42743,13 @@ var init_workspace2 = __esm({
42762
42743
  let directories;
42763
42744
  if (!opts.path && cwdIsWorkspace) {
42764
42745
  const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
42765
- workspacePath = resolvedCwd ?? path12.join(process.cwd(), ".tostudy");
42746
+ workspacePath = resolvedCwd ?? path13.join(process.cwd(), ".tostudy");
42766
42747
  await fs12.mkdir(workspacePath, { recursive: true });
42767
42748
  directories = ["exercises", "generated", "notes", "diagrams"];
42768
42749
  for (const dir of directories) {
42769
- await fs12.mkdir(path12.join(workspacePath, dir), { recursive: true });
42750
+ await fs12.mkdir(path13.join(workspacePath, dir), { recursive: true });
42770
42751
  }
42771
- const configPath = path12.join(workspacePath, ".ana-config.json");
42752
+ const configPath = path13.join(workspacePath, ".ana-config.json");
42772
42753
  await fs12.writeFile(
42773
42754
  configPath,
42774
42755
  JSON.stringify(
@@ -42787,7 +42768,7 @@ var init_workspace2 = __esm({
42787
42768
  "utf-8"
42788
42769
  );
42789
42770
  } else {
42790
- const basePath = opts.path ?? path12.join(os11.homedir(), "study");
42771
+ const basePath = opts.path ?? path13.join(os12.homedir(), "study");
42791
42772
  const result2 = await setupWorkspace({
42792
42773
  courseId: activeCourse.courseId,
42793
42774
  courseSlug: courseSlug(activeCourse.courseTitle),
@@ -42821,7 +42802,7 @@ Pr\xF3ximo passo: tostudy export
42821
42802
  process.exit(1);
42822
42803
  }
42823
42804
  });
42824
- workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path12.join(os11.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42805
+ workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42825
42806
  try {
42826
42807
  const activeCourse = await requireActiveCourse();
42827
42808
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -42840,22 +42821,22 @@ Pr\xF3ximo passo: tostudy export
42840
42821
  const workspacePath = ws.workspacePath;
42841
42822
  let configData = null;
42842
42823
  try {
42843
- const raw = await fs12.readFile(path12.join(workspacePath, ".ana-config.json"), "utf-8");
42824
+ const raw = await fs12.readFile(path13.join(workspacePath, ".ana-config.json"), "utf-8");
42844
42825
  configData = JSON.parse(raw);
42845
42826
  } catch {
42846
42827
  configData = null;
42847
42828
  }
42848
- const exercisesDir = path12.join(workspacePath, "exercises");
42829
+ const exercisesDir = path13.join(workspacePath, "exercises");
42849
42830
  let exerciseCount = 0;
42850
42831
  try {
42851
42832
  const moduleDirs = await fs12.readdir(exercisesDir);
42852
42833
  for (const modDir of moduleDirs) {
42853
- const modPath = path12.join(exercisesDir, modDir);
42834
+ const modPath = path13.join(exercisesDir, modDir);
42854
42835
  const stat = await fs12.stat(modPath);
42855
42836
  if (stat.isDirectory()) {
42856
42837
  const lessonDirs = await fs12.readdir(modPath);
42857
42838
  for (const lessonDir of lessonDirs) {
42858
- const lessonPath = path12.join(modPath, lessonDir);
42839
+ const lessonPath = path13.join(modPath, lessonDir);
42859
42840
  const lstat = await fs12.stat(lessonPath);
42860
42841
  if (lstat.isDirectory()) exerciseCount++;
42861
42842
  }
@@ -42863,14 +42844,14 @@ Pr\xF3ximo passo: tostudy export
42863
42844
  }
42864
42845
  } catch {
42865
42846
  }
42866
- const generatedDir = path12.join(workspacePath, "generated");
42847
+ const generatedDir = path13.join(workspacePath, "generated");
42867
42848
  let artifactCount = 0;
42868
42849
  try {
42869
42850
  const files = await fs12.readdir(generatedDir);
42870
42851
  artifactCount = files.length;
42871
42852
  } catch {
42872
42853
  }
42873
- const diagramsDir = path12.join(workspacePath, "diagrams");
42854
+ const diagramsDir = path13.join(workspacePath, "diagrams");
42874
42855
  let diagramCount = 0;
42875
42856
  try {
42876
42857
  const files = await fs12.readdir(diagramsDir);
@@ -42922,8 +42903,8 @@ Pr\xF3ximo passo: tostudy export
42922
42903
 
42923
42904
  // src/commands/export.ts
42924
42905
  import { Command as Command17 } from "commander";
42925
- import path13 from "node:path";
42926
- import os12 from "node:os";
42906
+ import path14 from "node:path";
42907
+ import os13 from "node:os";
42927
42908
  import fs13 from "node:fs/promises";
42928
42909
  var logger17, exportCommand;
42929
42910
  var init_export = __esm({
@@ -42935,7 +42916,7 @@ var init_export = __esm({
42935
42916
  init_session();
42936
42917
  init_resolve();
42937
42918
  logger17 = createLogger("cli:export");
42938
- exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42919
+ exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os13.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
42939
42920
  try {
42940
42921
  const session = await requireSession();
42941
42922
  const activeCourse = await requireActiveCourse();
@@ -42953,7 +42934,7 @@ var init_export = __esm({
42953
42934
  opts.path
42954
42935
  );
42955
42936
  if (ws.found && ws.source === "cwd" && ws.workspacePath) {
42956
- const configPath = path13.join(ws.workspacePath, ".ana-config.json");
42937
+ const configPath = path14.join(ws.workspacePath, ".ana-config.json");
42957
42938
  let hasConfig = false;
42958
42939
  try {
42959
42940
  await fs13.access(configPath);
@@ -42965,7 +42946,7 @@ var init_export = __esm({
42965
42946
  logger17.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
42966
42947
  await fs13.mkdir(ws.workspacePath, { recursive: true });
42967
42948
  for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
42968
- await fs13.mkdir(path13.join(ws.workspacePath, dir), { recursive: true });
42949
+ await fs13.mkdir(path14.join(ws.workspacePath, dir), { recursive: true });
42969
42950
  }
42970
42951
  await fs13.writeFile(
42971
42952
  configPath,
@@ -42985,7 +42966,7 @@ var init_export = __esm({
42985
42966
  "utf-8"
42986
42967
  );
42987
42968
  await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
42988
- const isNamespaced = ws.workspacePath === path13.join(process.cwd(), ".tostudy");
42969
+ const isNamespaced = ws.workspacePath === path14.join(process.cwd(), ".tostudy");
42989
42970
  process.stderr.write(
42990
42971
  isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
42991
42972
  ` : `\u2728 Workspace inicializado nesta pasta.
@@ -43038,8 +43019,8 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
43038
43019
  // src/commands/open.ts
43039
43020
  import { Command as Command18 } from "commander";
43040
43021
  import { execFile as execFile3 } from "node:child_process";
43041
- import path14 from "node:path";
43042
- import os13 from "node:os";
43022
+ import path15 from "node:path";
43023
+ import os14 from "node:os";
43043
43024
  var logger18, openCommand;
43044
43025
  var init_open = __esm({
43045
43026
  "src/commands/open.ts"() {
@@ -43048,7 +43029,7 @@ var init_open = __esm({
43048
43029
  init_session();
43049
43030
  init_resolve();
43050
43031
  logger18 = createLogger("cli:open");
43051
- openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os13.homedir(), "study")).action(async (opts) => {
43032
+ openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path15.join(os14.homedir(), "study")).action(async (opts) => {
43052
43033
  try {
43053
43034
  const activeCourse = await requireActiveCourse();
43054
43035
  const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
@@ -43104,14 +43085,14 @@ var init_types3 = __esm({
43104
43085
 
43105
43086
  // ../../packages/tostudy-core/src/vault/write-vault.ts
43106
43087
  import fs14 from "node:fs/promises";
43107
- import path15 from "node:path";
43088
+ import path16 from "node:path";
43108
43089
  async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
43109
43090
  for (const file2 of files) {
43110
- const fullPath = path15.join(outputPath, file2.relativePath);
43111
- await fs14.mkdir(path15.dirname(fullPath), { recursive: true });
43091
+ const fullPath = path16.join(outputPath, file2.relativePath);
43092
+ await fs14.mkdir(path16.dirname(fullPath), { recursive: true });
43112
43093
  await fs14.writeFile(fullPath, file2.content, "utf-8");
43113
43094
  }
43114
- const vaultPath = path15.join(outputPath, courseSlug2);
43095
+ const vaultPath = path16.join(outputPath, courseSlug2);
43115
43096
  const marker = {
43116
43097
  courseId,
43117
43098
  courseSlug: courseSlug2,
@@ -43120,7 +43101,7 @@ async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
43120
43101
  };
43121
43102
  await fs14.mkdir(vaultPath, { recursive: true });
43122
43103
  await fs14.writeFile(
43123
- path15.join(vaultPath, VAULT_MARKER_FILENAME),
43104
+ path16.join(vaultPath, VAULT_MARKER_FILENAME),
43124
43105
  JSON.stringify(marker, null, 2),
43125
43106
  "utf-8"
43126
43107
  );
@@ -43145,8 +43126,8 @@ var init_vault = __esm({
43145
43126
 
43146
43127
  // src/commands/vault.ts
43147
43128
  import { Command as Command19 } from "commander";
43148
- import path16 from "node:path";
43149
- import os14 from "node:os";
43129
+ import path17 from "node:path";
43130
+ import os15 from "node:os";
43150
43131
  import fs15 from "node:fs/promises";
43151
43132
  var logger19, vaultCommand;
43152
43133
  var init_vault2 = __esm({
@@ -43160,7 +43141,7 @@ var init_vault2 = __esm({
43160
43141
  init_resolve();
43161
43142
  logger19 = createLogger("cli:vault");
43162
43143
  vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
43163
- vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path16.join(os14.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43144
+ vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path17.join(os15.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43164
43145
  try {
43165
43146
  const session = await requireSession();
43166
43147
  const activeCourse = await requireActiveCourse();
@@ -43244,7 +43225,7 @@ Para visualizar:
43244
43225
  process.exit(1);
43245
43226
  }
43246
43227
  });
43247
- vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path16.join(os14.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43228
+ vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path17.join(os15.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
43248
43229
  try {
43249
43230
  const session = await requireSession();
43250
43231
  const activeCourse = await requireActiveCourse();
@@ -43272,7 +43253,7 @@ Para visualizar:
43272
43253
  const data = createHttpProvider(session.apiUrl, session.token);
43273
43254
  const deps = { data, logger: logger19 };
43274
43255
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
43275
- const markerPath = path16.join(vaultPath, ".ana-vault.json");
43256
+ const markerPath = path17.join(vaultPath, ".ana-vault.json");
43276
43257
  const markerRaw = await fs15.readFile(markerPath, "utf-8");
43277
43258
  const marker = JSON.parse(markerRaw);
43278
43259
  marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -43282,7 +43263,7 @@ Para visualizar:
43282
43263
  currentLesson: progress3.currentLesson.title
43283
43264
  };
43284
43265
  await fs15.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
43285
- const courseIndexPath = path16.join(vaultPath, slug, "index.md");
43266
+ const courseIndexPath = path17.join(vaultPath, slug, "index.md");
43286
43267
  try {
43287
43268
  let indexContent = await fs15.readFile(courseIndexPath, "utf-8");
43288
43269
  indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
@@ -43466,7 +43447,7 @@ var init_brief = __esm({
43466
43447
  init_src();
43467
43448
  init_session();
43468
43449
  init_cache();
43469
- init_api2();
43450
+ init_api();
43470
43451
  init_formatter();
43471
43452
  logger21 = createLogger("cli:brief");
43472
43453
  briefCommand = new Command22("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
@@ -43518,7 +43499,7 @@ var init_brief_create = __esm({
43518
43499
  init_src();
43519
43500
  init_session();
43520
43501
  init_bootstrap();
43521
- init_api2();
43502
+ init_api();
43522
43503
  init_cache();
43523
43504
  init_formatter();
43524
43505
  logger22 = createLogger("cli:brief-create");
@@ -43591,15 +43572,175 @@ var init_brief_open = __esm({
43591
43572
  }
43592
43573
  });
43593
43574
 
43575
+ // src/sessions/storage.ts
43576
+ import fs16 from "node:fs";
43577
+ import path18 from "node:path";
43578
+ function sessionsDir(workspacePath) {
43579
+ return path18.join(workspacePath, ".tostudy", "sessions");
43580
+ }
43581
+ async function saveModuleSummary(workspacePath, input2) {
43582
+ const dir = sessionsDir(workspacePath);
43583
+ fs16.mkdirSync(dir, { recursive: true });
43584
+ const filename = `module-${input2.moduleId}-summary.md`;
43585
+ const filePath = path18.join(dir, filename);
43586
+ const header = [
43587
+ "---",
43588
+ `moduleId: ${input2.moduleId}`,
43589
+ `moduleTitle: "${input2.moduleTitle}"`,
43590
+ `savedAt: "${(/* @__PURE__ */ new Date()).toISOString()}"`,
43591
+ "---",
43592
+ ""
43593
+ ].join("\n");
43594
+ fs16.writeFileSync(filePath, header + input2.summary, { mode: 384 });
43595
+ logger23.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
43596
+ return filePath;
43597
+ }
43598
+ async function loadSessionContext(workspacePath) {
43599
+ const dir = sessionsDir(workspacePath);
43600
+ if (!fs16.existsSync(dir)) return { moduleSummaries: [] };
43601
+ const files = fs16.readdirSync(dir).filter((f) => f.startsWith("module-") && f.endsWith("-summary.md")).sort();
43602
+ const summaries = [];
43603
+ for (const file2 of files) {
43604
+ const content = fs16.readFileSync(path18.join(dir, file2), "utf-8");
43605
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
43606
+ if (!frontmatterMatch) continue;
43607
+ const meta3 = frontmatterMatch[1];
43608
+ const body = frontmatterMatch[2].trim();
43609
+ const moduleId = meta3.match(/moduleId:\s*(.+)/)?.[1]?.trim() ?? "";
43610
+ const moduleTitle = meta3.match(/moduleTitle:\s*"(.+)"/)?.[1]?.trim() ?? "";
43611
+ const savedAt = meta3.match(/savedAt:\s*"(.+)"/)?.[1]?.trim() ?? "";
43612
+ summaries.push({ moduleId, moduleTitle, summary: body, savedAt });
43613
+ }
43614
+ return { moduleSummaries: summaries };
43615
+ }
43616
+ var logger23;
43617
+ var init_storage = __esm({
43618
+ "src/sessions/storage.ts"() {
43619
+ "use strict";
43620
+ init_src();
43621
+ logger23 = createLogger("cli:sessions");
43622
+ }
43623
+ });
43624
+
43625
+ // src/commands/compact.ts
43626
+ import fs17 from "node:fs";
43627
+ import { Command as Command25 } from "commander";
43628
+ var logger24, compactCommand;
43629
+ var init_compact = __esm({
43630
+ "src/commands/compact.ts"() {
43631
+ "use strict";
43632
+ init_src();
43633
+ init_session();
43634
+ init_workspace_state();
43635
+ init_storage();
43636
+ init_formatter();
43637
+ logger24 = createLogger("cli:compact");
43638
+ compactCommand = new Command25("compact").description("Save a module study summary (LLM-generated, from stdin)").option("--module-id <id>", "Module ID").option("--module-title <title>", "Module title").option("--json", "Output structured JSON").action(async (opts) => {
43639
+ try {
43640
+ const activeCourse = await requireActiveCourse();
43641
+ const ws = await findWorkspaceState();
43642
+ if (!ws) {
43643
+ if (opts.json) jsonError("no_workspace");
43644
+ error("Nenhum workspace encontrado.");
43645
+ }
43646
+ const summary = fs17.readFileSync("/dev/stdin", "utf-8").trim();
43647
+ if (!summary) {
43648
+ if (opts.json) jsonError("empty_summary", { message: "Summary vazio" });
43649
+ error("Summary vazio. Envie o conteudo via stdin.");
43650
+ }
43651
+ const moduleId = opts.moduleId ?? activeCourse.currentModuleId ?? "unknown";
43652
+ const moduleTitle = opts.moduleTitle ?? `Modulo ${moduleId}`;
43653
+ const filePath = await saveModuleSummary(ws.workspacePath, {
43654
+ moduleId,
43655
+ moduleTitle,
43656
+ summary
43657
+ });
43658
+ logger24.debug("Compact summary saved", { moduleId, filePath });
43659
+ if (opts.json) {
43660
+ output({ saved: true, path: filePath, moduleId }, { json: true });
43661
+ } else {
43662
+ output(`\u2713 Summary salvo: ${filePath}`, { json: false });
43663
+ }
43664
+ } catch (err) {
43665
+ const msg = err instanceof Error ? err.message : String(err);
43666
+ if (msg.includes("process.exit")) return;
43667
+ logger24.warn("compact failed", { error: msg });
43668
+ if (opts.json) jsonError(msg);
43669
+ error(msg);
43670
+ }
43671
+ });
43672
+ }
43673
+ });
43674
+
43675
+ // src/commands/context.ts
43676
+ import { Command as Command26 } from "commander";
43677
+ var logger25, contextCommand;
43678
+ var init_context2 = __esm({
43679
+ "src/commands/context.ts"() {
43680
+ "use strict";
43681
+ init_src();
43682
+ init_workspace_state();
43683
+ init_storage();
43684
+ init_formatter();
43685
+ logger25 = createLogger("cli:context");
43686
+ contextCommand = new Command26("context").description("Load session context (workspace state + module summaries) for LLM consumption").option("--json", "Output structured JSON").action(async (opts) => {
43687
+ try {
43688
+ const ws = await findWorkspaceState();
43689
+ if (!ws) {
43690
+ if (opts.json) jsonError("no_workspace");
43691
+ error("Nenhum workspace encontrado. Rode: tostudy setup");
43692
+ }
43693
+ const sessionCtx = await loadSessionContext(ws.workspacePath);
43694
+ const result = {
43695
+ workspace: {
43696
+ path: ws.workspacePath,
43697
+ courseId: ws.state.courseId,
43698
+ courseTitle: ws.state.courseTitle,
43699
+ enrollmentId: ws.state.enrollmentId,
43700
+ currentLessonId: ws.state.currentLessonId ?? null,
43701
+ currentModuleId: ws.state.currentModuleId ?? null,
43702
+ courseTags: ws.state.courseTags ?? [],
43703
+ courseLevel: ws.state.courseLevel ?? null
43704
+ },
43705
+ moduleSummaries: sessionCtx.moduleSummaries.map((s) => ({
43706
+ moduleId: s.moduleId,
43707
+ moduleTitle: s.moduleTitle,
43708
+ summary: s.summary,
43709
+ savedAt: s.savedAt
43710
+ })),
43711
+ totalModulesCompleted: sessionCtx.moduleSummaries.length
43712
+ };
43713
+ logger25.debug("Context loaded", {
43714
+ courseId: ws.state.courseId,
43715
+ moduleSummaries: sessionCtx.moduleSummaries.length
43716
+ });
43717
+ if (opts.json) {
43718
+ output(result, { json: true });
43719
+ } else {
43720
+ output(`Workspace: ${ws.workspacePath}`, { json: false });
43721
+ output(`Curso: ${ws.state.courseTitle}`, { json: false });
43722
+ output(`Modulos resumidos: ${sessionCtx.moduleSummaries.length}`, { json: false });
43723
+ }
43724
+ } catch (err) {
43725
+ const msg = err instanceof Error ? err.message : String(err);
43726
+ if (msg.includes("process.exit")) return;
43727
+ logger25.warn("context failed", { error: msg });
43728
+ if (opts.json) jsonError(msg);
43729
+ error(msg);
43730
+ }
43731
+ });
43732
+ }
43733
+ });
43734
+
43594
43735
  // src/cli.ts
43595
43736
  var cli_exports = {};
43596
43737
  __export(cli_exports, {
43597
43738
  CLI_VERSION: () => CLI_VERSION,
43598
43739
  createProgram: () => createProgram
43599
43740
  });
43600
- import { Command as Command25 } from "commander";
43741
+ import { Command as Command27 } from "commander";
43601
43742
  function createProgram() {
43602
- const program2 = new Command25();
43743
+ const program2 = new Command27();
43603
43744
  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");
43604
43745
  program2.addCommand(setupCommand);
43605
43746
  program2.addCommand(doctorCommand);
@@ -43625,6 +43766,8 @@ function createProgram() {
43625
43766
  program2.addCommand(exportCommand);
43626
43767
  program2.addCommand(openCommand);
43627
43768
  program2.addCommand(vaultCommand);
43769
+ program2.addCommand(compactCommand);
43770
+ program2.addCommand(contextCommand);
43628
43771
  return program2;
43629
43772
  }
43630
43773
  var CLI_VERSION;
@@ -43655,7 +43798,9 @@ var init_cli = __esm({
43655
43798
  init_brief();
43656
43799
  init_brief_create();
43657
43800
  init_brief_open();
43658
- CLI_VERSION = true ? "0.9.0" : "0.7.1";
43801
+ init_compact();
43802
+ init_context2();
43803
+ CLI_VERSION = true ? "0.10.0" : "0.7.1";
43659
43804
  }
43660
43805
  });
43661
43806