@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.
- package/dist/cli.js +1094 -949
- package/dist/cli.js.map +4 -4
- 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
|
|
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/
|
|
1353
|
-
var
|
|
1354
|
-
__export(
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
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
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
const
|
|
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
|
-
|
|
1393
|
-
|
|
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
|
|
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
|
|
1404
|
-
if (
|
|
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
|
|
1411
|
-
const
|
|
1412
|
-
|
|
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
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
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
|
|
1481
|
-
|
|
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
|
|
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 (!
|
|
1507
|
-
return JSON.parse(
|
|
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
|
-
|
|
1512
|
-
|
|
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 (!
|
|
1484
|
+
if (!fs2.existsSync(profilePath)) return null;
|
|
1549
1485
|
try {
|
|
1550
|
-
return JSON.parse(
|
|
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
|
-
|
|
1558
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
1587
|
-
const stored = JSON.parse(
|
|
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 (
|
|
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 (
|
|
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(
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
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,
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
await
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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(
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
if (!
|
|
1701
|
-
|
|
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 "${
|
|
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/
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1964
|
+
### Se tudo pronto (workspace + curso + contexto):
|
|
2527
1965
|
|
|
2528
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2256
|
+
writeFileSync4(join2(courseDir, "AGENTS.md"), content);
|
|
2798
2257
|
written.push(`.tostudy/courses/${slug}/AGENTS.md`);
|
|
2799
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2289
|
+
writeFileSync4(join2(dir, "AGENTS.md"), content);
|
|
2831
2290
|
written.push(".tostudy/AGENTS.md");
|
|
2832
2291
|
}
|
|
2833
|
-
|
|
2292
|
+
logger2.info("Installed universal /tostudy command", { platform: platform2, files: written });
|
|
2834
2293
|
return written;
|
|
2835
2294
|
}
|
|
2836
|
-
var
|
|
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
|
-
|
|
2301
|
+
logger2 = createLogger("cli:instruction-files");
|
|
2843
2302
|
}
|
|
2844
2303
|
});
|
|
2845
2304
|
|
|
2846
|
-
// src/commands/
|
|
2847
|
-
import { Command
|
|
2848
|
-
import {
|
|
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
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
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
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
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
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
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
|
-
|
|
2924
|
-
|
|
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
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
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
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
const
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
3024
|
-
return JSON.parse(
|
|
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
|
-
|
|
3033
|
-
|
|
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
|
|
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 (!
|
|
2650
|
+
if (!fs5.existsSync(p)) return null;
|
|
3118
2651
|
try {
|
|
3119
|
-
const raw =
|
|
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
|
-
|
|
3128
|
-
|
|
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
|
|
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 (!
|
|
2684
|
+
if (!fs6.existsSync(p)) return {};
|
|
3152
2685
|
try {
|
|
3153
|
-
return JSON.parse(
|
|
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
|
-
|
|
3160
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
3407
|
+
init_workspace_state();
|
|
3408
|
+
init_api();
|
|
3721
3409
|
init_cache();
|
|
3722
3410
|
init_bootstrap();
|
|
3723
|
-
|
|
3411
|
+
init_api2();
|
|
3724
3412
|
init_cache2();
|
|
3725
|
-
|
|
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
|
|
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
|
|
3559
|
+
await writeWorkspaceMarker(process.cwd(), {
|
|
3855
3560
|
courseId: detail.courseId,
|
|
3856
|
-
courseTitle: detail.courseTitle,
|
|
3857
3561
|
enrollmentId,
|
|
3858
|
-
|
|
3859
|
-
|
|
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 =
|
|
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(
|
|
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.
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
5534
|
-
if (pathChunkIndex <
|
|
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) &&
|
|
5543
|
-
const objectName =
|
|
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
|
|
9195
|
+
const path19 = b2.relation.schema + "." + b2.relation.table;
|
|
9491
9196
|
call("*", a, b2);
|
|
9492
|
-
call("*:" +
|
|
9493
|
-
b2.relation.keys.length && call("*:" +
|
|
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 + ":" +
|
|
9496
|
-
b2.relation.keys.length && call(b2.command + ":" +
|
|
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,
|
|
9610
|
-
return (command || "*") + (
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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((
|
|
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((
|
|
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
|
|
13385
|
-
const tableName2 = getTableLikeName(
|
|
13386
|
-
if (typeof tableName2 === "string" && !is(
|
|
13387
|
-
const fromFields = this.getTableLikeFields(
|
|
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,
|
|
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 =
|
|
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
|
|
14181
|
+
let logger26;
|
|
14477
14182
|
if (config2.logger === true) {
|
|
14478
|
-
|
|
14183
|
+
logger26 = new DefaultLogger();
|
|
14479
14184
|
} else if (config2.logger !== false) {
|
|
14480
|
-
|
|
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:
|
|
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,
|
|
14810
|
-
if (!
|
|
14514
|
+
function getElementAtPath(obj, path19) {
|
|
14515
|
+
if (!path19)
|
|
14811
14516
|
return obj;
|
|
14812
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
15413
|
-
for (const seg of
|
|
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
|
|
28108
|
-
if (
|
|
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 (
|
|
28113
|
-
const key =
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
42416
|
+
import path11 from "node:path";
|
|
42436
42417
|
async function setupWorkspace(input2) {
|
|
42437
|
-
const workspacePath =
|
|
42418
|
+
const workspacePath = path11.join(input2.basePath, input2.courseSlug);
|
|
42438
42419
|
for (const dir of WORKSPACE_DIRS) {
|
|
42439
|
-
await fs10.mkdir(
|
|
42420
|
+
await fs10.mkdir(path11.join(workspacePath, dir), { recursive: true });
|
|
42440
42421
|
}
|
|
42441
|
-
const configPath =
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
42610
|
-
await fs11.mkdir(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
42742
|
-
import
|
|
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 ??
|
|
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(
|
|
42750
|
+
await fs12.mkdir(path13.join(workspacePath, dir), { recursive: true });
|
|
42770
42751
|
}
|
|
42771
|
-
const configPath =
|
|
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 ??
|
|
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",
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
42926
|
-
import
|
|
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",
|
|
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 =
|
|
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(
|
|
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 ===
|
|
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
|
|
43042
|
-
import
|
|
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",
|
|
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
|
|
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 =
|
|
43111
|
-
await fs14.mkdir(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
43149
|
-
import
|
|
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",
|
|
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",
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
43741
|
+
import { Command as Command27 } from "commander";
|
|
43601
43742
|
function createProgram() {
|
|
43602
|
-
const program2 = new
|
|
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
|
-
|
|
43801
|
+
init_compact();
|
|
43802
|
+
init_context2();
|
|
43803
|
+
CLI_VERSION = true ? "0.10.0" : "0.7.1";
|
|
43659
43804
|
}
|
|
43660
43805
|
});
|
|
43661
43806
|
|