@tostudy-ai/cli 0.8.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 +2254 -1332
- package/dist/cli.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1289,19 +1289,153 @@ var init_secret_store = __esm({
|
|
|
1289
1289
|
}
|
|
1290
1290
|
});
|
|
1291
1291
|
|
|
1292
|
+
// src/workspace/workspace-marker.ts
|
|
1293
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync } from "node:fs";
|
|
1294
|
+
import os2 from "node:os";
|
|
1295
|
+
import path2 from "node:path";
|
|
1296
|
+
function resolveMarkerPath(workspacePath) {
|
|
1297
|
+
if (path2.basename(workspacePath) === ".tostudy") {
|
|
1298
|
+
return path2.join(workspacePath, "workspace.json");
|
|
1299
|
+
}
|
|
1300
|
+
return path2.join(workspacePath, ".tostudy", "workspace.json");
|
|
1301
|
+
}
|
|
1302
|
+
async function readWorkspaceMarker(workspacePath) {
|
|
1303
|
+
const filePath = resolveMarkerPath(workspacePath);
|
|
1304
|
+
if (!existsSync2(filePath)) return null;
|
|
1305
|
+
try {
|
|
1306
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
1307
|
+
const parsed = JSON.parse(raw);
|
|
1308
|
+
if (!SUPPORTED_VERSIONS.has(parsed.version)) return null;
|
|
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
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
return parsed;
|
|
1313
|
+
} catch {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function writeWorkspaceMarker(workspacePath, input2, options = {}) {
|
|
1318
|
+
const homeDir = path2.resolve(options.homeDir ?? os2.homedir());
|
|
1319
|
+
const resolvedWorkspace = path2.resolve(workspacePath);
|
|
1320
|
+
if (resolvedWorkspace === homeDir || homeDir.startsWith(resolvedWorkspace + path2.sep)) {
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
`Refusing to write workspace marker at ${workspacePath}: it is the home directory or an ancestor of it.`
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
const filePath = resolveMarkerPath(workspacePath);
|
|
1326
|
+
const dir = path2.dirname(filePath);
|
|
1327
|
+
if (!existsSync2(dir)) {
|
|
1328
|
+
mkdirSync2(dir, { recursive: true });
|
|
1329
|
+
}
|
|
1330
|
+
const existing = await readWorkspaceMarker(workspacePath);
|
|
1331
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1332
|
+
const marker = {
|
|
1333
|
+
version: CURRENT_VERSION,
|
|
1334
|
+
courseId: input2.courseId,
|
|
1335
|
+
enrollmentId: input2.enrollmentId,
|
|
1336
|
+
slug: input2.slug,
|
|
1337
|
+
courseTitle: input2.courseTitle,
|
|
1338
|
+
createdAt: existing?.createdAt ?? now,
|
|
1339
|
+
updatedAt: now
|
|
1340
|
+
};
|
|
1341
|
+
writeFileSync2(filePath, JSON.stringify(marker, null, 2), { mode: 384 });
|
|
1342
|
+
return filePath;
|
|
1343
|
+
}
|
|
1344
|
+
var CURRENT_VERSION, SUPPORTED_VERSIONS;
|
|
1345
|
+
var init_workspace_marker = __esm({
|
|
1346
|
+
"src/workspace/workspace-marker.ts"() {
|
|
1347
|
+
"use strict";
|
|
1348
|
+
CURRENT_VERSION = 1;
|
|
1349
|
+
SUPPORTED_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
|
|
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";
|
|
1361
|
+
import os3 from "node:os";
|
|
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);
|
|
1367
|
+
try {
|
|
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;
|
|
1386
|
+
} catch {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
async function findWorkspaceState(startCwd = process.cwd(), homeDir = os3.homedir()) {
|
|
1391
|
+
const home = path3.resolve(homeDir);
|
|
1392
|
+
let current = path3.resolve(startCwd);
|
|
1393
|
+
while (true) {
|
|
1394
|
+
if (current === home) return null;
|
|
1395
|
+
const state = await readWorkspaceState(current);
|
|
1396
|
+
if (state) return { state, workspacePath: current };
|
|
1397
|
+
const parent = path3.dirname(current);
|
|
1398
|
+
if (parent === current) return null;
|
|
1399
|
+
current = parent;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
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.`);
|
|
1406
|
+
}
|
|
1407
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1408
|
+
const merged = {
|
|
1409
|
+
...existing,
|
|
1410
|
+
...update,
|
|
1411
|
+
version: 2,
|
|
1412
|
+
createdAt: existing.createdAt,
|
|
1413
|
+
updatedAt: now
|
|
1414
|
+
};
|
|
1415
|
+
const filePath = resolveMarkerPath(workspacePath);
|
|
1416
|
+
writeFileSync3(filePath, JSON.stringify(merged, null, 2), { mode: 384 });
|
|
1417
|
+
return merged;
|
|
1418
|
+
}
|
|
1419
|
+
var init_workspace_state = __esm({
|
|
1420
|
+
"src/workspace/workspace-state.ts"() {
|
|
1421
|
+
"use strict";
|
|
1422
|
+
init_workspace_marker();
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1292
1426
|
// src/auth/session.ts
|
|
1293
1427
|
import fs2 from "node:fs";
|
|
1294
|
-
import
|
|
1295
|
-
import
|
|
1428
|
+
import path4 from "node:path";
|
|
1429
|
+
import os4 from "node:os";
|
|
1296
1430
|
function getConfigDir(override) {
|
|
1297
1431
|
if (override) return override;
|
|
1298
1432
|
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
1299
|
-
return
|
|
1433
|
+
return path4.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
1300
1434
|
}
|
|
1301
|
-
return
|
|
1435
|
+
return path4.join(os4.homedir(), ".tostudy");
|
|
1302
1436
|
}
|
|
1303
1437
|
function getCourseOnboardingPath(configDir) {
|
|
1304
|
-
return
|
|
1438
|
+
return path4.join(getConfigDir(configDir), "course-onboarding.json");
|
|
1305
1439
|
}
|
|
1306
1440
|
function readCourseOnboardingState(configDir) {
|
|
1307
1441
|
const onboardingPath = getCourseOnboardingPath(configDir);
|
|
@@ -1343,7 +1477,7 @@ async function saveCourseLearnerProfile(course, learnerProfile, artifacts, confi
|
|
|
1343
1477
|
await saveUserProfile(learnerProfile, configDir);
|
|
1344
1478
|
}
|
|
1345
1479
|
function getUserProfilePath(configDir) {
|
|
1346
|
-
return
|
|
1480
|
+
return path4.join(getConfigDir(configDir), "user-profile.json");
|
|
1347
1481
|
}
|
|
1348
1482
|
async function getUserProfile(configDir) {
|
|
1349
1483
|
const profilePath = getUserProfilePath(configDir);
|
|
@@ -1378,13 +1512,13 @@ async function saveSession(session, configDir) {
|
|
|
1378
1512
|
apiUrl: session.apiUrl,
|
|
1379
1513
|
sessionId: session.sessionId
|
|
1380
1514
|
};
|
|
1381
|
-
fs2.writeFileSync(
|
|
1515
|
+
fs2.writeFileSync(path4.join(dir, "config.json"), JSON.stringify(stored, null, 2), {
|
|
1382
1516
|
mode: 384
|
|
1383
1517
|
});
|
|
1384
1518
|
}
|
|
1385
1519
|
async function getSession(configDir) {
|
|
1386
1520
|
const dir = getConfigDir(configDir);
|
|
1387
|
-
const p =
|
|
1521
|
+
const p = path4.join(dir, "config.json");
|
|
1388
1522
|
if (!fs2.existsSync(p)) return null;
|
|
1389
1523
|
const stored = JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
1390
1524
|
const secrets = await loadStoredSessionSecrets(dir);
|
|
@@ -1401,29 +1535,12 @@ async function getSession(configDir) {
|
|
|
1401
1535
|
}
|
|
1402
1536
|
async function clearSession(configDir) {
|
|
1403
1537
|
const dir = getConfigDir(configDir);
|
|
1404
|
-
const p =
|
|
1538
|
+
const p = path4.join(dir, "config.json");
|
|
1405
1539
|
if (fs2.existsSync(p)) fs2.unlinkSync(p);
|
|
1406
|
-
const ap = path2.join(dir, "active-course.json");
|
|
1407
|
-
if (fs2.existsSync(ap)) fs2.unlinkSync(ap);
|
|
1408
1540
|
const onboardingPath = getCourseOnboardingPath(configDir);
|
|
1409
1541
|
if (fs2.existsSync(onboardingPath)) fs2.unlinkSync(onboardingPath);
|
|
1410
1542
|
await deleteStoredSessionSecrets(dir);
|
|
1411
1543
|
}
|
|
1412
|
-
async function getActiveCourse(configDir) {
|
|
1413
|
-
const dir = getConfigDir(configDir);
|
|
1414
|
-
const p = path2.join(dir, "active-course.json");
|
|
1415
|
-
if (!fs2.existsSync(p)) return null;
|
|
1416
|
-
return JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
1417
|
-
}
|
|
1418
|
-
async function setActiveCourse(course, configDir) {
|
|
1419
|
-
const dir = getConfigDir(configDir);
|
|
1420
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
1421
|
-
const onboardingState = await getCourseOnboardingState(course.courseId, configDir);
|
|
1422
|
-
const persistedCourse = onboardingState?.initCompletedAt && !course.lastInitCourseId ? { ...course, lastInitCourseId: course.courseId } : course;
|
|
1423
|
-
fs2.writeFileSync(path2.join(dir, "active-course.json"), JSON.stringify(persistedCourse, null, 2), {
|
|
1424
|
-
mode: 384
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
1544
|
async function refreshCliSession(session, configDir) {
|
|
1428
1545
|
try {
|
|
1429
1546
|
const response = await fetch(`${session.apiUrl}/api/cli/auth/refresh`, {
|
|
@@ -1466,33 +1583,31 @@ async function requireSession(configDir) {
|
|
|
1466
1583
|
}
|
|
1467
1584
|
return session;
|
|
1468
1585
|
}
|
|
1469
|
-
async function requireActiveCourse(
|
|
1470
|
-
const
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
const
|
|
1479
|
-
if (!
|
|
1480
|
-
await
|
|
1481
|
-
await updateCourseOnboardingState(
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
initCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1485
|
-
},
|
|
1486
|
-
configDir
|
|
1487
|
-
);
|
|
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");
|
|
1591
|
+
process.exit(3);
|
|
1592
|
+
}
|
|
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
|
+
});
|
|
1488
1601
|
}
|
|
1489
|
-
async function checkCourseDrift(
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
if (!
|
|
1493
|
-
|
|
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;
|
|
1494
1609
|
return [
|
|
1495
|
-
`\u26A0\uFE0F Curso ativo mudou para "${
|
|
1610
|
+
`\u26A0\uFE0F Curso ativo mudou para "${state.courseTitle}".`,
|
|
1496
1611
|
` Rode \`tostudy init\` para atualizar o contexto do assistente.`,
|
|
1497
1612
|
""
|
|
1498
1613
|
].join("\n");
|
|
@@ -1504,295 +1619,6 @@ var init_session = __esm({
|
|
|
1504
1619
|
}
|
|
1505
1620
|
});
|
|
1506
1621
|
|
|
1507
|
-
// src/onboarding/api.ts
|
|
1508
|
-
async function apiFetch2(url2, token2, init) {
|
|
1509
|
-
const response = await fetch(url2, {
|
|
1510
|
-
...init,
|
|
1511
|
-
headers: {
|
|
1512
|
-
"Content-Type": "application/json",
|
|
1513
|
-
Authorization: `Bearer ${token2}`,
|
|
1514
|
-
...init?.headers
|
|
1515
|
-
}
|
|
1516
|
-
});
|
|
1517
|
-
const body = await response.json();
|
|
1518
|
-
if (!response.ok) {
|
|
1519
|
-
throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
|
|
1520
|
-
}
|
|
1521
|
-
return body;
|
|
1522
|
-
}
|
|
1523
|
-
async function getRemoteEnrollmentOnboarding(input2) {
|
|
1524
|
-
const url2 = new URL(`${input2.apiUrl}/api/cli/onboarding`);
|
|
1525
|
-
url2.searchParams.set("enrollmentId", input2.enrollmentId);
|
|
1526
|
-
const response = await apiFetch2(
|
|
1527
|
-
url2.toString(),
|
|
1528
|
-
input2.token
|
|
1529
|
-
);
|
|
1530
|
-
return response.onboarding;
|
|
1531
|
-
}
|
|
1532
|
-
async function saveRemoteEnrollmentOnboarding(input2) {
|
|
1533
|
-
const response = await apiFetch2(
|
|
1534
|
-
`${input2.apiUrl}/api/cli/onboarding`,
|
|
1535
|
-
input2.token,
|
|
1536
|
-
{
|
|
1537
|
-
method: "POST",
|
|
1538
|
-
body: JSON.stringify({
|
|
1539
|
-
enrollmentId: input2.enrollmentId,
|
|
1540
|
-
learnerBrief: input2.learnerBrief,
|
|
1541
|
-
learnerProfile: input2.learnerProfile
|
|
1542
|
-
})
|
|
1543
|
-
}
|
|
1544
|
-
);
|
|
1545
|
-
return response.onboarding;
|
|
1546
|
-
}
|
|
1547
|
-
var init_api = __esm({
|
|
1548
|
-
"src/onboarding/api.ts"() {
|
|
1549
|
-
"use strict";
|
|
1550
|
-
init_http2();
|
|
1551
|
-
}
|
|
1552
|
-
});
|
|
1553
|
-
|
|
1554
|
-
// src/output/init-template.ts
|
|
1555
|
-
function detectTechFromTags(tags) {
|
|
1556
|
-
const found = /* @__PURE__ */ new Set();
|
|
1557
|
-
const keywords = Object.keys(LANGUAGE_TAGS);
|
|
1558
|
-
const joined = tags.map((t) => t.toLowerCase()).join(" ");
|
|
1559
|
-
for (const kw of keywords) {
|
|
1560
|
-
const pattern = new RegExp(`\\b${kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
1561
|
-
if (pattern.test(joined)) {
|
|
1562
|
-
found.add(kw);
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
return [...found];
|
|
1566
|
-
}
|
|
1567
|
-
function formatCourseLanguage(tags) {
|
|
1568
|
-
if (!tags || tags.length === 0) return "Programa\xE7\xE3o";
|
|
1569
|
-
const direct = tags.map((t) => LANGUAGE_TAGS[t.toLowerCase()]).filter(Boolean);
|
|
1570
|
-
if (direct.length > 0) return direct.slice(0, 3).join(" / ");
|
|
1571
|
-
const detected = detectTechFromTags(tags);
|
|
1572
|
-
const mapped = detected.map((t) => LANGUAGE_TAGS[t]).filter(Boolean);
|
|
1573
|
-
return mapped.length > 0 ? mapped.slice(0, 3).join(" / ") : "Programa\xE7\xE3o";
|
|
1574
|
-
}
|
|
1575
|
-
function formatCourseLevel(level) {
|
|
1576
|
-
const map2 = {
|
|
1577
|
-
beginner: "Iniciante",
|
|
1578
|
-
intermediate: "Intermedi\xE1rio",
|
|
1579
|
-
advanced: "Avan\xE7ado"
|
|
1580
|
-
};
|
|
1581
|
-
return map2[level ?? "intermediate"] ?? "Intermedi\xE1rio";
|
|
1582
|
-
}
|
|
1583
|
-
function formatCourseMethodology(approach) {
|
|
1584
|
-
const map2 = {
|
|
1585
|
-
hybrid: "Teoria e pr\xE1tica",
|
|
1586
|
-
structured: "Estruturado com exerc\xEDcios",
|
|
1587
|
-
exploratory: "Explorat\xF3rio",
|
|
1588
|
-
mentored: "Mentoria guiada",
|
|
1589
|
-
"ai-exploration": "Explora\xE7\xE3o com IA"
|
|
1590
|
-
};
|
|
1591
|
-
return map2[approach] ?? "Teoria e pr\xE1tica";
|
|
1592
|
-
}
|
|
1593
|
-
function formatCourseObjective(description) {
|
|
1594
|
-
if (!description || description.trim() === "") return null;
|
|
1595
|
-
const firstSentence = description.split(/[.!?]/)[0]?.trim();
|
|
1596
|
-
if (!firstSentence) return null;
|
|
1597
|
-
if (firstSentence.length > 200) return firstSentence.slice(0, 200) + "...";
|
|
1598
|
-
return firstSentence;
|
|
1599
|
-
}
|
|
1600
|
-
function buildInitTemplate(userName, course, progress3) {
|
|
1601
|
-
const lines = [];
|
|
1602
|
-
lines.push("# ToStudy CLI \u2014 Instru\xE7\xF5es para o Assistente AI");
|
|
1603
|
-
lines.push("");
|
|
1604
|
-
lines.push("Voc\xEA \xE9 um tutor de programa\xE7\xE3o auxiliando um aluno via terminal.");
|
|
1605
|
-
lines.push("");
|
|
1606
|
-
lines.push("## 1. Estado Atual");
|
|
1607
|
-
lines.push(`- Usu\xE1rio: ${userName}`);
|
|
1608
|
-
lines.push(`- Curso: "${course.title}" (${progress3?.coursePercent ?? course.progress}%)`);
|
|
1609
|
-
if (progress3) {
|
|
1610
|
-
const mod = progress3.currentModule;
|
|
1611
|
-
const les = progress3.currentLesson;
|
|
1612
|
-
lines.push(`- M\xF3dulo ${mod.order}/${mod.totalModules}: "${mod.title}"`);
|
|
1613
|
-
lines.push(`- Li\xE7\xE3o ${les.order}/${les.totalLessons}: "${les.title}"`);
|
|
1614
|
-
} else {
|
|
1615
|
-
lines.push("- Nenhuma li\xE7\xE3o iniciada. Rode `tostudy start` para come\xE7ar.");
|
|
1616
|
-
}
|
|
1617
|
-
const language = formatCourseLanguage(course.tags);
|
|
1618
|
-
const level = formatCourseLevel(course.level);
|
|
1619
|
-
const methodology = formatCourseMethodology(course.teachingApproach);
|
|
1620
|
-
const objective = formatCourseObjective(course.description);
|
|
1621
|
-
lines.push("");
|
|
1622
|
-
lines.push("## 2. Contexto do Curso");
|
|
1623
|
-
lines.push(`- Linguagem: ${language}`);
|
|
1624
|
-
lines.push(`- N\xEDvel: ${level}`);
|
|
1625
|
-
lines.push(`- Metodologia: ${methodology}`);
|
|
1626
|
-
if (objective) lines.push(`- Objetivo: ${objective}`);
|
|
1627
|
-
lines.push("");
|
|
1628
|
-
lines.push("Adapte explica\xE7\xF5es e exemplos para esta stack.");
|
|
1629
|
-
lines.push("Exerc\xEDcios envolvem arquivos de c\xF3digo no workspace do aluno.");
|
|
1630
|
-
lines.push("");
|
|
1631
|
-
lines.push("## 3. Comandos");
|
|
1632
|
-
lines.push("| Comando | Uso |");
|
|
1633
|
-
lines.push("|----------------------------------|----------------------------------|");
|
|
1634
|
-
lines.push("| `tostudy lesson` | Ver conte\xFAdo da li\xE7\xE3o atual |");
|
|
1635
|
-
lines.push("| `tostudy next` | Avan\xE7ar para pr\xF3xima li\xE7\xE3o |");
|
|
1636
|
-
lines.push("| `tostudy hint` | Dica progressiva |");
|
|
1637
|
-
lines.push("| `tostudy validate <arquivo>` | Validar solu\xE7\xE3o do exerc\xEDcio |");
|
|
1638
|
-
lines.push("| `tostudy progress` | Ver progresso detalhado |");
|
|
1639
|
-
lines.push("| `tostudy start` | Iniciar/retomar m\xF3dulo |");
|
|
1640
|
-
lines.push("| `tostudy start-next` | Avan\xE7ar para pr\xF3ximo m\xF3dulo |");
|
|
1641
|
-
lines.push("| `tostudy courses` | Listar cursos matriculados |");
|
|
1642
|
-
lines.push("| `tostudy select <n>` | Trocar curso ativo |");
|
|
1643
|
-
lines.push("| `tostudy doctor` | Diagn\xF3stico de problemas |");
|
|
1644
|
-
lines.push("");
|
|
1645
|
-
lines.push("Adicione `--json` para output estruturado quando precisar parsear.");
|
|
1646
|
-
lines.push("");
|
|
1647
|
-
lines.push("## 4. Regras");
|
|
1648
|
-
lines.push("1. Comece rodando `tostudy lesson` para carregar o conte\xFAdo");
|
|
1649
|
-
lines.push("2. N\xE3o d\xEA respostas diretas \u2014 use `tostudy hint` para dicas progressivas");
|
|
1650
|
-
lines.push("3. Ap\xF3s `tostudy validate`, analise cada crit\xE9rio e guie a corre\xE7\xE3o");
|
|
1651
|
-
lines.push("4. N\xE3o avance com `tostudy next` sem o aluno completar o exerc\xEDcio");
|
|
1652
|
-
lines.push('5. Se algum comando retornar aviso de "curso ativo mudou", rode `tostudy init`');
|
|
1653
|
-
lines.push("");
|
|
1654
|
-
lines.push("## 5. Coexist\xEAncia");
|
|
1655
|
-
lines.push("Este workspace pode ter suas pr\xF3prias instru\xE7\xF5es (CLAUDE.md, AGENTS.md).");
|
|
1656
|
-
lines.push("- Instru\xE7\xF5es do projeto \u2192 decis\xF5es de c\xF3digo (estilo, lint, arquitetura)");
|
|
1657
|
-
lines.push("- Instru\xE7\xF5es do ToStudy \u2192 fluxo de estudo (navega\xE7\xE3o, exerc\xEDcios, valida\xE7\xE3o)");
|
|
1658
|
-
return lines.join("\n");
|
|
1659
|
-
}
|
|
1660
|
-
function resolveTutorMode(level) {
|
|
1661
|
-
if (level === "beginner") return "guided";
|
|
1662
|
-
if (level === "advanced") return "direct";
|
|
1663
|
-
return "balanced";
|
|
1664
|
-
}
|
|
1665
|
-
function formatTutorMode(mode) {
|
|
1666
|
-
const map2 = {
|
|
1667
|
-
guided: "Guiado",
|
|
1668
|
-
balanced: "Equilibrado",
|
|
1669
|
-
direct: "Direto"
|
|
1670
|
-
};
|
|
1671
|
-
return map2[mode];
|
|
1672
|
-
}
|
|
1673
|
-
function buildTutorPersonalizationSection(course, learnerProfile) {
|
|
1674
|
-
const mode = resolveTutorMode(course.level);
|
|
1675
|
-
const lines = [];
|
|
1676
|
-
lines.push("## 6. Personaliza\xE7\xE3o do Tutor");
|
|
1677
|
-
lines.push(`- Modo inicial do tutor: ${formatTutorMode(mode)}`);
|
|
1678
|
-
lines.push(
|
|
1679
|
-
"- Na primeira intera\xE7\xE3o, apresente a escolha entre cen\xE1rio fict\xEDcio do curso e adapta\xE7\xE3o ao contexto real do aluno."
|
|
1680
|
-
);
|
|
1681
|
-
lines.push(
|
|
1682
|
-
`- O aluno atual j\xE1 optou por: ${learnerProfile.adaptToRealContext ? "Adaptar para o contexto real" : "Manter o cen\xE1rio fict\xEDcio"}`
|
|
1683
|
-
);
|
|
1684
|
-
lines.push(`- Segmento: ${learnerProfile.segment}`);
|
|
1685
|
-
lines.push(`- Empresa: ${learnerProfile.company}`);
|
|
1686
|
-
lines.push(`- Produtos/servi\xE7os: ${learnerProfile.productsOrServices}`);
|
|
1687
|
-
lines.push(`- Regi\xE3o: ${learnerProfile.region}`);
|
|
1688
|
-
lines.push(`- Equipe: ${learnerProfile.team}`);
|
|
1689
|
-
lines.push(`- Objetivo: ${learnerProfile.goal}`);
|
|
1690
|
-
lines.push(`- N\xEDvel declarado do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
|
|
1691
|
-
if (learnerProfile.adaptToRealContext) {
|
|
1692
|
-
lines.push(
|
|
1693
|
-
"- Use o contexto do aluno como padr\xE3o para explica\xE7\xF5es, exemplos, exerc\xEDcios e framing."
|
|
1694
|
-
);
|
|
1695
|
-
lines.push(
|
|
1696
|
-
"- Troque nomes, entreg\xE1veis e restri\xE7\xF5es do cen\xE1rio fict\xEDcio por equivalentes reais."
|
|
1697
|
-
);
|
|
1698
|
-
lines.push(
|
|
1699
|
-
"- Preserve objetivos pedag\xF3gicos, crit\xE9rios de valida\xE7\xE3o e progress\xE3o de dificuldade."
|
|
1700
|
-
);
|
|
1701
|
-
} else {
|
|
1702
|
-
lines.push(
|
|
1703
|
-
"- Preserve o cen\xE1rio fict\xEDcio como padr\xE3o e conecte os conceitos ao neg\xF3cio real quando isso ajudar."
|
|
1704
|
-
);
|
|
1705
|
-
lines.push(
|
|
1706
|
-
"- Se o aluno quiser migrar para o contexto real depois, oriente a rodar `tostudy init` novamente."
|
|
1707
|
-
);
|
|
1708
|
-
}
|
|
1709
|
-
if (mode === "guided") {
|
|
1710
|
-
lines.push(
|
|
1711
|
-
"- No modo guiado, explique o porqu\xEA de cada passo, proponha checkpoints curtos e confirme entendimento antes de avan\xE7ar."
|
|
1712
|
-
);
|
|
1713
|
-
} else if (mode === "direct") {
|
|
1714
|
-
lines.push(
|
|
1715
|
-
"- Mesmo no modo mais direto, continue sem dar a resposta pronta e preserve a progress\xE3o pedag\xF3gica."
|
|
1716
|
-
);
|
|
1717
|
-
} else {
|
|
1718
|
-
lines.push(
|
|
1719
|
-
"- No modo equilibrado, combine explica\xE7\xF5es curtas com autonomia entre checkpoints."
|
|
1720
|
-
);
|
|
1721
|
-
}
|
|
1722
|
-
return lines.join("\n");
|
|
1723
|
-
}
|
|
1724
|
-
function buildLearnerBrief(userName, course, learnerProfile) {
|
|
1725
|
-
const lines = [];
|
|
1726
|
-
lines.push("# ToStudy CLI \u2014 Brief do Aluno");
|
|
1727
|
-
lines.push("");
|
|
1728
|
-
lines.push("Este documento resume o contexto real do aluno para orientar exemplos e exercicios.");
|
|
1729
|
-
lines.push("");
|
|
1730
|
-
lines.push("## 1. Identificacao");
|
|
1731
|
-
lines.push(`- Aluno: ${userName}`);
|
|
1732
|
-
lines.push(`- Curso: "${course.title}"`);
|
|
1733
|
-
lines.push(`- Segmento: ${learnerProfile.segment}`);
|
|
1734
|
-
lines.push(`- Empresa: ${learnerProfile.company}`);
|
|
1735
|
-
lines.push(`- Produtos/servicos: ${learnerProfile.productsOrServices}`);
|
|
1736
|
-
lines.push(`- Regiao: ${learnerProfile.region}`);
|
|
1737
|
-
lines.push(`- Equipe: ${learnerProfile.team}`);
|
|
1738
|
-
lines.push("");
|
|
1739
|
-
lines.push("## 2. Objetivo e Contexto");
|
|
1740
|
-
lines.push(`- Objetivo principal: ${learnerProfile.goal}`);
|
|
1741
|
-
lines.push(`- Nivel do aluno: ${formatCourseLevel(learnerProfile.learnerLevel)}`);
|
|
1742
|
-
lines.push(
|
|
1743
|
-
`- Adaptar curso ao contexto real: ${learnerProfile.adaptToRealContext ? "Sim" : "Nao"}`
|
|
1744
|
-
);
|
|
1745
|
-
lines.push("");
|
|
1746
|
-
lines.push("## 3. Como usar este brief");
|
|
1747
|
-
lines.push("- Reutilize este contexto para adaptar exemplos, cenarios e exercicios.");
|
|
1748
|
-
lines.push("- Preserve a proposta pedagogica do curso antes de customizar o contexto.");
|
|
1749
|
-
lines.push(
|
|
1750
|
-
"- Se algo mudar no negocio do aluno, rode `tostudy init` novamente para atualizar o brief."
|
|
1751
|
-
);
|
|
1752
|
-
return lines.join("\n");
|
|
1753
|
-
}
|
|
1754
|
-
function buildInitArtifacts(userName, course, progress3, learnerProfile) {
|
|
1755
|
-
return {
|
|
1756
|
-
tutorInstructions: [
|
|
1757
|
-
buildInitTemplate(userName, course, progress3),
|
|
1758
|
-
buildTutorPersonalizationSection(course, learnerProfile)
|
|
1759
|
-
].join("\n\n"),
|
|
1760
|
-
learnerBrief: buildLearnerBrief(userName, course, learnerProfile)
|
|
1761
|
-
};
|
|
1762
|
-
}
|
|
1763
|
-
var LANGUAGE_TAGS;
|
|
1764
|
-
var init_init_template = __esm({
|
|
1765
|
-
"src/output/init-template.ts"() {
|
|
1766
|
-
"use strict";
|
|
1767
|
-
LANGUAGE_TAGS = {
|
|
1768
|
-
javascript: "JavaScript",
|
|
1769
|
-
typescript: "TypeScript",
|
|
1770
|
-
python: "Python",
|
|
1771
|
-
react: "React",
|
|
1772
|
-
"react-native": "React Native",
|
|
1773
|
-
node: "Node.js",
|
|
1774
|
-
nodejs: "Node.js",
|
|
1775
|
-
go: "Go",
|
|
1776
|
-
rust: "Rust",
|
|
1777
|
-
java: "Java",
|
|
1778
|
-
csharp: "C#",
|
|
1779
|
-
"c#": "C#",
|
|
1780
|
-
cpp: "C++",
|
|
1781
|
-
ruby: "Ruby",
|
|
1782
|
-
php: "PHP",
|
|
1783
|
-
swift: "Swift",
|
|
1784
|
-
kotlin: "Kotlin",
|
|
1785
|
-
html: "HTML",
|
|
1786
|
-
css: "CSS",
|
|
1787
|
-
sql: "SQL",
|
|
1788
|
-
vue: "Vue.js",
|
|
1789
|
-
angular: "Angular",
|
|
1790
|
-
nextjs: "Next.js",
|
|
1791
|
-
svelte: "Svelte"
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
});
|
|
1795
|
-
|
|
1796
1622
|
// src/output/formatter.ts
|
|
1797
1623
|
function output(data, opts) {
|
|
1798
1624
|
if (opts.json) {
|
|
@@ -1808,6 +1634,13 @@ function error(msg, code = 1) {
|
|
|
1808
1634
|
`);
|
|
1809
1635
|
process.exit(code);
|
|
1810
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
|
+
}
|
|
1811
1644
|
function progressBar(percent, width = 20) {
|
|
1812
1645
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
1813
1646
|
const filled = Math.round(clamped / 100 * width);
|
|
@@ -1995,192 +1828,40 @@ var init_formatter = __esm({
|
|
|
1995
1828
|
}
|
|
1996
1829
|
});
|
|
1997
1830
|
|
|
1998
|
-
// src/
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
try {
|
|
2003
|
-
const remote = await getRemoteEnrollmentOnboarding({
|
|
2004
|
-
apiUrl,
|
|
2005
|
-
token: token2,
|
|
2006
|
-
enrollmentId: activeCourse.enrollmentId
|
|
2007
|
-
});
|
|
2008
|
-
if (remote?.learnerProfile) {
|
|
2009
|
-
const artifacts = buildInitArtifacts(userName, courseData, null, remote.learnerProfile);
|
|
2010
|
-
await saveCourseLearnerProfile(activeCourse, remote.learnerProfile, artifacts);
|
|
2011
|
-
console.log(` \u2713 Perfil de aprendizagem sincronizado (${remote.source})`);
|
|
2012
|
-
}
|
|
2013
|
-
} catch (err) {
|
|
2014
|
-
logger2.warn("Failed to sync remote profile", {
|
|
2015
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
var logger2, DEFAULT_API_URL, PORT, loginCommand;
|
|
2020
|
-
var init_login = __esm({
|
|
2021
|
-
"src/commands/login.ts"() {
|
|
2022
|
-
"use strict";
|
|
2023
|
-
init_src();
|
|
2024
|
-
init_courses();
|
|
2025
|
-
init_http2();
|
|
2026
|
-
init_oauth_server();
|
|
2027
|
-
init_session();
|
|
2028
|
-
init_api();
|
|
2029
|
-
init_init_template();
|
|
2030
|
-
init_formatter();
|
|
2031
|
-
logger2 = createLogger("cli:login");
|
|
2032
|
-
DEFAULT_API_URL = "https://tostudy.ai";
|
|
2033
|
-
PORT = 9876;
|
|
2034
|
-
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) => {
|
|
2035
|
-
const apiUrl = opts.apiUrl;
|
|
2036
|
-
if (opts.manual) {
|
|
2037
|
-
console.log(`
|
|
2038
|
-
Acesse este endere\xE7o no seu browser:`);
|
|
2039
|
-
console.log(` ${apiUrl}/api/cli/auth/authorize?port=${PORT}
|
|
2040
|
-
`);
|
|
2041
|
-
console.log(" Ap\xF3s autorizar, o browser ser\xE1 redirecionado e o login ser\xE1 conclu\xEDdo.");
|
|
2042
|
-
console.log(" Certifique-se de rodar este comando sem --manual para o fluxo autom\xE1tico.\n");
|
|
2043
|
-
return;
|
|
2044
|
-
}
|
|
2045
|
-
console.log("\n Abrindo browser para autentica\xE7\xE3o...\n");
|
|
2046
|
-
const serverPromise = startCallbackServer(PORT);
|
|
2047
|
-
const authUrl = `${apiUrl}/api/cli/auth/authorize?port=${PORT}`;
|
|
2048
|
-
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
2049
|
-
execFile2(openCmd, [authUrl], (err) => {
|
|
2050
|
-
if (err) {
|
|
2051
|
-
console.log(` N\xE3o foi poss\xEDvel abrir o browser automaticamente.`);
|
|
2052
|
-
console.log(` Abra manualmente: ${authUrl}
|
|
2053
|
-
`);
|
|
2054
|
-
}
|
|
2055
|
-
});
|
|
2056
|
-
try {
|
|
2057
|
-
const { code } = await serverPromise;
|
|
2058
|
-
const res = await fetch(`${apiUrl}/api/cli/auth/exchange`, {
|
|
2059
|
-
method: "POST",
|
|
2060
|
-
headers: { "Content-Type": "application/json" },
|
|
2061
|
-
body: JSON.stringify({ code })
|
|
2062
|
-
});
|
|
2063
|
-
if (!res.ok) {
|
|
2064
|
-
const body = await res.json();
|
|
2065
|
-
error(body.error ?? "Falha na autentica\xE7\xE3o");
|
|
2066
|
-
}
|
|
2067
|
-
const { token: token2, refreshToken, sessionId, userId, userName, expiresAt } = await res.json();
|
|
2068
|
-
await saveSession({ token: token2, refreshToken, sessionId, userId, userName, expiresAt, apiUrl });
|
|
2069
|
-
console.log(`
|
|
2070
|
-
\u2713 Logado como ${userName}`);
|
|
2071
|
-
try {
|
|
2072
|
-
const data = createHttpProvider(apiUrl, token2);
|
|
2073
|
-
const courses3 = await listCourses({ userId }, { data, logger: logger2 });
|
|
2074
|
-
if (courses3.length > 0) {
|
|
2075
|
-
console.log(` \u2713 ${courses3.length} curso(s) matriculado(s)`);
|
|
2076
|
-
const activeCourse = await getActiveCourse();
|
|
2077
|
-
const targetCourse = activeCourse && courses3.find((c) => c.courseId === activeCourse.courseId) ? activeCourse : null;
|
|
2078
|
-
if (!targetCourse && courses3.length === 1) {
|
|
2079
|
-
const only = courses3[0];
|
|
2080
|
-
const newActive = {
|
|
2081
|
-
courseId: only.courseId,
|
|
2082
|
-
courseTitle: only.title,
|
|
2083
|
-
enrollmentId: only.enrollmentId,
|
|
2084
|
-
courseTags: only.tags,
|
|
2085
|
-
courseLevel: only.level
|
|
2086
|
-
};
|
|
2087
|
-
await setActiveCourse(newActive);
|
|
2088
|
-
console.log(` \u2713 Curso ativo: ${only.title} (${only.progress}%)`);
|
|
2089
|
-
await syncRemoteProfile(apiUrl, token2, newActive, userName, courses3[0]);
|
|
2090
|
-
} else if (targetCourse) {
|
|
2091
|
-
const matched = courses3.find((c) => c.courseId === targetCourse.courseId);
|
|
2092
|
-
if (matched) {
|
|
2093
|
-
await syncRemoteProfile(apiUrl, token2, targetCourse, userName, matched);
|
|
2094
|
-
}
|
|
2095
|
-
} else {
|
|
2096
|
-
console.log(` \u2192 Rode \`tostudy setup\` para configurar seu ambiente`);
|
|
2097
|
-
}
|
|
2098
|
-
} else {
|
|
2099
|
-
console.log(` \u2192 Nenhum curso matriculado. Acesse tostudy.ai para se matricular.`);
|
|
2100
|
-
}
|
|
2101
|
-
} catch (syncErr) {
|
|
2102
|
-
logger2.warn("Post-login sync failed", {
|
|
2103
|
-
error: syncErr instanceof Error ? syncErr.message : String(syncErr)
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
console.log("");
|
|
2107
|
-
} catch (err) {
|
|
2108
|
-
const msg = err instanceof Error ? err.message : "Login falhou";
|
|
2109
|
-
error(msg);
|
|
2110
|
-
}
|
|
2111
|
-
});
|
|
2112
|
-
}
|
|
2113
|
-
});
|
|
2114
|
-
|
|
2115
|
-
// src/commands/logout.ts
|
|
2116
|
-
import { Command as Command2 } from "commander";
|
|
2117
|
-
var logoutCommand;
|
|
2118
|
-
var init_logout = __esm({
|
|
2119
|
-
"src/commands/logout.ts"() {
|
|
2120
|
-
"use strict";
|
|
2121
|
-
init_session();
|
|
2122
|
-
logoutCommand = new Command2("logout").description("Remove token local").action(async () => {
|
|
2123
|
-
await clearSession();
|
|
2124
|
-
console.log("\n Deslogado com sucesso.\n");
|
|
2125
|
-
});
|
|
2126
|
-
}
|
|
1831
|
+
// src/installer/ide-detector.ts
|
|
1832
|
+
var ide_detector_exports = {};
|
|
1833
|
+
__export(ide_detector_exports, {
|
|
1834
|
+
detectIDEs: () => detectIDEs
|
|
2127
1835
|
});
|
|
2128
|
-
|
|
2129
|
-
// src/installer/node-detector.ts
|
|
2130
1836
|
import { execFileSync } from "node:child_process";
|
|
2131
|
-
function detectNode() {
|
|
2132
|
-
try {
|
|
2133
|
-
const version3 = execFileSync("node", ["--version"], {
|
|
2134
|
-
encoding: "utf-8"
|
|
2135
|
-
}).trim();
|
|
2136
|
-
let nodePath = null;
|
|
2137
|
-
try {
|
|
2138
|
-
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
2139
|
-
nodePath = execFileSync(whichCmd, ["node"], { encoding: "utf-8" }).trim();
|
|
2140
|
-
} catch {
|
|
2141
|
-
}
|
|
2142
|
-
const major = parseInt(version3.replace("v", ""), 10);
|
|
2143
|
-
return { installed: true, version: version3, meetsMinimum: major >= 18, path: nodePath };
|
|
2144
|
-
} catch {
|
|
2145
|
-
return { installed: false, version: null, meetsMinimum: false, path: null };
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
var init_node_detector = __esm({
|
|
2149
|
-
"src/installer/node-detector.ts"() {
|
|
2150
|
-
"use strict";
|
|
2151
|
-
}
|
|
2152
|
-
});
|
|
2153
|
-
|
|
2154
|
-
// src/installer/ide-detector.ts
|
|
2155
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2156
1837
|
import fs3 from "node:fs";
|
|
2157
|
-
import
|
|
2158
|
-
import
|
|
1838
|
+
import path5 from "node:path";
|
|
1839
|
+
import os5 from "node:os";
|
|
2159
1840
|
function checkExists(p) {
|
|
2160
1841
|
return fs3.existsSync(p);
|
|
2161
1842
|
}
|
|
2162
1843
|
function detectIDEs() {
|
|
2163
|
-
const home =
|
|
1844
|
+
const home = os5.homedir();
|
|
2164
1845
|
const ides = [
|
|
2165
1846
|
// Claude Code: detected by presence of 'claude' binary
|
|
2166
1847
|
{
|
|
2167
1848
|
name: "Claude Code",
|
|
2168
1849
|
id: "claude-code",
|
|
2169
1850
|
detected: false,
|
|
2170
|
-
configPath:
|
|
1851
|
+
configPath: path5.join(home, ".mcp.json")
|
|
2171
1852
|
},
|
|
2172
1853
|
// Cursor: ~/.cursor directory
|
|
2173
1854
|
{
|
|
2174
1855
|
name: "Cursor",
|
|
2175
1856
|
id: "cursor",
|
|
2176
|
-
detected: checkExists(
|
|
2177
|
-
configPath:
|
|
1857
|
+
detected: checkExists(path5.join(home, ".cursor")),
|
|
1858
|
+
configPath: path5.join(home, ".cursor", "mcp.json")
|
|
2178
1859
|
},
|
|
2179
1860
|
// VS Code: ~/.vscode directory
|
|
2180
1861
|
{
|
|
2181
1862
|
name: "VS Code",
|
|
2182
1863
|
id: "vscode",
|
|
2183
|
-
detected: checkExists(
|
|
1864
|
+
detected: checkExists(path5.join(home, ".vscode")),
|
|
2184
1865
|
configPath: ".vscode/mcp.json"
|
|
2185
1866
|
},
|
|
2186
1867
|
// Claude Desktop: platform-specific config dir
|
|
@@ -2188,29 +1869,29 @@ function detectIDEs() {
|
|
|
2188
1869
|
name: "Claude Desktop",
|
|
2189
1870
|
id: "desktop",
|
|
2190
1871
|
detected: checkExists(
|
|
2191
|
-
process.platform === "darwin" ?
|
|
1872
|
+
process.platform === "darwin" ? path5.join(home, "Library", "Application Support", "Claude") : process.platform === "win32" ? path5.join(process.env["APPDATA"] ?? home, "Claude") : path5.join(home, ".config", "claude")
|
|
2192
1873
|
),
|
|
2193
|
-
configPath: process.platform === "darwin" ?
|
|
1874
|
+
configPath: process.platform === "darwin" ? path5.join(
|
|
2194
1875
|
home,
|
|
2195
1876
|
"Library",
|
|
2196
1877
|
"Application Support",
|
|
2197
1878
|
"Claude",
|
|
2198
1879
|
"claude_desktop_config.json"
|
|
2199
|
-
) : process.platform === "win32" ?
|
|
1880
|
+
) : process.platform === "win32" ? path5.join(process.env["APPDATA"] ?? home, "Claude", "claude_desktop_config.json") : path5.join(home, ".config", "claude", "claude_desktop_config.json")
|
|
2200
1881
|
},
|
|
2201
1882
|
// Windsurf: ~/.codeium/windsurf
|
|
2202
1883
|
{
|
|
2203
1884
|
name: "Windsurf",
|
|
2204
1885
|
id: "windsurf",
|
|
2205
|
-
detected: checkExists(
|
|
2206
|
-
configPath:
|
|
1886
|
+
detected: checkExists(path5.join(home, ".codeium", "windsurf")),
|
|
1887
|
+
configPath: path5.join(home, ".codeium", "windsurf", "mcp_config.json")
|
|
2207
1888
|
},
|
|
2208
1889
|
// OpenCode: ~/.opencode
|
|
2209
1890
|
{
|
|
2210
1891
|
name: "OpenCode",
|
|
2211
1892
|
id: "opencode",
|
|
2212
|
-
detected: checkExists(
|
|
2213
|
-
configPath:
|
|
1893
|
+
detected: checkExists(path5.join(home, ".opencode")),
|
|
1894
|
+
configPath: path5.join(home, ".opencode", "opencode.json")
|
|
2214
1895
|
},
|
|
2215
1896
|
// Manual (always available as fallback)
|
|
2216
1897
|
{
|
|
@@ -2223,7 +1904,7 @@ function detectIDEs() {
|
|
|
2223
1904
|
const claudeIde = ides.find((ide) => ide.id === "claude-code");
|
|
2224
1905
|
if (claudeIde) {
|
|
2225
1906
|
try {
|
|
2226
|
-
|
|
1907
|
+
execFileSync("which", ["claude"], { encoding: "utf-8" });
|
|
2227
1908
|
claudeIde.detected = true;
|
|
2228
1909
|
} catch {
|
|
2229
1910
|
}
|
|
@@ -2236,418 +1917,362 @@ var init_ide_detector = __esm({
|
|
|
2236
1917
|
}
|
|
2237
1918
|
});
|
|
2238
1919
|
|
|
2239
|
-
// src/
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
const command = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
2256
|
-
await new Promise((resolve, reject) => {
|
|
2257
|
-
const child = spawnImpl(command, ["-y", "@tostudy-ai/mcp-setup", "--url", session.apiUrl], {
|
|
2258
|
-
stdio: "inherit",
|
|
2259
|
-
env: {
|
|
2260
|
-
...process.env,
|
|
2261
|
-
TOSTUDY_API_KEY: token2
|
|
2262
|
-
}
|
|
2263
|
-
});
|
|
2264
|
-
child.once("error", reject);
|
|
2265
|
-
child.once("exit", (code) => {
|
|
2266
|
-
if (code === 0) {
|
|
2267
|
-
resolve();
|
|
2268
|
-
return;
|
|
2269
|
-
}
|
|
2270
|
-
reject(new Error(`MCP setup failed with exit code ${code ?? "unknown"}`));
|
|
2271
|
-
});
|
|
2272
|
-
});
|
|
2273
|
-
}
|
|
2274
|
-
var init_mcp_setup = __esm({
|
|
2275
|
-
"src/installer/mcp-setup.ts"() {
|
|
2276
|
-
"use strict";
|
|
2277
|
-
}
|
|
2278
|
-
});
|
|
2279
|
-
|
|
2280
|
-
// src/workspace/instruction-files.ts
|
|
2281
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2282
|
-
import { join as join2 } from "node:path";
|
|
2283
|
-
import os4 from "node:os";
|
|
2284
|
-
function slugify(title) {
|
|
2285
|
-
return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
1920
|
+
// src/output/instruction-template-v3.ts
|
|
1921
|
+
function renderCourseInstruction(input2) {
|
|
1922
|
+
const sections = [];
|
|
1923
|
+
sections.push(renderTitle(input2));
|
|
1924
|
+
sections.push(renderWhoYouAre(input2.tutorPersona));
|
|
1925
|
+
sections.push(renderVoiceRules());
|
|
1926
|
+
sections.push(renderWhoIsStudent(input2.studentBrief, input2.enrollmentProfile));
|
|
1927
|
+
sections.push(renderHowToAdapt(input2.enrollmentProfile));
|
|
1928
|
+
sections.push(renderPrecedence());
|
|
1929
|
+
sections.push(renderWhereStudentIs(input2.progress));
|
|
1930
|
+
sections.push(renderHowToConduct());
|
|
1931
|
+
sections.push(renderSilentTools());
|
|
1932
|
+
sections.push(renderHandlingSituations());
|
|
1933
|
+
sections.push(renderTechnicalReference());
|
|
1934
|
+
sections.push(renderFooter(input2.course));
|
|
1935
|
+
return sections.join("\n\n") + "\n";
|
|
2286
1936
|
}
|
|
2287
|
-
function
|
|
1937
|
+
function renderUniversalInstruction() {
|
|
2288
1938
|
const sections = [];
|
|
2289
|
-
sections.push(`#
|
|
2290
|
-
sections.push(`##
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
sections.push(
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
tostudy
|
|
2337
|
-
tostudy start \u2192 Carregar m\xF3dulo atual
|
|
2338
|
-
|
|
2339
|
-
LOOP DE ESTUDO (repetir para cada li\xE7\xE3o):
|
|
2340
|
-
tostudy lesson \u2192 Ler conte\xFAdo da li\xE7\xE3o
|
|
2341
|
-
|
|
2342
|
-
[Se EXERC\xCDCIO]:
|
|
2343
|
-
\u2192 Aluno implementa a solu\xE7\xE3o
|
|
2344
|
-
\u2192 tostudy hint \u2192 Dica progressiva (se travado)
|
|
2345
|
-
\u2192 tostudy validate <arquivo> \u2192 Validar solu\xE7\xE3o
|
|
2346
|
-
\u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
|
|
2347
|
-
\u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2348
|
-
|
|
2349
|
-
[Se TEORIA/TEXTO]:
|
|
2350
|
-
\u2192 Explicar conceitos-chave
|
|
2351
|
-
\u2192 Fazer perguntas para verificar entendimento
|
|
2352
|
-
\u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2353
|
-
|
|
2354
|
-
[Se QUIZ/CHECKPOINT]:
|
|
2355
|
-
\u2192 Aluno escreve respostas em arquivo (ex: checkpoint.md)
|
|
2356
|
-
\u2192 tostudy validate checkpoint.md
|
|
2357
|
-
\u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2358
|
-
|
|
2359
|
-
FIM DO M\xD3DULO:
|
|
2360
|
-
\u2192 Parabenizar o aluno
|
|
2361
|
-
\u2192 tostudy start \u2192 Carregar pr\xF3ximo m\xF3dulo
|
|
2362
|
-
\`\`\``);
|
|
2363
|
-
sections.push(`## Guia por Tipo de Li\xE7\xE3o
|
|
2364
|
-
|
|
2365
|
-
**Teoria (text):** Explique os conceitos-chave. Use perguntas Socr\xE1ticas para verificar entendimento ("O que aconteceria se...?"). S\xF3 avance quando o aluno demonstrar compreens\xE3o.
|
|
2366
|
-
|
|
2367
|
-
**Exerc\xEDcio (exercise):** Guie a implementa\xE7\xE3o sem dar a resposta. Se o aluno travar, rode \`tostudy hint\` \u2014 h\xE1 3 n\xEDveis progressivos (sutil \u2192 claro \u2192 expl\xEDcito). Sempre valide com \`tostudy validate <arquivo>\` antes de avan\xE7ar.
|
|
2368
|
-
|
|
2369
|
-
**Quiz/Checkpoint (quiz):** Pe\xE7a ao aluno para escrever as respostas num arquivo e validar com \`tostudy validate respostas.md\`. Discuta as respostas ap\xF3s a valida\xE7\xE3o.
|
|
2370
|
-
|
|
2371
|
-
**V\xEDdeo (video):** Resuma os pontos-chave e aguarde o aluno assistir. Depois discuta o conte\xFAdo.`);
|
|
2372
|
-
const rules = [
|
|
2373
|
-
"**Nunca d\xEA a resposta direta** \u2014 guie com perguntas e dicas progressivas",
|
|
2374
|
-
"**Hints primeiro** \u2014 sempre rode `tostudy hint` antes de explicar a solu\xE7\xE3o",
|
|
2375
|
-
"**Valide antes de avan\xE7ar** \u2014 exerc\xEDcios exigem `tostudy validate` com nota de aprova\xE7\xE3o",
|
|
2376
|
-
"**Celebre progresso** \u2014 reconhe\xE7a quando o aluno completa li\xE7\xF5es e m\xF3dulos",
|
|
2377
|
-
"**Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es",
|
|
2378
|
-
"**Adapte ao n\xEDvel** \u2014 ajuste profundidade e exemplos conforme o perfil do aluno",
|
|
2379
|
-
"**Sempre rode `tostudy lesson`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material",
|
|
2380
|
-
'**Ignore erros de hook** \u2014 se aparecer "Stop hook error" ou "ECONNREFUSED", ignore completamente. \xC9 um problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy. Nunca mencione esses erros ao aluno.'
|
|
2381
|
-
];
|
|
2382
|
-
if (learner?.adaptToRealContext) {
|
|
2383
|
-
rules.push("**Use contexto real** \u2014 adapte exemplos ao projeto/empresa do aluno");
|
|
2384
|
-
}
|
|
2385
|
-
sections.push(`## Regras de Ouro
|
|
2386
|
-
|
|
2387
|
-
${rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`);
|
|
2388
|
-
sections.push(`## Quando Algo D\xE1 Errado
|
|
2389
|
-
|
|
2390
|
-
| Situa\xE7\xE3o | O que fazer |
|
|
2391
|
-
|----------|-------------|
|
|
2392
|
-
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2393
|
-
| \`tostudy start\` mostra "rode tostudy init" | N\xE3o interrompa a aula. Continue ensinando normalmente. Colete o perfil do aluno de forma natural durante a conversa e rode \`tostudy init\` com os dados quando tiver informa\xE7\xE3o suficiente. |
|
|
2394
|
-
| "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
|
|
2395
|
-
| Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
|
|
2396
|
-
| Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2397
|
-
| "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |`);
|
|
2398
|
-
if (ctx.workspaceReady !== false) {
|
|
2399
|
-
const home = os4.homedir();
|
|
2400
|
-
const cwd = process.cwd();
|
|
2401
|
-
const isNamespaced = ctx.workspacePath === join2(cwd, ".tostudy");
|
|
2402
|
-
const isFlatCwd = ctx.workspacePath === cwd;
|
|
2403
|
-
const displayPath = ctx.workspacePath ? ctx.workspacePath.startsWith(home) ? ctx.workspacePath.replace(home, "~") : ctx.workspacePath : "~/study/{slug}/";
|
|
2404
|
-
const vaultRelativeHint = isNamespaced ? "../vault-<slug>/ \u2190 Vault Obsidian (fora de .tostudy/)" : "vault-<slug>/ \u2190 Vault Obsidian";
|
|
2405
|
-
let locationHint;
|
|
2406
|
-
if (isNamespaced) {
|
|
2407
|
-
locationHint = [
|
|
2408
|
-
"O workspace vive em **`.tostudy/`** nesta pasta \u2014 isolado do resto do projeto.",
|
|
2409
|
-
"Os arquivos do projeto (src/, README.md, AGENTS.md...) permanecem intactos.",
|
|
2410
|
-
"Os exerc\xEDcios s\xE3o extra\xEDdos dentro de `.tostudy/exercises/` e o assistente AI enxerga tudo normalmente.",
|
|
2411
|
-
"",
|
|
2412
|
-
"> \u26A0\uFE0F O **vault Obsidian** fica em `vault-<slug>/` **fora** de `.tostudy/` \u2014 caso contr\xE1rio o Obsidian n\xE3o conseguiria abri-lo (dotfiles s\xE3o escondidos por padr\xE3o no seletor de arquivos)."
|
|
2413
|
-
].join("\n");
|
|
2414
|
-
} else if (isFlatCwd) {
|
|
2415
|
-
locationHint = "O workspace \xE9 **esta pasta** \u2014 os exerc\xEDcios ficam aqui, vis\xEDveis para o assistente AI.";
|
|
2416
|
-
} else {
|
|
2417
|
-
locationHint = "O workspace local organiza os arquivos do curso:";
|
|
2418
|
-
}
|
|
2419
|
-
sections.push(`## Workspace
|
|
2420
|
-
|
|
2421
|
-
${locationHint}
|
|
2422
|
-
|
|
2423
|
-
\`\`\`
|
|
2424
|
-
${displayPath}/
|
|
2425
|
-
\u251C\u2500\u2500 exercises/{m\xF3dulo}/{li\xE7\xE3o}/ \u2190 Exerc\xEDcios extra\xEDdos
|
|
2426
|
-
\u251C\u2500\u2500 generated/ \u2190 Artefatos gerados
|
|
2427
|
-
\u251C\u2500\u2500 diagrams/ \u2190 Diagramas
|
|
2428
|
-
\u2514\u2500\u2500 ${vaultRelativeHint}
|
|
2429
|
-
\`\`\`
|
|
2430
|
-
|
|
2431
|
-
Comandos \xFAteis:
|
|
2432
|
-
- \`tostudy export\` \u2014 Extrair exerc\xEDcio para o workspace
|
|
2433
|
-
- \`tostudy vault init\` \u2014 Gerar vault Obsidian (abra no Obsidian: "Open folder as vault")
|
|
2434
|
-
- \`tostudy open\` \u2014 Abrir workspace no editor
|
|
2435
|
-
- \`tostudy workspace status\` \u2014 Verificar estado do workspace`);
|
|
2436
|
-
}
|
|
2437
|
-
sections.push(`## Comandos CLI
|
|
2438
|
-
|
|
2439
|
-
| Comando | Quando usar |
|
|
2440
|
-
|---------|-------------|
|
|
2441
|
-
| \`tostudy progress\` | No in\xEDcio da sess\xE3o e quando o aluno perguntar "onde estou?" |
|
|
2442
|
-
| \`tostudy start\` | Para carregar o m\xF3dulo atual ou o pr\xF3ximo |
|
|
2443
|
-
| \`tostudy lesson\` | Antes de discutir qualquer conte\xFAdo \u2014 sempre ler primeiro |
|
|
2444
|
-
| \`tostudy next\` | Ap\xF3s completar uma li\xE7\xE3o (teoria discutida ou exerc\xEDcio validado) |
|
|
2445
|
-
| \`tostudy hint\` | Quando o aluno travar \u2014 antes de explicar voc\xEA mesmo |
|
|
2446
|
-
| \`tostudy validate <arquivo>\` | Para validar exerc\xEDcios e checkpoints |
|
|
2447
|
-
| \`tostudy export\` | Para extrair exerc\xEDcio ao workspace local |`);
|
|
2448
|
-
sections.push(`## Refer\xEAncia T\xE9cnica (Modo Agente)
|
|
2449
|
-
|
|
2450
|
-
- Use \`--json\` em qualquer comando para sa\xEDda estruturada (ex: \`tostudy progress --json\`)
|
|
2451
|
-
- \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado)
|
|
2452
|
-
- \`tostudy validate --stdin\` aceita solu\xE7\xE3o via pipe
|
|
2453
|
-
- \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\``);
|
|
2454
|
-
sections.push(`<!-- tostudy-course-id: ${ctx.courseId} -->`);
|
|
1939
|
+
sections.push(`# ToStudy \u2014 Seu Tutor`);
|
|
1940
|
+
sections.push(`## Quem Voce E
|
|
1941
|
+
|
|
1942
|
+
Voce e o tutor ToStudy. Fale com o aluno em **primeira pessoa**. Apresente o conteudo voce mesmo \u2014 nao narre comandos CLI, nao descreva o sistema em terceira pessoa.`);
|
|
1943
|
+
sections.push(renderVoiceRules());
|
|
1944
|
+
sections.push(`## Primeira Coisa (SEMPRE)
|
|
1945
|
+
|
|
1946
|
+
Rode \`tostudy context --json\` em silencio para descobrir o estado do aluno.
|
|
1947
|
+
|
|
1948
|
+
### Se erro "no_workspace" (aluno ainda nao configurou):
|
|
1949
|
+
|
|
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:
|
|
1958
|
+
|
|
1959
|
+
1. Rode \`tostudy courses --json\` em silencio.
|
|
1960
|
+
2. Mostre a lista ao aluno de forma amigavel.
|
|
1961
|
+
3. Pergunte: "Qual curso quer estudar?"
|
|
1962
|
+
4. Rode \`tostudy select <numero> --json\` e siga o campo \`instruction\`.
|
|
1963
|
+
|
|
1964
|
+
### Se tudo pronto (workspace + curso + contexto):
|
|
1965
|
+
|
|
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.`);
|
|
1969
|
+
sections.push(renderHowToConduct());
|
|
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.`);
|
|
1984
|
+
sections.push(renderHandlingSituations());
|
|
1985
|
+
sections.push(renderTechnicalReference());
|
|
1986
|
+
sections.push(`<!-- tostudy-template-version: 4 -->`);
|
|
2455
1987
|
return sections.join("\n\n") + "\n";
|
|
2456
1988
|
}
|
|
2457
|
-
function
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
const written = [];
|
|
2462
|
-
const claudeDir = join2(cwd, ".claude", "commands");
|
|
2463
|
-
if (!existsSync2(claudeDir)) {
|
|
2464
|
-
mkdirSync2(claudeDir, { recursive: true });
|
|
1989
|
+
function renderTitle(input2) {
|
|
1990
|
+
if (input2.tutorPersona) {
|
|
1991
|
+
const emoji3 = input2.tutorPersona.avatarEmoji ? ` ${input2.tutorPersona.avatarEmoji}` : "";
|
|
1992
|
+
return `# ${input2.course.courseTitle} \u2014 ${input2.tutorPersona.tutorName}${emoji3}, seu tutor`;
|
|
2465
1993
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
if (!existsSync2(cursorDir)) {
|
|
2472
|
-
mkdirSync2(cursorDir, { recursive: true });
|
|
2473
|
-
}
|
|
2474
|
-
const mdcContent = `---
|
|
2475
|
-
description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
|
|
2476
|
-
globs: ["**/*"]
|
|
2477
|
-
alwaysApply: true
|
|
2478
|
-
---
|
|
1994
|
+
return `# ${input2.course.courseTitle} \u2014 Seu Tutor`;
|
|
1995
|
+
}
|
|
1996
|
+
function renderWhoYouAre(persona) {
|
|
1997
|
+
if (!persona) {
|
|
1998
|
+
return `## Quem Voce E
|
|
2479
1999
|
|
|
2480
|
-
|
|
2481
|
-
const cursorFile = `tostudy-${slug}.mdc`;
|
|
2482
|
-
writeFileSync2(join2(cursorDir, cursorFile), mdcContent);
|
|
2483
|
-
written.push(`.cursor/rules/${cursorFile}`);
|
|
2000
|
+
Voce e o tutor deste curso. Fale em tom neutro e didatico \u2014 o creator ainda nao configurou uma persona especifica para este curso.`;
|
|
2484
2001
|
}
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
slug,
|
|
2493
|
-
files: written,
|
|
2494
|
-
hasLearnerProfile: !!learner
|
|
2495
|
-
});
|
|
2496
|
-
return slug;
|
|
2497
|
-
}
|
|
2498
|
-
function buildUniversalInstructionContent() {
|
|
2499
|
-
return `# ToStudy \u2014 Tutor AI
|
|
2002
|
+
const emoji3 = persona.avatarEmoji ? ` ${persona.avatarEmoji}` : "";
|
|
2003
|
+
const toneLabel = TONE_LABELS[persona.tone] ?? persona.tone;
|
|
2004
|
+
return `## Quem Voce E
|
|
2005
|
+
|
|
2006
|
+
Voce e **${persona.tutorName}**${emoji3}, o tutor deste curso. Nao "um tutor" \u2014 e voce.
|
|
2007
|
+
|
|
2008
|
+
### Sua Bagagem
|
|
2500
2009
|
|
|
2501
|
-
|
|
2010
|
+
${persona.background}
|
|
2502
2011
|
|
|
2503
|
-
|
|
2012
|
+
### Como Voce Fala
|
|
2504
2013
|
|
|
2505
|
-
|
|
2014
|
+
Seu tom natural e **${toneLabel}**. Seu estilo de comunicacao:
|
|
2506
2015
|
|
|
2507
|
-
|
|
2016
|
+
> ${persona.communicationStyle}
|
|
2508
2017
|
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2018
|
+
### Seu Dominio e Expressoes
|
|
2019
|
+
|
|
2020
|
+
Suas especialidades: ${persona.specialties.join(", ")}.
|
|
2021
|
+
|
|
2022
|
+
Expressoes que voce usa organicamente (nao force, use quando fizer sentido): ${persona.catchphrases.join(" - ")}.`;
|
|
2023
|
+
}
|
|
2024
|
+
function renderVoiceRules() {
|
|
2025
|
+
return `## Regras de Voz
|
|
2516
2026
|
|
|
2517
|
-
|
|
2027
|
+
Fale com o aluno em **primeira pessoa**. Apresente o conteudo voce mesmo, como um professor fazendo uma aula, nao como um sistema narrando comandos.
|
|
2518
2028
|
|
|
2519
|
-
|
|
2029
|
+
| Nunca | Sempre |
|
|
2030
|
+
| ---------------------------------------- | ----------------------------------- |
|
|
2031
|
+
| "O tutor vai mostrar..." | "Vou te mostrar..." |
|
|
2032
|
+
| "O aluno deve rodar \`tostudy lesson\`" | "Olha so, a ideia desta licao e..." |
|
|
2033
|
+
| "O sistema validou sua resposta" | "Sua resposta passou \u2014 parabens!" |
|
|
2034
|
+
| "Agora executando \`tostudy next\`..." | "Vamos para a proxima." |
|
|
2035
|
+
| "Conforme o material indica..." | "Repara como funciona..." |
|
|
2036
|
+
|
|
2037
|
+
**Regra de ouro:** comandos CLI sao suas ferramentas internas. Use-os em silencio. O aluno nunca deveria ver voce anunciando ou narrando um comando \u2014 ele so ve o resultado que voce traz em palavras humanas.`;
|
|
2038
|
+
}
|
|
2039
|
+
function renderWhoIsStudent(brief, profile) {
|
|
2040
|
+
const parts = ["## Quem e o Aluno"];
|
|
2041
|
+
parts.push("### Base");
|
|
2042
|
+
if (brief && brief.text.trim().length > 0) {
|
|
2043
|
+
parts.push(brief.text);
|
|
2044
|
+
parts.push(
|
|
2045
|
+
"_(Este e o brief que o aluno escreveu sobre si mesmo. Use como contexto principal \u2014 nomes, empresa, projetos reais, motivacoes. Tudo que voce disser deve parecer que voce leu e entendeu quem ele e.)_"
|
|
2046
|
+
);
|
|
2047
|
+
} else {
|
|
2048
|
+
parts.push(
|
|
2049
|
+
"Ainda nao tenho um brief base do aluno. Vou usar so o contexto especifico deste curso. Se fizer sentido, durante a conversa eu pergunto coisas que ajudem a te conhecer melhor."
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
parts.push("### Neste Curso");
|
|
2053
|
+
if (profile) {
|
|
2054
|
+
const levelLabel = LEVEL_LABELS[profile.learnerLevel];
|
|
2055
|
+
const adaptLabel = profile.adaptToRealContext ? "Sim \u2014 sempre que puder, troque exemplos genericos por casos reais do dia a dia do aluno." : "Nao \u2014 use os exemplos do curso como estao.";
|
|
2056
|
+
parts.push(
|
|
2057
|
+
[
|
|
2058
|
+
`- **Objetivo neste curso:** ${profile.goal}`,
|
|
2059
|
+
`- **Nivel neste assunto:** ${profile.learnerLevel} (${levelLabel})`,
|
|
2060
|
+
`- **Contexto de aplicacao:** ${profile.productsOrServices} na ${profile.company}`,
|
|
2061
|
+
`- **Segmento:** ${profile.segment}`,
|
|
2062
|
+
`- **Regiao:** ${profile.region}`,
|
|
2063
|
+
`- **Equipe envolvida:** ${profile.team}`,
|
|
2064
|
+
`- **Adaptar exemplos ao contexto real:** ${adaptLabel}`
|
|
2065
|
+
].join("\n")
|
|
2066
|
+
);
|
|
2067
|
+
} else {
|
|
2068
|
+
parts.push(
|
|
2069
|
+
"Ainda nao sei seu nivel neste assunto. Comece explicativo mas breve; calibre pelas primeiras respostas dele."
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
return parts.join("\n\n");
|
|
2073
|
+
}
|
|
2074
|
+
function renderHowToAdapt(profile) {
|
|
2075
|
+
if (!profile) {
|
|
2076
|
+
return `### Como Adaptar
|
|
2520
2077
|
|
|
2521
|
-
|
|
2078
|
+
Sem informacao de nivel especifico deste curso \u2014 comece explicativo mas breve; calibre pelas primeiras respostas do aluno. Na duvida, prefira "porque antes do como".`;
|
|
2079
|
+
}
|
|
2080
|
+
if (profile.learnerLevel === "beginner") {
|
|
2081
|
+
return `### Como Adaptar
|
|
2522
2082
|
|
|
2523
|
-
|
|
2524
|
-
Pergunte de forma natural (N\xC3O como formul\xE1rio):
|
|
2525
|
-
1. "Em que \xE1rea voc\xEA trabalha?" \u2192 segment
|
|
2526
|
-
2. "Qual sua empresa ou tipo de neg\xF3cio?" \u2192 company
|
|
2527
|
-
3. "Quais produtos ou servi\xE7os voc\xEAs oferecem?" \u2192 products
|
|
2528
|
-
4. "Em qual regi\xE3o atuam?" \u2192 region
|
|
2529
|
-
5. "Qual equipe est\xE1 envolvida?" \u2192 team
|
|
2530
|
-
6. "Qual seu objetivo principal com este curso?" \u2192 goal
|
|
2531
|
-
7. "Como voc\xEA se considera: iniciante, intermedi\xE1rio ou avan\xE7ado?" \u2192 level
|
|
2532
|
-
8. "Quer que eu adapte os exemplos ao seu contexto real?" \u2192 adapt-context
|
|
2083
|
+
O aluno e iniciante neste assunto. Regras:
|
|
2533
2084
|
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2085
|
+
- Sempre explique o **porque** antes do **como**.
|
|
2086
|
+
- Use analogias do cotidiano. Evite jargao sem traduzir.
|
|
2087
|
+
- Celebre pequenas vitorias. A primeira licao de cada modulo merece um "mao na massa" simples antes do conceitual.
|
|
2088
|
+
- Se ele travar, comece com dica sutil \u2014 nao pule direto para a resposta.`;
|
|
2089
|
+
}
|
|
2090
|
+
if (profile.learnerLevel === "advanced") {
|
|
2091
|
+
return `### Como Adaptar
|
|
2537
2092
|
|
|
2538
|
-
|
|
2093
|
+
O aluno e avancado. Regras:
|
|
2539
2094
|
|
|
2540
|
-
|
|
2095
|
+
- Assuma que ele ja sabe o basico. Comece direto pelo caso interessante.
|
|
2096
|
+
- Traga edge cases, trade-offs de arquitetura, alternativas reais.
|
|
2097
|
+
- Seja breve no obvio, profundo no nao-obvio.
|
|
2098
|
+
- Se ele perguntar algo trivial, responda direto sem rodeios didaticos.`;
|
|
2099
|
+
}
|
|
2100
|
+
return `### Como Adaptar
|
|
2541
2101
|
|
|
2542
|
-
|
|
2102
|
+
O aluno ja tem base. Regras:
|
|
2543
2103
|
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2104
|
+
- Pule fundamentos obvios. Foque em padroes, boas praticas e decisoes de design.
|
|
2105
|
+
- Mostre trade-offs: "isso funciona, mas se o volume crescer, prefira X porque...".
|
|
2106
|
+
- Desafie com perguntas tipo "o que aconteceria se...?".
|
|
2107
|
+
- Conecte conceitos desta licao com outras que ele ja fez.`;
|
|
2108
|
+
}
|
|
2109
|
+
function renderPrecedence() {
|
|
2110
|
+
return `## Precedencia: Voz vs Adaptacao
|
|
2548
2111
|
|
|
2549
|
-
|
|
2550
|
-
tostudy lesson \u2192 Ler conte\xFAdo da li\xE7\xE3o
|
|
2112
|
+
Voce tem DUAS fontes que te orientam, e elas resolvem conflitos assim:
|
|
2551
2113
|
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
\u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
|
|
2557
|
-
\u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2114
|
+
| Fonte | Governa | Exemplo |
|
|
2115
|
+
| ----------------------------- | ----------------------------- | ---------------------------------------------- |
|
|
2116
|
+
| Sua **persona** (acima) | voz, tom, nome, expressoes | usar catchphrase do creator ao cumprimentar |
|
|
2117
|
+
| **Contexto do aluno** (acima) | profundidade, ritmo, exemplos | "vou comecar pelo porque" (iniciante) |
|
|
2558
2118
|
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2119
|
+
**Se conflitam, ambos valem ao mesmo tempo.** Voce mantem sua voz E adapta a profundidade. Exemplo: persona \`technical\` + aluno \`beginner\` \u2014 voce explica conceitos tecnicos de forma precisa MAS sempre comeca pelo "porque" antes do "como", e traduz jargao na primeira vez que usa.`;
|
|
2120
|
+
}
|
|
2121
|
+
function renderWhereStudentIs(progress3) {
|
|
2122
|
+
const lines = [
|
|
2123
|
+
`- ${progress3.percent}% completo | ${progress3.moduleCount} modulos | ${progress3.lessonCount} licoes`
|
|
2124
|
+
];
|
|
2125
|
+
if (progress3.currentModuleTitle) lines.push(`- **Modulo atual:** ${progress3.currentModuleTitle}`);
|
|
2126
|
+
if (progress3.currentLessonTitle) lines.push(`- **Licao atual:** ${progress3.currentLessonTitle}`);
|
|
2127
|
+
return `## Onde o Aluno Esta
|
|
2563
2128
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2129
|
+
${lines.join("\n")}`;
|
|
2130
|
+
}
|
|
2131
|
+
function renderHowToConduct() {
|
|
2132
|
+
return `## Como Conduzir a Aula
|
|
2568
2133
|
|
|
2569
|
-
|
|
2570
|
-
\u2192 Parabenizar o aluno
|
|
2571
|
-
\u2192 tostudy start \u2192 Carregar pr\xF3ximo m\xF3dulo
|
|
2572
|
-
\`\`\`
|
|
2134
|
+
Quando o aluno comeca uma conversa:
|
|
2573
2135
|
|
|
2574
|
-
|
|
2136
|
+
1. Cumprimente ele pelo nome/contexto (use o brief base). Breve \u2014 1 frase.
|
|
2137
|
+
2. Descubra onde ele parou (\`tostudy progress --json\` em silencio).
|
|
2138
|
+
3. Resuma o estado em uma frase: "Voce esta no Modulo X, Licao Y \u2014 [titulo]".
|
|
2139
|
+
4. Pergunte se ele quer continuar ou revisar.
|
|
2575
2140
|
|
|
2576
|
-
|
|
2141
|
+
Quando o aluno quer estudar uma licao:
|
|
2577
2142
|
|
|
2578
|
-
|
|
2143
|
+
1. Carregue o conteudo em silencio (\`tostudy lesson --json\`).
|
|
2144
|
+
2. **Apresente a licao VOCE.** Nao diga "vou rodar o comando" nem cole o markdown cru. Leia, entenda, e ensine com suas palavras \u2014 exemplos, analogias, perguntas que engajam. Voce e o professor.
|
|
2145
|
+
3. Se for **texto/teoria**: explique os conceitos. Use perguntas socraticas ("O que voce acha que aconteceria se...?"). So avance quando ele demonstrar entendimento.
|
|
2146
|
+
4. Se for **exercicio**: explique o objetivo, mostre o setup, **nunca de a resposta**. Se travar \u2014 \`tostudy hint\` (em silencio) e traduza a dica. Quando ele submeter \u2014 \`tostudy validate\` e comente o resultado.
|
|
2147
|
+
5. Se for **quiz/checkpoint**: peca que ele escreva as respostas num arquivo, valide com \`tostudy validate respostas.md\`, discuta.
|
|
2148
|
+
6. Se for **video**: resuma os pontos-chave, aguarde, depois discuta.
|
|
2579
2149
|
|
|
2580
|
-
|
|
2150
|
+
Quando o aluno passou na licao:
|
|
2581
2151
|
|
|
2582
|
-
|
|
2152
|
+
- Celebre (brevemente).
|
|
2153
|
+
- Pergunte se ele quer seguir ou pausar.
|
|
2154
|
+
- Ao seguir \u2014 \`tostudy next\` (silencio) \u2014 apresente a proxima.`;
|
|
2155
|
+
}
|
|
2156
|
+
function renderSilentTools() {
|
|
2157
|
+
return `## Ferramentas Silenciosas
|
|
2583
2158
|
|
|
2584
|
-
|
|
2159
|
+
Estes comandos sao suas ferramentas. Rode em silencio (sem anunciar), use o resultado, e traduza em palavras ao aluno.
|
|
2585
2160
|
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
7. **Sempre rode \`tostudy lesson\`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material
|
|
2593
|
-
8. **Ignore erros de hook** \u2014 se aparecer "Stop hook error" ou "ECONNREFUSED", ignore completamente. \xC9 um problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy. Nunca mencione esses erros ao aluno.
|
|
2161
|
+
- \`tostudy progress --json\` \u2014 estado atual (modulo, licao, %).
|
|
2162
|
+
- \`tostudy lesson --json\` \u2014 conteudo da licao (type, title, content, hints, acceptanceCriteria).
|
|
2163
|
+
- \`tostudy start --json\` \u2014 ativa modulo atual ou proximo.
|
|
2164
|
+
- \`tostudy next --json\` \u2014 avanca para a proxima licao.
|
|
2165
|
+
- \`tostudy hint --json\` \u2014 dica progressiva (3 niveis).
|
|
2166
|
+
- \`tostudy validate <arquivo>\` \u2014 valida exercicio (exit 0 = passou, 1 = falhou).
|
|
2594
2167
|
|
|
2595
|
-
|
|
2168
|
+
Voce nunca menciona estes comandos ao aluno. Ele fala com VOCE, nao com o CLI.`;
|
|
2169
|
+
}
|
|
2170
|
+
function renderHandlingSituations() {
|
|
2171
|
+
return `## Tratando Situacoes
|
|
2596
2172
|
|
|
2597
|
-
|
|
|
2598
|
-
|
|
2599
|
-
| \`tostudy validate\` falhou
|
|
2600
|
-
|
|
|
2601
|
-
|
|
|
2602
|
-
|
|
|
2603
|
-
| Aluno perdido | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2604
|
-
| "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |
|
|
2605
|
-
|
|
2606
|
-
## Workspace
|
|
2607
|
-
|
|
2608
|
-
Se esta pasta cont\xE9m \`.tostudy/\`, ela j\xE1 \xE9 o workspace \u2014 exerc\xEDcios ficam aqui.
|
|
2609
|
-
Caso contr\xE1rio, rode \`tostudy workspace setup\` para criar a estrutura.
|
|
2610
|
-
|
|
2611
|
-
- \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace (esta pasta ou ~/study/{slug}/)
|
|
2612
|
-
- \`tostudy open\` \u2014 Abre workspace no editor
|
|
2613
|
-
- \`tostudy workspace status\` \u2014 Verifica estado
|
|
2173
|
+
| Situacao | O que fazer |
|
|
2174
|
+
| ------------------------------------- | --------------------------------------------------------- |
|
|
2175
|
+
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2176
|
+
| "Nenhuma licao ativa" | Rodar \`tostudy start\` para carregar modulo |
|
|
2177
|
+
| Comando retorna erro | Verificar \`tostudy doctor\` para diagnostico |
|
|
2178
|
+
| Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2614
2179
|
|
|
2615
|
-
|
|
2180
|
+
> Se aparecer qualquer erro de hook ou conexao do IDE ("Stop hook error", "ECONNREFUSED"), ignore \u2014 nao e problema seu nem do aluno. Nunca mencione esses erros ao aluno.`;
|
|
2181
|
+
}
|
|
2182
|
+
function renderTechnicalReference() {
|
|
2183
|
+
return `## Referencia Tecnica (Modo Agente)
|
|
2616
2184
|
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2185
|
+
- Use \`--json\` em qualquer comando para saida estruturada.
|
|
2186
|
+
- \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado).
|
|
2187
|
+
- \`tostudy validate --stdin\` aceita solucao via pipe.
|
|
2188
|
+
- \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\`.
|
|
2189
|
+
- \`tostudy progress --json\` retorna \`{ coursePercent, currentModule, currentLesson }\`.`;
|
|
2190
|
+
}
|
|
2191
|
+
function renderFooter(course) {
|
|
2192
|
+
return `<!-- tostudy-course-id: ${course.courseId} -->
|
|
2193
|
+
<!-- tostudy-template-version: 3 -->`;
|
|
2194
|
+
}
|
|
2195
|
+
var TONE_LABELS, LEVEL_LABELS;
|
|
2196
|
+
var init_instruction_template_v3 = __esm({
|
|
2197
|
+
"src/output/instruction-template-v3.ts"() {
|
|
2198
|
+
"use strict";
|
|
2199
|
+
TONE_LABELS = {
|
|
2200
|
+
formal: "formal",
|
|
2201
|
+
casual: "casual",
|
|
2202
|
+
motivational: "motivacional",
|
|
2203
|
+
technical: "tecnico",
|
|
2204
|
+
relaxed: "descontraido"
|
|
2205
|
+
};
|
|
2206
|
+
LEVEL_LABELS = {
|
|
2207
|
+
beginner: "iniciante",
|
|
2208
|
+
intermediate: "intermediario",
|
|
2209
|
+
advanced: "avancado"
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2631
2213
|
|
|
2632
|
-
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2214
|
+
// src/workspace/instruction-files.ts
|
|
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";
|
|
2222
|
+
import { join as join2 } from "node:path";
|
|
2223
|
+
function slugify(title) {
|
|
2224
|
+
return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
2225
|
+
}
|
|
2226
|
+
function writeInstructionFiles(cwd, ctx, content) {
|
|
2227
|
+
const slug = slugify(ctx.courseTitle);
|
|
2228
|
+
const written = [];
|
|
2229
|
+
const claudeDir = join2(cwd, ".claude", "commands");
|
|
2230
|
+
if (!existsSync3(claudeDir)) {
|
|
2231
|
+
mkdirSync3(claudeDir, { recursive: true });
|
|
2232
|
+
}
|
|
2233
|
+
const claudeFile = `tostudy-${slug}.md`;
|
|
2234
|
+
writeFileSync4(join2(claudeDir, claudeFile), content);
|
|
2235
|
+
written.push(`.claude/commands/${claudeFile}`);
|
|
2236
|
+
const cursorDir = join2(cwd, ".cursor", "rules");
|
|
2237
|
+
if (existsSync3(join2(cwd, ".cursor")) || existsSync3(cursorDir)) {
|
|
2238
|
+
if (!existsSync3(cursorDir)) {
|
|
2239
|
+
mkdirSync3(cursorDir, { recursive: true });
|
|
2240
|
+
}
|
|
2241
|
+
const mdcContent = `---
|
|
2242
|
+
description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
|
|
2243
|
+
globs: ["**/*"]
|
|
2244
|
+
alwaysApply: true
|
|
2245
|
+
---
|
|
2246
|
+
|
|
2247
|
+
${content}`;
|
|
2248
|
+
const cursorFile = `tostudy-${slug}.mdc`;
|
|
2249
|
+
writeFileSync4(join2(cursorDir, cursorFile), mdcContent);
|
|
2250
|
+
written.push(`.cursor/rules/${cursorFile}`);
|
|
2251
|
+
}
|
|
2252
|
+
const courseDir = join2(cwd, ".tostudy", "courses", slug);
|
|
2253
|
+
if (!existsSync3(courseDir)) {
|
|
2254
|
+
mkdirSync3(courseDir, { recursive: true });
|
|
2255
|
+
}
|
|
2256
|
+
writeFileSync4(join2(courseDir, "AGENTS.md"), content);
|
|
2257
|
+
written.push(`.tostudy/courses/${slug}/AGENTS.md`);
|
|
2258
|
+
logger2.info("Generated instruction files (v3)", {
|
|
2259
|
+
slug,
|
|
2260
|
+
files: written
|
|
2261
|
+
});
|
|
2262
|
+
return written;
|
|
2638
2263
|
}
|
|
2639
|
-
function installUniversalCommand(
|
|
2640
|
-
const content =
|
|
2264
|
+
function installUniversalCommand(platform2, cwd = process.cwd()) {
|
|
2265
|
+
const content = renderUniversalInstruction();
|
|
2641
2266
|
const written = [];
|
|
2642
|
-
if (
|
|
2267
|
+
if (platform2 === "claude" || platform2 === "codex") {
|
|
2643
2268
|
const dir = join2(cwd, ".claude", "commands");
|
|
2644
|
-
if (!
|
|
2645
|
-
|
|
2269
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2270
|
+
writeFileSync4(join2(dir, "tostudy.md"), content);
|
|
2646
2271
|
written.push(".claude/commands/tostudy.md");
|
|
2647
2272
|
}
|
|
2648
|
-
if (
|
|
2273
|
+
if (platform2 === "cursor") {
|
|
2649
2274
|
const dir = join2(cwd, ".cursor", "rules");
|
|
2650
|
-
if (!
|
|
2275
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2651
2276
|
const mdcContent = `---
|
|
2652
2277
|
description: ToStudy \u2014 AI Tutor Guide
|
|
2653
2278
|
globs: ["**/*"]
|
|
@@ -2655,31 +2280,31 @@ alwaysApply: true
|
|
|
2655
2280
|
---
|
|
2656
2281
|
|
|
2657
2282
|
${content}`;
|
|
2658
|
-
|
|
2283
|
+
writeFileSync4(join2(dir, "tostudy.mdc"), mdcContent);
|
|
2659
2284
|
written.push(".cursor/rules/tostudy.mdc");
|
|
2660
2285
|
}
|
|
2661
|
-
if (
|
|
2286
|
+
if (platform2 === "generic") {
|
|
2662
2287
|
const dir = join2(cwd, ".tostudy");
|
|
2663
|
-
if (!
|
|
2664
|
-
|
|
2288
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2289
|
+
writeFileSync4(join2(dir, "AGENTS.md"), content);
|
|
2665
2290
|
written.push(".tostudy/AGENTS.md");
|
|
2666
2291
|
}
|
|
2667
|
-
|
|
2292
|
+
logger2.info("Installed universal /tostudy command", { platform: platform2, files: written });
|
|
2668
2293
|
return written;
|
|
2669
2294
|
}
|
|
2670
|
-
var
|
|
2295
|
+
var logger2;
|
|
2671
2296
|
var init_instruction_files = __esm({
|
|
2672
2297
|
"src/workspace/instruction-files.ts"() {
|
|
2673
2298
|
"use strict";
|
|
2674
2299
|
init_src();
|
|
2675
|
-
|
|
2300
|
+
init_instruction_template_v3();
|
|
2301
|
+
logger2 = createLogger("cli:instruction-files");
|
|
2676
2302
|
}
|
|
2677
2303
|
});
|
|
2678
2304
|
|
|
2679
|
-
// src/commands/
|
|
2680
|
-
import { Command
|
|
2681
|
-
import {
|
|
2682
|
-
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";
|
|
2683
2308
|
function ideToPlatform(ideName) {
|
|
2684
2309
|
const lower = ideName.toLowerCase();
|
|
2685
2310
|
if (lower.includes("claude")) return "claude";
|
|
@@ -2689,107 +2314,168 @@ function ideToPlatform(ideName) {
|
|
|
2689
2314
|
return "generic";
|
|
2690
2315
|
return null;
|
|
2691
2316
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
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}
|
|
2707
2336
|
`);
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
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}
|
|
2720
2349
|
`);
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
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
|
+
});
|
|
2755
2416
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
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
|
+
});
|
|
2758
2430
|
}
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
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}`
|
|
2768
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})`);
|
|
2769
2445
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
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";
|
|
2791
2471
|
}
|
|
2792
|
-
|
|
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");
|
|
2793
2479
|
}
|
|
2794
2480
|
async function runSetupMcpSubcommand() {
|
|
2795
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");
|
|
@@ -2806,23 +2492,12 @@ var defaultDeps, setupCommand;
|
|
|
2806
2492
|
var init_setup = __esm({
|
|
2807
2493
|
"src/commands/setup.ts"() {
|
|
2808
2494
|
"use strict";
|
|
2809
|
-
init_node_detector();
|
|
2810
|
-
init_ide_detector();
|
|
2811
2495
|
init_session();
|
|
2812
2496
|
init_mcp_setup();
|
|
2813
|
-
init_instruction_files();
|
|
2814
2497
|
defaultDeps = {
|
|
2815
|
-
detectNode,
|
|
2816
|
-
detectIDEs,
|
|
2817
|
-
getSession,
|
|
2818
|
-
getActiveCourse,
|
|
2819
|
-
exchangeCliSessionForMcpToken,
|
|
2820
|
-
runMcpSetup,
|
|
2821
|
-
installUniversalCommand,
|
|
2822
2498
|
log: (message = "") => {
|
|
2823
2499
|
console.log(message);
|
|
2824
|
-
}
|
|
2825
|
-
exit: (code) => process.exit(code)
|
|
2500
|
+
}
|
|
2826
2501
|
};
|
|
2827
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) => {
|
|
2828
2503
|
await runSetup(opts);
|
|
@@ -2833,6 +2508,31 @@ var init_setup = __esm({
|
|
|
2833
2508
|
}
|
|
2834
2509
|
});
|
|
2835
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
|
+
|
|
2836
2536
|
// src/update-checker.ts
|
|
2837
2537
|
var update_checker_exports = {};
|
|
2838
2538
|
__export(update_checker_exports, {
|
|
@@ -2842,17 +2542,17 @@ __export(update_checker_exports, {
|
|
|
2842
2542
|
isNewerVersion: () => isNewerVersion
|
|
2843
2543
|
});
|
|
2844
2544
|
import fs4 from "node:fs";
|
|
2845
|
-
import
|
|
2846
|
-
import
|
|
2545
|
+
import path6 from "node:path";
|
|
2546
|
+
import os6 from "node:os";
|
|
2847
2547
|
function getConfigDir2() {
|
|
2848
2548
|
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
2849
|
-
return
|
|
2549
|
+
return path6.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
2850
2550
|
}
|
|
2851
|
-
return
|
|
2551
|
+
return path6.join(os6.homedir(), ".tostudy");
|
|
2852
2552
|
}
|
|
2853
2553
|
function readCache() {
|
|
2854
2554
|
try {
|
|
2855
|
-
const p =
|
|
2555
|
+
const p = path6.join(getConfigDir2(), CACHE_FILE);
|
|
2856
2556
|
if (!fs4.existsSync(p)) return null;
|
|
2857
2557
|
return JSON.parse(fs4.readFileSync(p, "utf-8"));
|
|
2858
2558
|
} catch {
|
|
@@ -2863,7 +2563,7 @@ function writeCache(cache) {
|
|
|
2863
2563
|
try {
|
|
2864
2564
|
const dir = getConfigDir2();
|
|
2865
2565
|
fs4.mkdirSync(dir, { recursive: true });
|
|
2866
|
-
fs4.writeFileSync(
|
|
2566
|
+
fs4.writeFileSync(path6.join(dir, CACHE_FILE), JSON.stringify(cache));
|
|
2867
2567
|
} catch {
|
|
2868
2568
|
}
|
|
2869
2569
|
}
|
|
@@ -2931,6 +2631,84 @@ var init_update_checker = __esm({
|
|
|
2931
2631
|
}
|
|
2932
2632
|
});
|
|
2933
2633
|
|
|
2634
|
+
// src/learner-brief/cache.ts
|
|
2635
|
+
import fs5 from "node:fs";
|
|
2636
|
+
import os7 from "node:os";
|
|
2637
|
+
import path7 from "node:path";
|
|
2638
|
+
function getDefaultConfigDir() {
|
|
2639
|
+
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
2640
|
+
return path7.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
2641
|
+
}
|
|
2642
|
+
return path7.join(os7.homedir(), ".tostudy");
|
|
2643
|
+
}
|
|
2644
|
+
function resolveCachePath(configDir) {
|
|
2645
|
+
return path7.join(configDir, "student-brief.json");
|
|
2646
|
+
}
|
|
2647
|
+
async function readBriefCache(configDir) {
|
|
2648
|
+
const dir = configDir ?? getDefaultConfigDir();
|
|
2649
|
+
const p = resolveCachePath(dir);
|
|
2650
|
+
if (!fs5.existsSync(p)) return null;
|
|
2651
|
+
try {
|
|
2652
|
+
const raw = fs5.readFileSync(p, "utf-8");
|
|
2653
|
+
return JSON.parse(raw);
|
|
2654
|
+
} catch {
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
async function writeBriefCache(configDir, cache) {
|
|
2659
|
+
const dir = configDir ?? getDefaultConfigDir();
|
|
2660
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
2661
|
+
fs5.writeFileSync(resolveCachePath(dir), JSON.stringify(cache, null, 2), { mode: 384 });
|
|
2662
|
+
}
|
|
2663
|
+
var init_cache = __esm({
|
|
2664
|
+
"src/learner-brief/cache.ts"() {
|
|
2665
|
+
"use strict";
|
|
2666
|
+
}
|
|
2667
|
+
});
|
|
2668
|
+
|
|
2669
|
+
// src/tutor-persona/cache.ts
|
|
2670
|
+
import fs6 from "node:fs";
|
|
2671
|
+
import os8 from "node:os";
|
|
2672
|
+
import path8 from "node:path";
|
|
2673
|
+
function getDefaultConfigDir2() {
|
|
2674
|
+
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
2675
|
+
return path8.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
2676
|
+
}
|
|
2677
|
+
return path8.join(os8.homedir(), ".tostudy");
|
|
2678
|
+
}
|
|
2679
|
+
function resolveCachePath2(configDir) {
|
|
2680
|
+
return path8.join(configDir, "tutor-personalities.json");
|
|
2681
|
+
}
|
|
2682
|
+
function readAll(configDir) {
|
|
2683
|
+
const p = resolveCachePath2(configDir);
|
|
2684
|
+
if (!fs6.existsSync(p)) return {};
|
|
2685
|
+
try {
|
|
2686
|
+
return JSON.parse(fs6.readFileSync(p, "utf-8"));
|
|
2687
|
+
} catch {
|
|
2688
|
+
return {};
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
function writeAll(configDir, cache) {
|
|
2692
|
+
fs6.mkdirSync(configDir, { recursive: true });
|
|
2693
|
+
fs6.writeFileSync(resolveCachePath2(configDir), JSON.stringify(cache, null, 2), { mode: 384 });
|
|
2694
|
+
}
|
|
2695
|
+
async function readPersonaCacheForCourse(configDir, courseId) {
|
|
2696
|
+
const dir = configDir ?? getDefaultConfigDir2();
|
|
2697
|
+
const all = readAll(dir);
|
|
2698
|
+
return all[courseId] ?? null;
|
|
2699
|
+
}
|
|
2700
|
+
async function writePersonaCacheForCourse(configDir, courseId, entry) {
|
|
2701
|
+
const dir = configDir ?? getDefaultConfigDir2();
|
|
2702
|
+
const all = readAll(dir);
|
|
2703
|
+
all[courseId] = entry;
|
|
2704
|
+
writeAll(dir, all);
|
|
2705
|
+
}
|
|
2706
|
+
var init_cache2 = __esm({
|
|
2707
|
+
"src/tutor-persona/cache.ts"() {
|
|
2708
|
+
"use strict";
|
|
2709
|
+
}
|
|
2710
|
+
});
|
|
2711
|
+
|
|
2934
2712
|
// src/commands/doctor.ts
|
|
2935
2713
|
import { Command as Command4 } from "commander";
|
|
2936
2714
|
var doctorCommand;
|
|
@@ -2940,9 +2718,13 @@ var init_doctor = __esm({
|
|
|
2940
2718
|
init_node_detector();
|
|
2941
2719
|
init_ide_detector();
|
|
2942
2720
|
init_session();
|
|
2721
|
+
init_workspace_state();
|
|
2943
2722
|
init_formatter();
|
|
2944
2723
|
init_cli();
|
|
2945
2724
|
init_update_checker();
|
|
2725
|
+
init_cache();
|
|
2726
|
+
init_cache2();
|
|
2727
|
+
init_workspace_marker();
|
|
2946
2728
|
doctorCommand = new Command4("doctor").description("Diagn\xF3stico do ambiente").option("--json", "Output JSON").option("--fix", "Auto-corrigir problemas (reservado para vers\xF5es futuras)").action(async (opts) => {
|
|
2947
2729
|
const checks = {};
|
|
2948
2730
|
const node = detectNode();
|
|
@@ -2993,6 +2775,69 @@ var init_doctor = __esm({
|
|
|
2993
2775
|
} catch {
|
|
2994
2776
|
}
|
|
2995
2777
|
checks["ides"] = ides.filter((ide) => ide.detected);
|
|
2778
|
+
let workspaceMarkerCheck;
|
|
2779
|
+
try {
|
|
2780
|
+
const marker = await readWorkspaceMarker(process.cwd());
|
|
2781
|
+
workspaceMarkerCheck = marker ? {
|
|
2782
|
+
status: "found",
|
|
2783
|
+
courseId: marker.courseId,
|
|
2784
|
+
slug: marker.slug,
|
|
2785
|
+
courseTitle: marker.courseTitle
|
|
2786
|
+
} : { status: "absent" };
|
|
2787
|
+
} catch (err) {
|
|
2788
|
+
workspaceMarkerCheck = {
|
|
2789
|
+
status: "error",
|
|
2790
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
checks["workspaceMarker"] = workspaceMarkerCheck;
|
|
2794
|
+
let briefCacheCheck;
|
|
2795
|
+
try {
|
|
2796
|
+
const briefCache = await readBriefCache();
|
|
2797
|
+
if (briefCache?.brief) {
|
|
2798
|
+
const ageMs = Date.now() - new Date(briefCache.fetchedAt).getTime();
|
|
2799
|
+
const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
|
|
2800
|
+
briefCacheCheck = { status: "cached", ageDays, stale: ageDays > 7 };
|
|
2801
|
+
} else if (briefCache?.skipped) {
|
|
2802
|
+
briefCacheCheck = { status: "skipped" };
|
|
2803
|
+
} else {
|
|
2804
|
+
briefCacheCheck = { status: "missing" };
|
|
2805
|
+
}
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
briefCacheCheck = {
|
|
2808
|
+
status: "error",
|
|
2809
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
checks["briefCache"] = briefCacheCheck;
|
|
2813
|
+
let personaCacheCheck;
|
|
2814
|
+
try {
|
|
2815
|
+
const wsResult = await findWorkspaceState();
|
|
2816
|
+
const active = wsResult?.state ?? null;
|
|
2817
|
+
if (active) {
|
|
2818
|
+
const personaEntry = await readPersonaCacheForCourse(void 0, active.courseId);
|
|
2819
|
+
if (personaEntry) {
|
|
2820
|
+
const ageMs = Date.now() - new Date(personaEntry.fetchedAt).getTime();
|
|
2821
|
+
const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
|
|
2822
|
+
personaCacheCheck = {
|
|
2823
|
+
status: "cached",
|
|
2824
|
+
courseTitle: active.courseTitle,
|
|
2825
|
+
ageDays,
|
|
2826
|
+
stale: ageDays > 7
|
|
2827
|
+
};
|
|
2828
|
+
} else {
|
|
2829
|
+
personaCacheCheck = { status: "missing", courseTitle: active.courseTitle };
|
|
2830
|
+
}
|
|
2831
|
+
} else {
|
|
2832
|
+
personaCacheCheck = { status: "no-active-course" };
|
|
2833
|
+
}
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
personaCacheCheck = {
|
|
2836
|
+
status: "error",
|
|
2837
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
checks["personaCache"] = personaCacheCheck;
|
|
2996
2841
|
if (opts.json) {
|
|
2997
2842
|
output(checks, { json: true });
|
|
2998
2843
|
return;
|
|
@@ -3038,6 +2883,38 @@ var init_doctor = __esm({
|
|
|
3038
2883
|
` ${ide.detected ? "\u2713" : "\u25CB"} ${ide.name.padEnd(14)} ${ide.detected ? "detectado" : "n\xE3o detectado"}`
|
|
3039
2884
|
);
|
|
3040
2885
|
}
|
|
2886
|
+
console.log("\n Contexto de estudo");
|
|
2887
|
+
if (workspaceMarkerCheck.status === "found") {
|
|
2888
|
+
console.log(
|
|
2889
|
+
` \u2713 Workspace ${workspaceMarkerCheck.courseTitle} (${workspaceMarkerCheck.slug})`
|
|
2890
|
+
);
|
|
2891
|
+
} else if (workspaceMarkerCheck.status === "absent") {
|
|
2892
|
+
console.log(" \u25CB Workspace sem marker no diret\xF3rio atual (usando estado global)");
|
|
2893
|
+
} else {
|
|
2894
|
+
console.log(` \u2717 Workspace erro ao ler marker: ${workspaceMarkerCheck.message}`);
|
|
2895
|
+
}
|
|
2896
|
+
if (briefCacheCheck.status === "cached") {
|
|
2897
|
+
const warn = briefCacheCheck.stale ? " (desatualizado \u2014 rode `tostudy sync`)" : "";
|
|
2898
|
+
console.log(` \u2713 Brief (T1) cache com ${briefCacheCheck.ageDays}d${warn}`);
|
|
2899
|
+
} else if (briefCacheCheck.status === "skipped") {
|
|
2900
|
+
console.log(" \u25CB Brief (T1) ignorado pelo usu\xE1rio");
|
|
2901
|
+
} else if (briefCacheCheck.status === "missing") {
|
|
2902
|
+
console.log(" \u25CB Brief (T1) n\xE3o criado \u2014 rode `tostudy brief-create`");
|
|
2903
|
+
} else {
|
|
2904
|
+
console.log(` \u2717 Brief (T1) erro: ${briefCacheCheck.message}`);
|
|
2905
|
+
}
|
|
2906
|
+
if (personaCacheCheck.status === "cached") {
|
|
2907
|
+
const warn = personaCacheCheck.stale ? " (desatualizado)" : "";
|
|
2908
|
+
console.log(
|
|
2909
|
+
` \u2713 Persona (T0) cache para "${personaCacheCheck.courseTitle}" (${personaCacheCheck.ageDays}d)${warn}`
|
|
2910
|
+
);
|
|
2911
|
+
} else if (personaCacheCheck.status === "missing") {
|
|
2912
|
+
console.log(` \u25CB Persona (T0) sem cache para "${personaCacheCheck.courseTitle}"`);
|
|
2913
|
+
} else if (personaCacheCheck.status === "no-active-course") {
|
|
2914
|
+
console.log(" \u25CB Persona (T0) nenhum curso ativo");
|
|
2915
|
+
} else {
|
|
2916
|
+
console.log(` \u2717 Persona (T0) erro: ${personaCacheCheck.message}`);
|
|
2917
|
+
}
|
|
3041
2918
|
console.log("");
|
|
3042
2919
|
});
|
|
3043
2920
|
}
|
|
@@ -3075,17 +2952,17 @@ var init_courses2 = __esm({
|
|
|
3075
2952
|
});
|
|
3076
2953
|
|
|
3077
2954
|
// src/workspace/resolve.ts
|
|
3078
|
-
import
|
|
3079
|
-
import
|
|
3080
|
-
import
|
|
2955
|
+
import fs7 from "node:fs/promises";
|
|
2956
|
+
import path9 from "node:path";
|
|
2957
|
+
import os9 from "node:os";
|
|
3081
2958
|
function courseSlug(title) {
|
|
3082
2959
|
return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
3083
2960
|
}
|
|
3084
2961
|
async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
|
|
3085
2962
|
const slug = courseSlug(courseTitle);
|
|
3086
|
-
const candidate =
|
|
2963
|
+
const candidate = path9.join(basePath, slug);
|
|
3087
2964
|
try {
|
|
3088
|
-
await
|
|
2965
|
+
await fs7.access(path9.join(candidate, ".ana-config.json"));
|
|
3089
2966
|
return { found: true, workspacePath: candidate, source: "default" };
|
|
3090
2967
|
} catch {
|
|
3091
2968
|
return { found: false, workspacePath: null };
|
|
@@ -3095,46 +2972,58 @@ async function isCwdWorkspace(cwd = process.cwd()) {
|
|
|
3095
2972
|
return await resolveCwdWorkspacePath(cwd) !== null;
|
|
3096
2973
|
}
|
|
3097
2974
|
async function resolveCwdWorkspacePath(cwd = process.cwd()) {
|
|
3098
|
-
const tostudyDir =
|
|
2975
|
+
const tostudyDir = path9.join(cwd, ".tostudy");
|
|
3099
2976
|
try {
|
|
3100
|
-
const stat = await
|
|
2977
|
+
const stat = await fs7.stat(tostudyDir);
|
|
3101
2978
|
if (stat.isDirectory()) return tostudyDir;
|
|
3102
2979
|
} catch {
|
|
3103
2980
|
}
|
|
3104
2981
|
try {
|
|
3105
|
-
await
|
|
2982
|
+
await fs7.access(path9.join(cwd, ".ana-config.json"));
|
|
3106
2983
|
return cwd;
|
|
3107
2984
|
} catch {
|
|
3108
2985
|
return null;
|
|
3109
2986
|
}
|
|
3110
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
|
+
}
|
|
3111
3000
|
function resolveVaultPath(workspacePath, slug) {
|
|
3112
|
-
const base =
|
|
3113
|
-
return
|
|
3001
|
+
const base = path9.basename(workspacePath) === ".tostudy" ? path9.dirname(workspacePath) : workspacePath;
|
|
3002
|
+
return path9.join(base, `vault-${slug}`);
|
|
3114
3003
|
}
|
|
3115
3004
|
async function findExistingVault(workspacePath, slug) {
|
|
3116
3005
|
const slugged = resolveVaultPath(workspacePath, slug);
|
|
3117
3006
|
try {
|
|
3118
|
-
await
|
|
3007
|
+
await fs7.access(path9.join(slugged, ".ana-vault.json"));
|
|
3119
3008
|
return slugged;
|
|
3120
3009
|
} catch {
|
|
3121
3010
|
}
|
|
3122
|
-
const legacy =
|
|
3011
|
+
const legacy = path9.join(workspacePath, "vault");
|
|
3123
3012
|
try {
|
|
3124
|
-
await
|
|
3013
|
+
await fs7.access(path9.join(legacy, ".ana-vault.json"));
|
|
3125
3014
|
return legacy;
|
|
3126
3015
|
} catch {
|
|
3127
3016
|
return null;
|
|
3128
3017
|
}
|
|
3129
3018
|
}
|
|
3130
|
-
async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE) {
|
|
3131
|
-
const cwdWorkspace = await
|
|
3019
|
+
async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE, homeDir = os9.homedir()) {
|
|
3020
|
+
const cwdWorkspace = await findCwdWorkspaceUpwards(cwd, homeDir);
|
|
3132
3021
|
if (cwdWorkspace) {
|
|
3133
3022
|
return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
|
|
3134
3023
|
}
|
|
3135
3024
|
if (storedPath) {
|
|
3136
3025
|
try {
|
|
3137
|
-
await
|
|
3026
|
+
await fs7.access(path9.join(storedPath, ".ana-config.json"));
|
|
3138
3027
|
return { found: true, workspacePath: storedPath, source: "stored" };
|
|
3139
3028
|
} catch {
|
|
3140
3029
|
}
|
|
@@ -3149,7 +3038,7 @@ var DEFAULT_BASE;
|
|
|
3149
3038
|
var init_resolve = __esm({
|
|
3150
3039
|
"src/workspace/resolve.ts"() {
|
|
3151
3040
|
"use strict";
|
|
3152
|
-
DEFAULT_BASE =
|
|
3041
|
+
DEFAULT_BASE = path9.join(os9.homedir(), "study");
|
|
3153
3042
|
}
|
|
3154
3043
|
});
|
|
3155
3044
|
|
|
@@ -3177,10 +3066,459 @@ var init_status = __esm({
|
|
|
3177
3066
|
}
|
|
3178
3067
|
});
|
|
3179
3068
|
|
|
3069
|
+
// src/learner-brief/api.ts
|
|
3070
|
+
async function apiFetch2(url2, token2, init) {
|
|
3071
|
+
const response = await fetch(url2, {
|
|
3072
|
+
method: init?.method ?? "GET",
|
|
3073
|
+
body: init?.body,
|
|
3074
|
+
headers: {
|
|
3075
|
+
"Content-Type": "application/json",
|
|
3076
|
+
Authorization: `Bearer ${token2}`,
|
|
3077
|
+
...init?.headers ?? {}
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
3080
|
+
const body = await response.json();
|
|
3081
|
+
if (!response.ok) {
|
|
3082
|
+
throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
|
|
3083
|
+
}
|
|
3084
|
+
return body;
|
|
3085
|
+
}
|
|
3086
|
+
async function fetchLearnerBrief(input2) {
|
|
3087
|
+
const response = await apiFetch2(
|
|
3088
|
+
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
3089
|
+
input2.token
|
|
3090
|
+
);
|
|
3091
|
+
return response.brief;
|
|
3092
|
+
}
|
|
3093
|
+
async function upsertLearnerBrief(input2) {
|
|
3094
|
+
const response = await apiFetch2(
|
|
3095
|
+
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
3096
|
+
input2.token,
|
|
3097
|
+
{
|
|
3098
|
+
method: "POST",
|
|
3099
|
+
body: JSON.stringify({ text: input2.text, source: input2.source })
|
|
3100
|
+
}
|
|
3101
|
+
);
|
|
3102
|
+
return response.brief;
|
|
3103
|
+
}
|
|
3104
|
+
var init_api = __esm({
|
|
3105
|
+
"src/learner-brief/api.ts"() {
|
|
3106
|
+
"use strict";
|
|
3107
|
+
init_http2();
|
|
3108
|
+
}
|
|
3109
|
+
});
|
|
3110
|
+
|
|
3111
|
+
// src/learner-brief/bootstrap.ts
|
|
3112
|
+
import readline from "node:readline/promises";
|
|
3113
|
+
import { stdin as processStdin, stdout as processStdout } from "node:process";
|
|
3114
|
+
function composeBriefFromAnswers(answers) {
|
|
3115
|
+
const levelLabel = {
|
|
3116
|
+
beginner: "iniciante",
|
|
3117
|
+
intermediate: "intermediario",
|
|
3118
|
+
advanced: "avancado"
|
|
3119
|
+
}[answers.yourLevel];
|
|
3120
|
+
const paragraphOne = [answers.whoYouAre, answers.whereYouWork, answers.whatYouDo].map((s) => s.trim()).filter(Boolean).join(". ");
|
|
3121
|
+
const paragraphTwo = `Meu nivel neste assunto e ${levelLabel}. ${answers.yourGoals.trim()}`;
|
|
3122
|
+
const paragraphThree = `Contexto real do meu dia a dia: ${answers.realContext.trim()}`;
|
|
3123
|
+
const text2 = [paragraphOne, paragraphTwo, paragraphThree].map((p) => p.replace(/\.\.+/g, ".")).join("\n\n");
|
|
3124
|
+
return text2.slice(0, 5e3);
|
|
3125
|
+
}
|
|
3126
|
+
async function askNonEmpty(question, deps) {
|
|
3127
|
+
while (true) {
|
|
3128
|
+
const answer = (await deps.ask(question)).trim();
|
|
3129
|
+
if (answer.length > 0) return answer;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
async function askLevel(deps) {
|
|
3133
|
+
while (true) {
|
|
3134
|
+
const raw = (await deps.ask(" Seu nivel geral neste assunto (beginner/intermediate/advanced): ")).trim().toLowerCase();
|
|
3135
|
+
if (raw === "beginner" || raw === "intermediate" || raw === "advanced") {
|
|
3136
|
+
return raw;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
async function collectBootstrapAnswersWithDeps(input2, deps) {
|
|
3141
|
+
deps.write(
|
|
3142
|
+
[
|
|
3143
|
+
"",
|
|
3144
|
+
`Ola ${input2.userName}, vamos criar seu brief base (1-2 minutos).`,
|
|
3145
|
+
"Ele ajuda o tutor a te conhecer para adaptar o ensino a voce.",
|
|
3146
|
+
"Voce pode editar depois em https://tostudy.ai/student/settings/learner-brief.",
|
|
3147
|
+
""
|
|
3148
|
+
].join("\n")
|
|
3149
|
+
);
|
|
3150
|
+
const whoYouAre = await askNonEmpty(" Quem voce e (profissao/cargo/area): ", deps);
|
|
3151
|
+
const whereYouWork = await askNonEmpty(" Onde trabalha (empresa/setor): ", deps);
|
|
3152
|
+
const whatYouDo = await askNonEmpty(" O que voce faz (responsabilidades/projetos): ", deps);
|
|
3153
|
+
const yourLevel = await askLevel(deps);
|
|
3154
|
+
const yourGoals = await askNonEmpty(" Seu objetivo de aprendizado: ", deps);
|
|
3155
|
+
const realContext = await askNonEmpty(
|
|
3156
|
+
" Contexto real (exemplos que o tutor pode usar nas aulas): ",
|
|
3157
|
+
deps
|
|
3158
|
+
);
|
|
3159
|
+
return { whoYouAre, whereYouWork, whatYouDo, yourLevel, yourGoals, realContext };
|
|
3160
|
+
}
|
|
3161
|
+
async function collectBootstrapAnswers(input2) {
|
|
3162
|
+
const rl = readline.createInterface({ input: processStdin, output: processStdout });
|
|
3163
|
+
try {
|
|
3164
|
+
return await collectBootstrapAnswersWithDeps(input2, {
|
|
3165
|
+
ask: (q) => rl.question(q),
|
|
3166
|
+
write: (s) => {
|
|
3167
|
+
processStdout.write(s);
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
} finally {
|
|
3171
|
+
rl.close();
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
var init_bootstrap = __esm({
|
|
3175
|
+
"src/learner-brief/bootstrap.ts"() {
|
|
3176
|
+
"use strict";
|
|
3177
|
+
}
|
|
3178
|
+
});
|
|
3179
|
+
|
|
3180
|
+
// src/tutor-persona/api.ts
|
|
3181
|
+
async function fetchTutorPersonality(input2) {
|
|
3182
|
+
const url2 = new URL(`${input2.apiUrl}/api/cli/tutor-personality`);
|
|
3183
|
+
url2.searchParams.set("courseId", input2.courseId);
|
|
3184
|
+
const response = await fetch(url2.toString(), {
|
|
3185
|
+
method: "GET",
|
|
3186
|
+
headers: {
|
|
3187
|
+
"Content-Type": "application/json",
|
|
3188
|
+
Authorization: `Bearer ${input2.token}`
|
|
3189
|
+
}
|
|
3190
|
+
});
|
|
3191
|
+
const body = await response.json();
|
|
3192
|
+
if (!response.ok) {
|
|
3193
|
+
throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
|
|
3194
|
+
}
|
|
3195
|
+
return body.personality ?? null;
|
|
3196
|
+
}
|
|
3197
|
+
var init_api2 = __esm({
|
|
3198
|
+
"src/tutor-persona/api.ts"() {
|
|
3199
|
+
"use strict";
|
|
3200
|
+
init_http2();
|
|
3201
|
+
}
|
|
3202
|
+
});
|
|
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
|
+
|
|
3251
|
+
// src/instruction-pipeline.ts
|
|
3252
|
+
async function defaultWriteFiles(_cwd, _courseMeta, _content) {
|
|
3253
|
+
throw new Error(
|
|
3254
|
+
"writeFiles default implementation is a stub \u2014 inject a real one from the calling command (select/init/sync). Commit 9 will wire the real implementation."
|
|
3255
|
+
);
|
|
3256
|
+
}
|
|
3257
|
+
async function defaultFetchCourseMeta(_input) {
|
|
3258
|
+
throw new Error(
|
|
3259
|
+
"fetchCourseMeta default implementation is a stub \u2014 inject a real one from the calling command (select/init/sync)"
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
async function resolveAndGenerate(input2, options = {}, context) {
|
|
3263
|
+
const deps = context.deps ?? defaultDeps2;
|
|
3264
|
+
const forceRefresh = options.forceRefresh ?? false;
|
|
3265
|
+
const session = await deps.getSession(context.configDir);
|
|
3266
|
+
if (!session) {
|
|
3267
|
+
throw new Error("No active session \u2014 run `tostudy login`");
|
|
3268
|
+
}
|
|
3269
|
+
const courseMeta = await deps.fetchCourseMeta({
|
|
3270
|
+
session,
|
|
3271
|
+
courseId: input2.courseId,
|
|
3272
|
+
enrollmentId: input2.enrollmentId
|
|
3273
|
+
});
|
|
3274
|
+
let tutorPersona = null;
|
|
3275
|
+
if (!forceRefresh) {
|
|
3276
|
+
const cached2 = await deps.readPersonaCacheForCourse(context.configDir, input2.courseId);
|
|
3277
|
+
if (cached2) tutorPersona = cached2.personality;
|
|
3278
|
+
}
|
|
3279
|
+
if (!tutorPersona) {
|
|
3280
|
+
try {
|
|
3281
|
+
tutorPersona = await deps.fetchTutorPersona({
|
|
3282
|
+
apiUrl: session.apiUrl,
|
|
3283
|
+
token: session.token,
|
|
3284
|
+
courseId: input2.courseId
|
|
3285
|
+
});
|
|
3286
|
+
if (tutorPersona) {
|
|
3287
|
+
await deps.writePersonaCacheForCourse(context.configDir, input2.courseId, {
|
|
3288
|
+
personality: tutorPersona,
|
|
3289
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3290
|
+
profileUpdatedAt: tutorPersona.updatedAt
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
} catch (err) {
|
|
3294
|
+
logger5.warn("T0 fetch failed \u2014 rendering without persona", { err });
|
|
3295
|
+
tutorPersona = null;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
let studentBrief = null;
|
|
3299
|
+
let bootstrapped = null;
|
|
3300
|
+
if (!forceRefresh) {
|
|
3301
|
+
const cached2 = await deps.readBriefCache(context.configDir);
|
|
3302
|
+
if (cached2 && cached2.userId === session.userId && !cached2.skipped) {
|
|
3303
|
+
studentBrief = cached2.brief;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
if (!studentBrief) {
|
|
3307
|
+
try {
|
|
3308
|
+
studentBrief = await deps.fetchLearnerBrief({
|
|
3309
|
+
apiUrl: session.apiUrl,
|
|
3310
|
+
token: session.token
|
|
3311
|
+
});
|
|
3312
|
+
await deps.writeBriefCache(context.configDir, {
|
|
3313
|
+
userId: session.userId,
|
|
3314
|
+
brief: studentBrief,
|
|
3315
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3316
|
+
});
|
|
3317
|
+
} catch (err) {
|
|
3318
|
+
logger5.warn("T1 fetch failed \u2014 rendering without brief", { err });
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
if (!studentBrief && options.collectT1Interactive) {
|
|
3322
|
+
try {
|
|
3323
|
+
const answers = await deps.collectBootstrapAnswers({ userName: session.userName });
|
|
3324
|
+
const text2 = composeBriefFromAnswers(answers);
|
|
3325
|
+
studentBrief = await deps.upsertLearnerBrief({
|
|
3326
|
+
apiUrl: session.apiUrl,
|
|
3327
|
+
token: session.token,
|
|
3328
|
+
text: text2,
|
|
3329
|
+
source: "manual"
|
|
3330
|
+
});
|
|
3331
|
+
await deps.writeBriefCache(context.configDir, {
|
|
3332
|
+
userId: session.userId,
|
|
3333
|
+
brief: studentBrief,
|
|
3334
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3335
|
+
});
|
|
3336
|
+
bootstrapped = "t1";
|
|
3337
|
+
} catch (err) {
|
|
3338
|
+
logger5.warn("T1 bootstrap failed", { err });
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
let enrollmentProfile = null;
|
|
3342
|
+
try {
|
|
3343
|
+
const onboarding = await deps.fetchEnrollmentOnboarding({
|
|
3344
|
+
apiUrl: session.apiUrl,
|
|
3345
|
+
token: session.token,
|
|
3346
|
+
enrollmentId: input2.enrollmentId
|
|
3347
|
+
});
|
|
3348
|
+
if (onboarding) {
|
|
3349
|
+
enrollmentProfile = onboarding.learnerProfile;
|
|
3350
|
+
}
|
|
3351
|
+
} catch (err) {
|
|
3352
|
+
logger5.warn("T2 fetch failed \u2014 rendering without enrollment profile", { err });
|
|
3353
|
+
}
|
|
3354
|
+
const content = renderCourseInstruction({
|
|
3355
|
+
course: {
|
|
3356
|
+
courseId: courseMeta.courseId,
|
|
3357
|
+
courseTitle: courseMeta.courseTitle,
|
|
3358
|
+
slug: courseMeta.slug,
|
|
3359
|
+
courseDescription: courseMeta.courseDescription
|
|
3360
|
+
},
|
|
3361
|
+
progress: courseMeta.progress,
|
|
3362
|
+
tutorPersona,
|
|
3363
|
+
studentBrief,
|
|
3364
|
+
enrollmentProfile
|
|
3365
|
+
});
|
|
3366
|
+
const wroteFiles = await deps.writeFiles(context.cwd, courseMeta, content);
|
|
3367
|
+
let markerPath = null;
|
|
3368
|
+
try {
|
|
3369
|
+
markerPath = await deps.writeWorkspaceMarker(context.cwd, {
|
|
3370
|
+
courseId: courseMeta.courseId,
|
|
3371
|
+
enrollmentId: input2.enrollmentId,
|
|
3372
|
+
slug: courseMeta.slug,
|
|
3373
|
+
courseTitle: courseMeta.courseTitle
|
|
3374
|
+
});
|
|
3375
|
+
} catch (err) {
|
|
3376
|
+
logger5.warn("Failed to write workspace marker", { err });
|
|
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
|
+
}
|
|
3391
|
+
return {
|
|
3392
|
+
wroteFiles,
|
|
3393
|
+
markerPath,
|
|
3394
|
+
usedT0: tutorPersona !== null,
|
|
3395
|
+
usedT1: studentBrief !== null,
|
|
3396
|
+
usedT2: enrollmentProfile !== null,
|
|
3397
|
+
bootstrapped,
|
|
3398
|
+
renderedInstruction: content
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
var logger5, defaultDeps2;
|
|
3402
|
+
var init_instruction_pipeline = __esm({
|
|
3403
|
+
"src/instruction-pipeline.ts"() {
|
|
3404
|
+
"use strict";
|
|
3405
|
+
init_src();
|
|
3406
|
+
init_workspace_marker();
|
|
3407
|
+
init_workspace_state();
|
|
3408
|
+
init_api();
|
|
3409
|
+
init_cache();
|
|
3410
|
+
init_bootstrap();
|
|
3411
|
+
init_api2();
|
|
3412
|
+
init_cache2();
|
|
3413
|
+
init_api3();
|
|
3414
|
+
init_session();
|
|
3415
|
+
init_instruction_template_v3();
|
|
3416
|
+
logger5 = createLogger("cli:instruction-pipeline");
|
|
3417
|
+
defaultDeps2 = {
|
|
3418
|
+
fetchTutorPersona: fetchTutorPersonality,
|
|
3419
|
+
fetchLearnerBrief,
|
|
3420
|
+
fetchEnrollmentOnboarding: getRemoteEnrollmentOnboarding,
|
|
3421
|
+
fetchCourseMeta: defaultFetchCourseMeta,
|
|
3422
|
+
readBriefCache,
|
|
3423
|
+
writeBriefCache,
|
|
3424
|
+
readPersonaCacheForCourse,
|
|
3425
|
+
writePersonaCacheForCourse,
|
|
3426
|
+
writeFiles: defaultWriteFiles,
|
|
3427
|
+
writeWorkspaceMarker,
|
|
3428
|
+
collectBootstrapAnswers,
|
|
3429
|
+
upsertLearnerBrief,
|
|
3430
|
+
getSession
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
});
|
|
3434
|
+
|
|
3435
|
+
// src/commands/_shared/pipeline-deps.ts
|
|
3436
|
+
async function fetchCourseMetaViaHttp(input2) {
|
|
3437
|
+
const { session, courseId, enrollmentId } = input2;
|
|
3438
|
+
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3439
|
+
const coreDeps = { data, logger: logger6 };
|
|
3440
|
+
const detail = await selectCourse({ userId: session.userId, courseId }, coreDeps);
|
|
3441
|
+
let currentModuleTitle;
|
|
3442
|
+
let currentLessonTitle;
|
|
3443
|
+
try {
|
|
3444
|
+
const progress3 = await getProgress({ enrollmentId }, coreDeps);
|
|
3445
|
+
currentModuleTitle = progress3.currentModule?.title;
|
|
3446
|
+
currentLessonTitle = progress3.currentLesson?.title;
|
|
3447
|
+
} catch (err) {
|
|
3448
|
+
logger6.debug("getProgress failed \u2014 falling back to basic course meta", {
|
|
3449
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3450
|
+
});
|
|
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
|
+
}
|
|
3466
|
+
return {
|
|
3467
|
+
courseId: detail.courseId,
|
|
3468
|
+
courseTitle: detail.courseTitle,
|
|
3469
|
+
slug: slugify(detail.courseTitle),
|
|
3470
|
+
courseDescription: detail.courseDescription,
|
|
3471
|
+
courseTags,
|
|
3472
|
+
courseLevel,
|
|
3473
|
+
progress: {
|
|
3474
|
+
percent: detail.progress,
|
|
3475
|
+
moduleCount: detail.moduleCount,
|
|
3476
|
+
lessonCount: detail.lessonCount,
|
|
3477
|
+
currentModuleTitle,
|
|
3478
|
+
currentLessonTitle
|
|
3479
|
+
}
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
async function writeFilesAdapter(cwd, courseMeta, content) {
|
|
3483
|
+
return writeInstructionFiles(
|
|
3484
|
+
cwd,
|
|
3485
|
+
{
|
|
3486
|
+
courseTitle: courseMeta.courseTitle,
|
|
3487
|
+
courseId: courseMeta.courseId,
|
|
3488
|
+
progress: courseMeta.progress.percent,
|
|
3489
|
+
moduleCount: courseMeta.progress.moduleCount,
|
|
3490
|
+
lessonCount: courseMeta.progress.lessonCount,
|
|
3491
|
+
currentModuleTitle: courseMeta.progress.currentModuleTitle,
|
|
3492
|
+
currentLessonTitle: courseMeta.progress.currentLessonTitle,
|
|
3493
|
+
courseDescription: courseMeta.courseDescription
|
|
3494
|
+
},
|
|
3495
|
+
content
|
|
3496
|
+
);
|
|
3497
|
+
}
|
|
3498
|
+
function buildPipelineDeps() {
|
|
3499
|
+
return {
|
|
3500
|
+
...defaultDeps2,
|
|
3501
|
+
fetchCourseMeta: fetchCourseMetaViaHttp,
|
|
3502
|
+
writeFiles: writeFilesAdapter
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
var logger6;
|
|
3506
|
+
var init_pipeline_deps = __esm({
|
|
3507
|
+
"src/commands/_shared/pipeline-deps.ts"() {
|
|
3508
|
+
"use strict";
|
|
3509
|
+
init_src();
|
|
3510
|
+
init_courses();
|
|
3511
|
+
init_http2();
|
|
3512
|
+
init_instruction_pipeline();
|
|
3513
|
+
init_instruction_files();
|
|
3514
|
+
logger6 = createLogger("cli:pipeline-deps");
|
|
3515
|
+
}
|
|
3516
|
+
});
|
|
3517
|
+
|
|
3180
3518
|
// src/commands/select.ts
|
|
3181
3519
|
import { Command as Command6 } from "commander";
|
|
3182
|
-
import
|
|
3183
|
-
var
|
|
3520
|
+
import os10 from "node:os";
|
|
3521
|
+
var logger7, selectCommand;
|
|
3184
3522
|
var init_select = __esm({
|
|
3185
3523
|
"src/commands/select.ts"() {
|
|
3186
3524
|
"use strict";
|
|
@@ -3188,15 +3526,18 @@ var init_select = __esm({
|
|
|
3188
3526
|
init_courses();
|
|
3189
3527
|
init_http2();
|
|
3190
3528
|
init_session();
|
|
3529
|
+
init_workspace_marker();
|
|
3530
|
+
init_resolve();
|
|
3191
3531
|
init_formatter();
|
|
3192
|
-
init_instruction_files();
|
|
3193
3532
|
init_status();
|
|
3194
|
-
|
|
3533
|
+
init_instruction_pipeline();
|
|
3534
|
+
init_pipeline_deps();
|
|
3535
|
+
logger7 = createLogger("cli:select");
|
|
3195
3536
|
selectCommand = new Command6("select").description("Activate a course by ID or list index number").argument("<course>", "Course ID (UUID) or index number from `tostudy courses`").option("--json", "Output structured JSON").action(async (course, opts) => {
|
|
3196
3537
|
try {
|
|
3197
3538
|
const session = await requireSession();
|
|
3198
3539
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3199
|
-
const deps = { data, logger:
|
|
3540
|
+
const deps = { data, logger: logger7 };
|
|
3200
3541
|
const courses3 = await listCourses({ userId: session.userId }, deps);
|
|
3201
3542
|
let courseId = course;
|
|
3202
3543
|
let enrollmentId = "";
|
|
@@ -3214,14 +3555,12 @@ var init_select = __esm({
|
|
|
3214
3555
|
enrollmentId = found.enrollmentId;
|
|
3215
3556
|
}
|
|
3216
3557
|
}
|
|
3217
|
-
const matched = courses3.find((c) => c.courseId === courseId);
|
|
3218
3558
|
const detail = await selectCourse({ userId: session.userId, courseId }, deps);
|
|
3219
|
-
await
|
|
3559
|
+
await writeWorkspaceMarker(process.cwd(), {
|
|
3220
3560
|
courseId: detail.courseId,
|
|
3221
|
-
courseTitle: detail.courseTitle,
|
|
3222
3561
|
enrollmentId,
|
|
3223
|
-
|
|
3224
|
-
|
|
3562
|
+
slug: courseSlug(detail.courseTitle),
|
|
3563
|
+
courseTitle: detail.courseTitle
|
|
3225
3564
|
});
|
|
3226
3565
|
const activeCourseForStatus = {
|
|
3227
3566
|
courseId: detail.courseId,
|
|
@@ -3229,23 +3568,20 @@ var init_select = __esm({
|
|
|
3229
3568
|
enrollmentId
|
|
3230
3569
|
};
|
|
3231
3570
|
const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
|
|
3232
|
-
let
|
|
3571
|
+
let pipelineResult = null;
|
|
3233
3572
|
try {
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
lessonCount: detail.lessonCount,
|
|
3240
|
-
courseDescription: detail.courseDescription,
|
|
3241
|
-
workspaceReady: onboarding.workspaceReady,
|
|
3242
|
-
workspacePath: onboarding.workspacePath ?? void 0
|
|
3243
|
-
});
|
|
3573
|
+
pipelineResult = await resolveAndGenerate(
|
|
3574
|
+
{ courseId: detail.courseId, enrollmentId },
|
|
3575
|
+
{ forceRefresh: false, collectT1Interactive: false, collectT2Interactive: false },
|
|
3576
|
+
{ cwd: process.cwd(), deps: buildPipelineDeps() }
|
|
3577
|
+
);
|
|
3244
3578
|
} catch (err) {
|
|
3245
|
-
|
|
3579
|
+
logger7.warn("Failed to run instruction pipeline", {
|
|
3246
3580
|
error: err instanceof Error ? err.message : String(err)
|
|
3247
3581
|
});
|
|
3248
3582
|
}
|
|
3583
|
+
const slugMatch = pipelineResult?.wroteFiles.find((f) => f.startsWith(".claude/commands/"))?.match(/tostudy-(.+)\.md$/);
|
|
3584
|
+
const courseSlug2 = slugMatch?.[1] ?? "";
|
|
3249
3585
|
if (opts.json) {
|
|
3250
3586
|
output(
|
|
3251
3587
|
{
|
|
@@ -3253,13 +3589,19 @@ var init_select = __esm({
|
|
|
3253
3589
|
enrollmentId,
|
|
3254
3590
|
courseSlug: courseSlug2,
|
|
3255
3591
|
workspacePath: onboarding.workspacePath,
|
|
3256
|
-
workspaceSource: onboarding.workspaceSource
|
|
3592
|
+
workspaceSource: onboarding.workspaceSource,
|
|
3593
|
+
wroteFiles: pipelineResult?.wroteFiles ?? [],
|
|
3594
|
+
markerPath: pipelineResult?.markerPath ?? null,
|
|
3595
|
+
usedT0: pipelineResult?.usedT0 ?? false,
|
|
3596
|
+
usedT1: pipelineResult?.usedT1 ?? false,
|
|
3597
|
+
usedT2: pipelineResult?.usedT2 ?? false,
|
|
3598
|
+
instruction: pipelineResult?.renderedInstruction ?? null
|
|
3257
3599
|
},
|
|
3258
3600
|
{ json: true }
|
|
3259
3601
|
);
|
|
3260
3602
|
} else {
|
|
3261
3603
|
const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
|
|
3262
|
-
const home =
|
|
3604
|
+
const home = os10.homedir();
|
|
3263
3605
|
const cwd = process.cwd();
|
|
3264
3606
|
const namespacedPath = `${cwd}/.tostudy`;
|
|
3265
3607
|
let wsLine;
|
|
@@ -3272,20 +3614,26 @@ var init_select = __esm({
|
|
|
3272
3614
|
} else {
|
|
3273
3615
|
wsLine = " Workspace: rode `tostudy workspace setup` para configurar";
|
|
3274
3616
|
}
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3617
|
+
const lines = [
|
|
3618
|
+
`\u2713 Curso ativado: ${detail.courseTitle}`,
|
|
3619
|
+
` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
|
|
3620
|
+
wsLine,
|
|
3621
|
+
"",
|
|
3622
|
+
" Arquivos de contexto criados para seu assistente AI."
|
|
3623
|
+
];
|
|
3624
|
+
if (pipelineResult && !pipelineResult.usedT0) {
|
|
3625
|
+
lines.push(" (sem persona do creator \u2014 usando tutor neutro)");
|
|
3626
|
+
}
|
|
3627
|
+
if (pipelineResult && !pipelineResult.usedT1) {
|
|
3628
|
+
lines.push(" (sem brief base \u2014 rode `tostudy init` para configurar)");
|
|
3629
|
+
}
|
|
3630
|
+
lines.push(
|
|
3631
|
+
"",
|
|
3632
|
+
"\u2192 Abra sua plataforma (claude, codex, cursor...)",
|
|
3633
|
+
`\u2192 No Claude Code, digite: ${slashCmd}`,
|
|
3634
|
+
"\u2192 Em outras plataformas: tostudy init"
|
|
3288
3635
|
);
|
|
3636
|
+
output(lines.join("\n"), { json: false });
|
|
3289
3637
|
}
|
|
3290
3638
|
} catch (err) {
|
|
3291
3639
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3297,7 +3645,7 @@ var init_select = __esm({
|
|
|
3297
3645
|
|
|
3298
3646
|
// src/commands/progress.ts
|
|
3299
3647
|
import { Command as Command7 } from "commander";
|
|
3300
|
-
var
|
|
3648
|
+
var logger8, progressCommand;
|
|
3301
3649
|
var init_progress = __esm({
|
|
3302
3650
|
"src/commands/progress.ts"() {
|
|
3303
3651
|
"use strict";
|
|
@@ -3306,7 +3654,7 @@ var init_progress = __esm({
|
|
|
3306
3654
|
init_http2();
|
|
3307
3655
|
init_session();
|
|
3308
3656
|
init_formatter();
|
|
3309
|
-
|
|
3657
|
+
logger8 = createLogger("cli:progress");
|
|
3310
3658
|
progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
3311
3659
|
try {
|
|
3312
3660
|
const session = await requireSession();
|
|
@@ -3314,7 +3662,7 @@ var init_progress = __esm({
|
|
|
3314
3662
|
const driftWarning = await checkCourseDrift();
|
|
3315
3663
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
3316
3664
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3317
|
-
const deps = { data, logger:
|
|
3665
|
+
const deps = { data, logger: logger8 };
|
|
3318
3666
|
const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
3319
3667
|
if (opts.json) {
|
|
3320
3668
|
output(progress3, { json: true });
|
|
@@ -4132,7 +4480,7 @@ var init_subquery = __esm({
|
|
|
4132
4480
|
var version;
|
|
4133
4481
|
var init_version = __esm({
|
|
4134
4482
|
"../../node_modules/drizzle-orm/version.js"() {
|
|
4135
|
-
version = "0.45.
|
|
4483
|
+
version = "0.45.2";
|
|
4136
4484
|
}
|
|
4137
4485
|
});
|
|
4138
4486
|
|
|
@@ -4573,7 +4921,7 @@ var init_sql = __esm({
|
|
|
4573
4921
|
return new SQL([new StringChunk(str)]);
|
|
4574
4922
|
}
|
|
4575
4923
|
sql2.raw = raw;
|
|
4576
|
-
function
|
|
4924
|
+
function join3(chunks, separator) {
|
|
4577
4925
|
const result = [];
|
|
4578
4926
|
for (const [i, chunk] of chunks.entries()) {
|
|
4579
4927
|
if (i > 0 && separator !== void 0) {
|
|
@@ -4583,7 +4931,7 @@ var init_sql = __esm({
|
|
|
4583
4931
|
}
|
|
4584
4932
|
return new SQL(result);
|
|
4585
4933
|
}
|
|
4586
|
-
sql2.join =
|
|
4934
|
+
sql2.join = join3;
|
|
4587
4935
|
function identifier(value) {
|
|
4588
4936
|
return new Name(value);
|
|
4589
4937
|
}
|
|
@@ -4875,7 +5223,7 @@ var init_query_promise = __esm({
|
|
|
4875
5223
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
4876
5224
|
const nullifyMap = {};
|
|
4877
5225
|
const result = columns.reduce(
|
|
4878
|
-
(result2, { path:
|
|
5226
|
+
(result2, { path: path19, field }, columnIndex) => {
|
|
4879
5227
|
let decoder;
|
|
4880
5228
|
if (is(field, Column)) {
|
|
4881
5229
|
decoder = field;
|
|
@@ -4887,8 +5235,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4887
5235
|
decoder = field.sql.decoder;
|
|
4888
5236
|
}
|
|
4889
5237
|
let node = result2;
|
|
4890
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
4891
|
-
if (pathChunkIndex <
|
|
5238
|
+
for (const [pathChunkIndex, pathChunk] of path19.entries()) {
|
|
5239
|
+
if (pathChunkIndex < path19.length - 1) {
|
|
4892
5240
|
if (!(pathChunk in node)) {
|
|
4893
5241
|
node[pathChunk] = {};
|
|
4894
5242
|
}
|
|
@@ -4896,8 +5244,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4896
5244
|
} else {
|
|
4897
5245
|
const rawValue = row[columnIndex];
|
|
4898
5246
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
4899
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
4900
|
-
const objectName =
|
|
5247
|
+
if (joinsNotNullableMap && is(field, Column) && path19.length === 2) {
|
|
5248
|
+
const objectName = path19[0];
|
|
4901
5249
|
if (!(objectName in nullifyMap)) {
|
|
4902
5250
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
4903
5251
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -8844,13 +9192,13 @@ function Subscribe(postgres2, options) {
|
|
|
8844
9192
|
}
|
|
8845
9193
|
}
|
|
8846
9194
|
function handle(a, b2) {
|
|
8847
|
-
const
|
|
9195
|
+
const path19 = b2.relation.schema + "." + b2.relation.table;
|
|
8848
9196
|
call("*", a, b2);
|
|
8849
|
-
call("*:" +
|
|
8850
|
-
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);
|
|
8851
9199
|
call(b2.command, a, b2);
|
|
8852
|
-
call(b2.command + ":" +
|
|
8853
|
-
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);
|
|
8854
9202
|
}
|
|
8855
9203
|
function pong() {
|
|
8856
9204
|
const x2 = Buffer.alloc(34);
|
|
@@ -8963,8 +9311,8 @@ function parseEvent(x) {
|
|
|
8963
9311
|
const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
|
|
8964
9312
|
if (!xs)
|
|
8965
9313
|
throw new Error("Malformed subscribe pattern: " + x);
|
|
8966
|
-
const [, command,
|
|
8967
|
-
return (command || "*") + (
|
|
9314
|
+
const [, command, path19, key] = xs;
|
|
9315
|
+
return (command || "*") + (path19 ? ":" + (path19.indexOf(".") === -1 ? "public." + path19 : path19) : "") + (key ? "=" + key : "");
|
|
8968
9316
|
}
|
|
8969
9317
|
var noop2;
|
|
8970
9318
|
var init_subscribe = __esm({
|
|
@@ -9045,8 +9393,8 @@ var init_large = __esm({
|
|
|
9045
9393
|
});
|
|
9046
9394
|
|
|
9047
9395
|
// ../../node_modules/postgres/src/index.js
|
|
9048
|
-
import
|
|
9049
|
-
import
|
|
9396
|
+
import os11 from "os";
|
|
9397
|
+
import fs8 from "fs";
|
|
9050
9398
|
function Postgres(a, b2) {
|
|
9051
9399
|
const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
|
|
9052
9400
|
let ending = false;
|
|
@@ -9102,10 +9450,10 @@ function Postgres(a, b2) {
|
|
|
9102
9450
|
});
|
|
9103
9451
|
return query;
|
|
9104
9452
|
}
|
|
9105
|
-
function file2(
|
|
9453
|
+
function file2(path19, args = [], options2 = {}) {
|
|
9106
9454
|
arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
|
|
9107
9455
|
const query = new Query([], args, (query2) => {
|
|
9108
|
-
|
|
9456
|
+
fs8.readFile(path19, "utf8", (err, string4) => {
|
|
9109
9457
|
if (err)
|
|
9110
9458
|
return query2.reject(err);
|
|
9111
9459
|
query2.strings = [string4];
|
|
@@ -9423,7 +9771,7 @@ function parseUrl(url2) {
|
|
|
9423
9771
|
}
|
|
9424
9772
|
function osUsername() {
|
|
9425
9773
|
try {
|
|
9426
|
-
return
|
|
9774
|
+
return os11.userInfo().username;
|
|
9427
9775
|
} catch (_) {
|
|
9428
9776
|
return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
|
|
9429
9777
|
}
|
|
@@ -9901,7 +10249,7 @@ var init_dialect = __esm({
|
|
|
9901
10249
|
});
|
|
9902
10250
|
}
|
|
9903
10251
|
escapeName(name) {
|
|
9904
|
-
return `"${name}"`;
|
|
10252
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
9905
10253
|
}
|
|
9906
10254
|
escapeParam(num) {
|
|
9907
10255
|
return `$${num + 1}`;
|
|
@@ -11115,7 +11463,7 @@ var init_select3 = __esm({
|
|
|
11115
11463
|
const baseTableName = this.tableName;
|
|
11116
11464
|
const tableName = getTableLikeName(table);
|
|
11117
11465
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
11118
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
11466
|
+
if (typeof tableName === "string" && this.config.joins?.some((join3) => join3.alias === tableName)) {
|
|
11119
11467
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
11120
11468
|
}
|
|
11121
11469
|
if (!this.isPartialSelect) {
|
|
@@ -12642,7 +12990,7 @@ var init_update = __esm({
|
|
|
12642
12990
|
createJoin(joinType) {
|
|
12643
12991
|
return (table, on) => {
|
|
12644
12992
|
const tableName = getTableLikeName(table);
|
|
12645
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
12993
|
+
if (typeof tableName === "string" && this.config.joins.some((join3) => join3.alias === tableName)) {
|
|
12646
12994
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
12647
12995
|
}
|
|
12648
12996
|
if (typeof on === "function") {
|
|
@@ -12738,10 +13086,10 @@ var init_update = __esm({
|
|
|
12738
13086
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
12739
13087
|
fields[tableName] = fromFields;
|
|
12740
13088
|
}
|
|
12741
|
-
for (const
|
|
12742
|
-
const tableName2 = getTableLikeName(
|
|
12743
|
-
if (typeof tableName2 === "string" && !is(
|
|
12744
|
-
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);
|
|
12745
13093
|
fields[tableName2] = fromFields;
|
|
12746
13094
|
}
|
|
12747
13095
|
}
|
|
@@ -13331,7 +13679,7 @@ async function hashQuery(sql2, params) {
|
|
|
13331
13679
|
return hashHex;
|
|
13332
13680
|
}
|
|
13333
13681
|
var Cache, NoopCache;
|
|
13334
|
-
var
|
|
13682
|
+
var init_cache3 = __esm({
|
|
13335
13683
|
"../../node_modules/drizzle-orm/cache/core/cache.js"() {
|
|
13336
13684
|
init_entity();
|
|
13337
13685
|
Cache = class {
|
|
@@ -13356,7 +13704,7 @@ var init_cache = __esm({
|
|
|
13356
13704
|
// ../../node_modules/drizzle-orm/cache/core/index.js
|
|
13357
13705
|
var init_core = __esm({
|
|
13358
13706
|
"../../node_modules/drizzle-orm/cache/core/index.js"() {
|
|
13359
|
-
|
|
13707
|
+
init_cache3();
|
|
13360
13708
|
}
|
|
13361
13709
|
});
|
|
13362
13710
|
|
|
@@ -13464,7 +13812,7 @@ var init_schema = __esm({
|
|
|
13464
13812
|
var PgPreparedQuery, PgSession, PgTransaction;
|
|
13465
13813
|
var init_session2 = __esm({
|
|
13466
13814
|
"../../node_modules/drizzle-orm/pg-core/session.js"() {
|
|
13467
|
-
|
|
13815
|
+
init_cache3();
|
|
13468
13816
|
init_entity();
|
|
13469
13817
|
init_errors();
|
|
13470
13818
|
init_sql2();
|
|
@@ -13684,12 +14032,12 @@ var init_session3 = __esm({
|
|
|
13684
14032
|
init_tracing();
|
|
13685
14033
|
init_utils();
|
|
13686
14034
|
PostgresJsPreparedQuery = class extends PgPreparedQuery {
|
|
13687
|
-
constructor(client, queryString, params,
|
|
14035
|
+
constructor(client, queryString, params, logger26, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
|
|
13688
14036
|
super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
|
|
13689
14037
|
this.client = client;
|
|
13690
14038
|
this.queryString = queryString;
|
|
13691
14039
|
this.params = params;
|
|
13692
|
-
this.logger =
|
|
14040
|
+
this.logger = logger26;
|
|
13693
14041
|
this.fields = fields;
|
|
13694
14042
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
13695
14043
|
this.customResultMapper = customResultMapper;
|
|
@@ -13830,11 +14178,11 @@ function construct(client, config2 = {}) {
|
|
|
13830
14178
|
client.options.serializers["114"] = transparentParser;
|
|
13831
14179
|
client.options.serializers["3802"] = transparentParser;
|
|
13832
14180
|
const dialect = new PgDialect({ casing: config2.casing });
|
|
13833
|
-
let
|
|
14181
|
+
let logger26;
|
|
13834
14182
|
if (config2.logger === true) {
|
|
13835
|
-
|
|
14183
|
+
logger26 = new DefaultLogger();
|
|
13836
14184
|
} else if (config2.logger !== false) {
|
|
13837
|
-
|
|
14185
|
+
logger26 = config2.logger;
|
|
13838
14186
|
}
|
|
13839
14187
|
let schema;
|
|
13840
14188
|
if (config2.schema) {
|
|
@@ -13848,7 +14196,7 @@ function construct(client, config2 = {}) {
|
|
|
13848
14196
|
tableNamesMap: tablesConfig.tableNamesMap
|
|
13849
14197
|
};
|
|
13850
14198
|
}
|
|
13851
|
-
const session = new PostgresJsSession(client, dialect, schema, { logger:
|
|
14199
|
+
const session = new PostgresJsSession(client, dialect, schema, { logger: logger26, cache: config2.cache });
|
|
13852
14200
|
const db2 = new PostgresJsDatabase(dialect, session, schema);
|
|
13853
14201
|
db2.$client = client;
|
|
13854
14202
|
db2.$cache = config2.cache;
|
|
@@ -14163,10 +14511,10 @@ function mergeDefs(...defs) {
|
|
|
14163
14511
|
function cloneDef(schema) {
|
|
14164
14512
|
return mergeDefs(schema._zod.def);
|
|
14165
14513
|
}
|
|
14166
|
-
function getElementAtPath(obj,
|
|
14167
|
-
if (!
|
|
14514
|
+
function getElementAtPath(obj, path19) {
|
|
14515
|
+
if (!path19)
|
|
14168
14516
|
return obj;
|
|
14169
|
-
return
|
|
14517
|
+
return path19.reduce((acc, key) => acc?.[key], obj);
|
|
14170
14518
|
}
|
|
14171
14519
|
function promiseAllObject(promisesObj) {
|
|
14172
14520
|
const keys = Object.keys(promisesObj);
|
|
@@ -14478,11 +14826,11 @@ function aborted(x, startIndex = 0) {
|
|
|
14478
14826
|
}
|
|
14479
14827
|
return false;
|
|
14480
14828
|
}
|
|
14481
|
-
function prefixIssues(
|
|
14829
|
+
function prefixIssues(path19, issues) {
|
|
14482
14830
|
return issues.map((iss) => {
|
|
14483
14831
|
var _a2;
|
|
14484
14832
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
14485
|
-
iss.path.unshift(
|
|
14833
|
+
iss.path.unshift(path19);
|
|
14486
14834
|
return iss;
|
|
14487
14835
|
});
|
|
14488
14836
|
}
|
|
@@ -14724,7 +15072,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14724
15072
|
}
|
|
14725
15073
|
function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
14726
15074
|
const result = { errors: [] };
|
|
14727
|
-
const processError = (error50,
|
|
15075
|
+
const processError = (error50, path19 = []) => {
|
|
14728
15076
|
var _a2, _b;
|
|
14729
15077
|
for (const issue2 of error50.issues) {
|
|
14730
15078
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -14734,7 +15082,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14734
15082
|
} else if (issue2.code === "invalid_element") {
|
|
14735
15083
|
processError({ issues: issue2.issues }, issue2.path);
|
|
14736
15084
|
} else {
|
|
14737
|
-
const fullpath = [...
|
|
15085
|
+
const fullpath = [...path19, ...issue2.path];
|
|
14738
15086
|
if (fullpath.length === 0) {
|
|
14739
15087
|
result.errors.push(mapper(issue2));
|
|
14740
15088
|
continue;
|
|
@@ -14766,8 +15114,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14766
15114
|
}
|
|
14767
15115
|
function toDotPath(_path) {
|
|
14768
15116
|
const segs = [];
|
|
14769
|
-
const
|
|
14770
|
-
for (const seg of
|
|
15117
|
+
const path19 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
15118
|
+
for (const seg of path19) {
|
|
14771
15119
|
if (typeof seg === "number")
|
|
14772
15120
|
segs.push(`[${seg}]`);
|
|
14773
15121
|
else if (typeof seg === "symbol")
|
|
@@ -24678,7 +25026,7 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
|
|
|
24678
25026
|
return inst;
|
|
24679
25027
|
}
|
|
24680
25028
|
var TimePrecision;
|
|
24681
|
-
var
|
|
25029
|
+
var init_api4 = __esm({
|
|
24682
25030
|
"../../node_modules/zod/v4/core/api.js"() {
|
|
24683
25031
|
init_checks2();
|
|
24684
25032
|
init_registries();
|
|
@@ -25989,7 +26337,7 @@ var init_core3 = __esm({
|
|
|
25989
26337
|
init_locales();
|
|
25990
26338
|
init_registries();
|
|
25991
26339
|
init_doc();
|
|
25992
|
-
|
|
26340
|
+
init_api4();
|
|
25993
26341
|
init_to_json_schema();
|
|
25994
26342
|
init_json_schema_processors();
|
|
25995
26343
|
init_json_schema_generator();
|
|
@@ -27461,13 +27809,13 @@ function resolveRef(ref, ctx) {
|
|
|
27461
27809
|
if (!ref.startsWith("#")) {
|
|
27462
27810
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
27463
27811
|
}
|
|
27464
|
-
const
|
|
27465
|
-
if (
|
|
27812
|
+
const path19 = ref.slice(1).split("/").filter(Boolean);
|
|
27813
|
+
if (path19.length === 0) {
|
|
27466
27814
|
return ctx.rootSchema;
|
|
27467
27815
|
}
|
|
27468
27816
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
27469
|
-
if (
|
|
27470
|
-
const key =
|
|
27817
|
+
if (path19[0] === defsKey) {
|
|
27818
|
+
const key = path19[1];
|
|
27471
27819
|
if (!key || !ctx.defs[key]) {
|
|
27472
27820
|
throw new Error(`Reference not found: ${ref}`);
|
|
27473
27821
|
}
|
|
@@ -38785,27 +39133,33 @@ var init_media_assets = __esm({
|
|
|
38785
39133
|
"marketing",
|
|
38786
39134
|
"other"
|
|
38787
39135
|
];
|
|
38788
|
-
mediaAssets = pgTable(
|
|
38789
|
-
|
|
38790
|
-
|
|
38791
|
-
|
|
38792
|
-
|
|
38793
|
-
|
|
38794
|
-
|
|
38795
|
-
|
|
38796
|
-
|
|
38797
|
-
|
|
38798
|
-
|
|
38799
|
-
|
|
38800
|
-
|
|
38801
|
-
|
|
38802
|
-
|
|
38803
|
-
|
|
38804
|
-
|
|
38805
|
-
|
|
38806
|
-
|
|
38807
|
-
|
|
38808
|
-
|
|
39136
|
+
mediaAssets = pgTable(
|
|
39137
|
+
"media_assets",
|
|
39138
|
+
{
|
|
39139
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
39140
|
+
url: text("url").notNull(),
|
|
39141
|
+
r2Key: text("r2_key").notNull(),
|
|
39142
|
+
thumbnailUrl: text("thumbnail_url"),
|
|
39143
|
+
type: text("type", { enum: mediaAssetTypeEnum }).notNull(),
|
|
39144
|
+
mimeType: text("mime_type").notNull(),
|
|
39145
|
+
title: text("title"),
|
|
39146
|
+
originalFilename: text("original_filename").notNull(),
|
|
39147
|
+
size: integer("size").notNull(),
|
|
39148
|
+
width: integer("width"),
|
|
39149
|
+
height: integer("height"),
|
|
39150
|
+
tags: text("tags").array().default([]),
|
|
39151
|
+
uploadedById: uuid("uploaded_by_id").references(() => users.id, {
|
|
39152
|
+
onDelete: "set null"
|
|
39153
|
+
}),
|
|
39154
|
+
domain: text("domain", { enum: mediaAssetDomainEnum }),
|
|
39155
|
+
domainEntityId: uuid("domain_entity_id"),
|
|
39156
|
+
status: text("status", { enum: mediaAssetStatusEnum }).notNull().default("pending"),
|
|
39157
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
39158
|
+
},
|
|
39159
|
+
(table) => ({
|
|
39160
|
+
r2KeyUnique: uniqueIndex("media_assets_r2_key_unique").on(table.r2Key)
|
|
39161
|
+
})
|
|
39162
|
+
);
|
|
38809
39163
|
}
|
|
38810
39164
|
});
|
|
38811
39165
|
|
|
@@ -40995,7 +41349,7 @@ var init_lessons2 = __esm({
|
|
|
40995
41349
|
|
|
40996
41350
|
// src/commands/start.ts
|
|
40997
41351
|
import { Command as Command8 } from "commander";
|
|
40998
|
-
async function runStart(opts, deps =
|
|
41352
|
+
async function runStart(opts, deps = defaultDeps3) {
|
|
40999
41353
|
try {
|
|
41000
41354
|
const session = await deps.requireSession();
|
|
41001
41355
|
const activeCourse = await deps.requireActiveCourse();
|
|
@@ -41011,7 +41365,12 @@ async function runStart(opts, deps = defaultDeps2) {
|
|
|
41011
41365
|
{ enrollmentId: activeCourse.enrollmentId },
|
|
41012
41366
|
{ data, logger: deps.logger }
|
|
41013
41367
|
);
|
|
41014
|
-
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
|
+
});
|
|
41015
41374
|
if (opts.json) {
|
|
41016
41375
|
deps.output(moduleData, { json: true });
|
|
41017
41376
|
} else {
|
|
@@ -41027,7 +41386,7 @@ async function runStart(opts, deps = defaultDeps2) {
|
|
|
41027
41386
|
deps.error(msg);
|
|
41028
41387
|
}
|
|
41029
41388
|
}
|
|
41030
|
-
var
|
|
41389
|
+
var logger9, defaultDeps3, startCommand;
|
|
41031
41390
|
var init_start = __esm({
|
|
41032
41391
|
"src/commands/start.ts"() {
|
|
41033
41392
|
"use strict";
|
|
@@ -41035,22 +41394,24 @@ var init_start = __esm({
|
|
|
41035
41394
|
init_lessons2();
|
|
41036
41395
|
init_http2();
|
|
41037
41396
|
init_session();
|
|
41397
|
+
init_workspace_state();
|
|
41038
41398
|
init_status();
|
|
41039
41399
|
init_formatter();
|
|
41040
|
-
|
|
41041
|
-
|
|
41400
|
+
logger9 = createLogger("cli:start");
|
|
41401
|
+
defaultDeps3 = {
|
|
41042
41402
|
requireSession,
|
|
41043
41403
|
requireActiveCourse,
|
|
41044
41404
|
checkCourseDrift,
|
|
41045
41405
|
getCourseOnboardingStatus,
|
|
41046
41406
|
createHttpProvider,
|
|
41047
41407
|
startModule,
|
|
41048
|
-
|
|
41408
|
+
findWorkspaceState,
|
|
41409
|
+
updateWorkspaceState,
|
|
41049
41410
|
formatModuleStart,
|
|
41050
41411
|
output,
|
|
41051
41412
|
error,
|
|
41052
41413
|
stderrWrite: (message) => process.stderr.write(message),
|
|
41053
|
-
logger:
|
|
41414
|
+
logger: logger9
|
|
41054
41415
|
};
|
|
41055
41416
|
startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41056
41417
|
await runStart(opts);
|
|
@@ -41060,7 +41421,7 @@ var init_start = __esm({
|
|
|
41060
41421
|
|
|
41061
41422
|
// src/commands/start-next.ts
|
|
41062
41423
|
import { Command as Command9 } from "commander";
|
|
41063
|
-
var
|
|
41424
|
+
var logger10, startNextCommand;
|
|
41064
41425
|
var init_start_next = __esm({
|
|
41065
41426
|
"src/commands/start-next.ts"() {
|
|
41066
41427
|
"use strict";
|
|
@@ -41068,8 +41429,9 @@ var init_start_next = __esm({
|
|
|
41068
41429
|
init_lessons2();
|
|
41069
41430
|
init_http2();
|
|
41070
41431
|
init_session();
|
|
41432
|
+
init_workspace_state();
|
|
41071
41433
|
init_formatter();
|
|
41072
|
-
|
|
41434
|
+
logger10 = createLogger("cli:start-next");
|
|
41073
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) => {
|
|
41074
41436
|
try {
|
|
41075
41437
|
const session = await requireSession();
|
|
@@ -41077,9 +41439,14 @@ var init_start_next = __esm({
|
|
|
41077
41439
|
const driftWarning = await checkCourseDrift();
|
|
41078
41440
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41079
41441
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41080
|
-
const deps = { data, logger:
|
|
41442
|
+
const deps = { data, logger: logger10 };
|
|
41081
41443
|
const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
41082
|
-
|
|
41444
|
+
const ws = await findWorkspaceState();
|
|
41445
|
+
if (ws)
|
|
41446
|
+
await updateWorkspaceState(ws.workspacePath, {
|
|
41447
|
+
currentLessonId: moduleData.firstLesson.id,
|
|
41448
|
+
currentModuleId: moduleData.module.id
|
|
41449
|
+
});
|
|
41083
41450
|
if (opts.json) {
|
|
41084
41451
|
output(moduleData, { json: true });
|
|
41085
41452
|
} else {
|
|
@@ -41092,6 +41459,7 @@ var init_start_next = __esm({
|
|
|
41092
41459
|
process.exit(0);
|
|
41093
41460
|
}
|
|
41094
41461
|
const msg = err instanceof Error ? err.message : String(err);
|
|
41462
|
+
if (opts.json) jsonError(msg);
|
|
41095
41463
|
error(msg);
|
|
41096
41464
|
}
|
|
41097
41465
|
});
|
|
@@ -41100,7 +41468,7 @@ var init_start_next = __esm({
|
|
|
41100
41468
|
|
|
41101
41469
|
// src/commands/next.ts
|
|
41102
41470
|
import { Command as Command10 } from "commander";
|
|
41103
|
-
var
|
|
41471
|
+
var logger11, nextCommand;
|
|
41104
41472
|
var init_next = __esm({
|
|
41105
41473
|
"src/commands/next.ts"() {
|
|
41106
41474
|
"use strict";
|
|
@@ -41108,8 +41476,9 @@ var init_next = __esm({
|
|
|
41108
41476
|
init_lessons2();
|
|
41109
41477
|
init_http2();
|
|
41110
41478
|
init_session();
|
|
41479
|
+
init_workspace_state();
|
|
41111
41480
|
init_formatter();
|
|
41112
|
-
|
|
41481
|
+
logger11 = createLogger("cli:next");
|
|
41113
41482
|
nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41114
41483
|
try {
|
|
41115
41484
|
const session = await requireSession();
|
|
@@ -41117,12 +41486,17 @@ var init_next = __esm({
|
|
|
41117
41486
|
const driftWarning = await checkCourseDrift();
|
|
41118
41487
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41119
41488
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41120
|
-
const deps = { data, logger:
|
|
41489
|
+
const deps = { data, logger: logger11 };
|
|
41121
41490
|
const lessonData = await nextLesson(
|
|
41122
41491
|
{ enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
|
|
41123
41492
|
deps
|
|
41124
41493
|
);
|
|
41125
|
-
|
|
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
|
+
});
|
|
41126
41500
|
if (opts.json) {
|
|
41127
41501
|
output(lessonData, { json: true });
|
|
41128
41502
|
} else {
|
|
@@ -41152,6 +41526,7 @@ var init_next = __esm({
|
|
|
41152
41526
|
}
|
|
41153
41527
|
}
|
|
41154
41528
|
const msg = err instanceof Error ? err.message : String(err);
|
|
41529
|
+
if (opts.json) jsonError(msg);
|
|
41155
41530
|
error(msg);
|
|
41156
41531
|
}
|
|
41157
41532
|
});
|
|
@@ -41198,7 +41573,7 @@ function formatLessonContent(data) {
|
|
|
41198
41573
|
}
|
|
41199
41574
|
return lines.join("\n");
|
|
41200
41575
|
}
|
|
41201
|
-
var
|
|
41576
|
+
var logger12, lessonCommand;
|
|
41202
41577
|
var init_lesson = __esm({
|
|
41203
41578
|
"src/commands/lesson.ts"() {
|
|
41204
41579
|
"use strict";
|
|
@@ -41208,7 +41583,7 @@ var init_lesson = __esm({
|
|
|
41208
41583
|
init_session();
|
|
41209
41584
|
init_formatter();
|
|
41210
41585
|
init_resolve();
|
|
41211
|
-
|
|
41586
|
+
logger12 = createLogger("cli:lesson");
|
|
41212
41587
|
lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41213
41588
|
try {
|
|
41214
41589
|
const session = await requireSession();
|
|
@@ -41216,9 +41591,10 @@ var init_lesson = __esm({
|
|
|
41216
41591
|
const driftWarning = await checkCourseDrift();
|
|
41217
41592
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41218
41593
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41219
|
-
const deps = { data, logger:
|
|
41594
|
+
const deps = { data, logger: logger12 };
|
|
41220
41595
|
const lessonId = activeCourse.currentLessonId;
|
|
41221
41596
|
if (!lessonId) {
|
|
41597
|
+
if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
|
|
41222
41598
|
error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
|
|
41223
41599
|
}
|
|
41224
41600
|
const content = await getContent({ lessonId, enrollmentId: activeCourse.enrollmentId }, deps);
|
|
@@ -41241,6 +41617,7 @@ var init_lesson = __esm({
|
|
|
41241
41617
|
}
|
|
41242
41618
|
} catch (err) {
|
|
41243
41619
|
const msg = err instanceof Error ? err.message : String(err);
|
|
41620
|
+
if (opts.json) jsonError(msg);
|
|
41244
41621
|
error(msg);
|
|
41245
41622
|
}
|
|
41246
41623
|
});
|
|
@@ -41249,7 +41626,7 @@ var init_lesson = __esm({
|
|
|
41249
41626
|
|
|
41250
41627
|
// src/commands/hint.ts
|
|
41251
41628
|
import { Command as Command12 } from "commander";
|
|
41252
|
-
var
|
|
41629
|
+
var logger13, hintCommand;
|
|
41253
41630
|
var init_hint = __esm({
|
|
41254
41631
|
"src/commands/hint.ts"() {
|
|
41255
41632
|
"use strict";
|
|
@@ -41258,7 +41635,7 @@ var init_hint = __esm({
|
|
|
41258
41635
|
init_http2();
|
|
41259
41636
|
init_session();
|
|
41260
41637
|
init_formatter();
|
|
41261
|
-
|
|
41638
|
+
logger13 = createLogger("cli:hint");
|
|
41262
41639
|
hintCommand = new Command12("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41263
41640
|
try {
|
|
41264
41641
|
const session = await requireSession();
|
|
@@ -41266,7 +41643,7 @@ var init_hint = __esm({
|
|
|
41266
41643
|
const driftWarning = await checkCourseDrift();
|
|
41267
41644
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41268
41645
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41269
|
-
const deps = { data, logger:
|
|
41646
|
+
const deps = { data, logger: logger13 };
|
|
41270
41647
|
const hint = await getHint(
|
|
41271
41648
|
{ userId: session.userId, enrollmentId: activeCourse.enrollmentId },
|
|
41272
41649
|
deps
|
|
@@ -41278,6 +41655,7 @@ var init_hint = __esm({
|
|
|
41278
41655
|
}
|
|
41279
41656
|
} catch (err) {
|
|
41280
41657
|
const msg = err instanceof Error ? err.message : String(err);
|
|
41658
|
+
if (opts.json) jsonError(msg);
|
|
41281
41659
|
error(msg);
|
|
41282
41660
|
}
|
|
41283
41661
|
});
|
|
@@ -41311,11 +41689,253 @@ var init_exercises = __esm({
|
|
|
41311
41689
|
}
|
|
41312
41690
|
});
|
|
41313
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
|
+
|
|
41314
41934
|
// src/commands/validate.ts
|
|
41315
|
-
import
|
|
41316
|
-
import
|
|
41935
|
+
import fs9 from "node:fs";
|
|
41936
|
+
import path10 from "node:path";
|
|
41317
41937
|
import { Command as Command13 } from "commander";
|
|
41318
|
-
var
|
|
41938
|
+
var logger14, validateCommand;
|
|
41319
41939
|
var init_validate = __esm({
|
|
41320
41940
|
"src/commands/validate.ts"() {
|
|
41321
41941
|
"use strict";
|
|
@@ -41325,7 +41945,7 @@ var init_validate = __esm({
|
|
|
41325
41945
|
init_session();
|
|
41326
41946
|
init_formatter();
|
|
41327
41947
|
init_init_template();
|
|
41328
|
-
|
|
41948
|
+
logger14 = createLogger("cli:validate");
|
|
41329
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) => {
|
|
41330
41950
|
try {
|
|
41331
41951
|
const session = await requireSession();
|
|
@@ -41334,21 +41954,26 @@ var init_validate = __esm({
|
|
|
41334
41954
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41335
41955
|
const lessonId = activeCourse.currentLessonId;
|
|
41336
41956
|
if (!lessonId) {
|
|
41957
|
+
if (opts.json) jsonError("no_active_lesson", { message: "Nenhuma li\xE7\xE3o ativa encontrada" });
|
|
41337
41958
|
error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
|
|
41338
41959
|
}
|
|
41339
41960
|
let solution;
|
|
41340
41961
|
if (opts.stdin) {
|
|
41341
|
-
solution =
|
|
41962
|
+
solution = fs9.readFileSync("/dev/stdin", "utf-8");
|
|
41342
41963
|
} else if (file2) {
|
|
41343
|
-
if (!
|
|
41964
|
+
if (!fs9.existsSync(file2)) {
|
|
41965
|
+
if (opts.json)
|
|
41966
|
+
jsonError("file_not_found", { message: `Arquivo n\xE3o encontrado: ${file2}` });
|
|
41344
41967
|
error(`Arquivo n\xE3o encontrado: ${file2}`);
|
|
41345
41968
|
}
|
|
41346
|
-
solution =
|
|
41969
|
+
solution = fs9.readFileSync(file2, "utf-8");
|
|
41347
41970
|
} else {
|
|
41971
|
+
if (opts.json)
|
|
41972
|
+
jsonError("no_solution_provided", { message: "Forne\xE7a um arquivo ou use --stdin" });
|
|
41348
41973
|
error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
|
|
41349
41974
|
}
|
|
41350
41975
|
if (file2 && activeCourse.courseTags?.length) {
|
|
41351
|
-
const ext =
|
|
41976
|
+
const ext = path10.extname(file2).toLowerCase();
|
|
41352
41977
|
const LANG_EXTENSIONS = {
|
|
41353
41978
|
".html": ["html", "html5"],
|
|
41354
41979
|
".css": ["css"],
|
|
@@ -41384,7 +42009,7 @@ var init_validate = __esm({
|
|
|
41384
42009
|
}
|
|
41385
42010
|
}
|
|
41386
42011
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41387
|
-
const deps = { data, logger:
|
|
42012
|
+
const deps = { data, logger: logger14 };
|
|
41388
42013
|
const result = await validateSolution(
|
|
41389
42014
|
{
|
|
41390
42015
|
lessonId,
|
|
@@ -41402,6 +42027,7 @@ var init_validate = __esm({
|
|
|
41402
42027
|
process.exit(result.passed ? 0 : 1);
|
|
41403
42028
|
} catch (err) {
|
|
41404
42029
|
const msg = err instanceof Error ? err.message : String(err);
|
|
42030
|
+
if (opts.json) jsonError(msg);
|
|
41405
42031
|
error(msg);
|
|
41406
42032
|
}
|
|
41407
42033
|
});
|
|
@@ -41415,10 +42041,12 @@ var init_menu = __esm({
|
|
|
41415
42041
|
"src/commands/menu.ts"() {
|
|
41416
42042
|
"use strict";
|
|
41417
42043
|
init_session();
|
|
42044
|
+
init_workspace_state();
|
|
41418
42045
|
init_formatter();
|
|
41419
42046
|
menuCommand = new Command14("menu").description("Show available commands and current study context").action(async () => {
|
|
41420
42047
|
const session = await getSession();
|
|
41421
|
-
const
|
|
42048
|
+
const wsResult = session ? await findWorkspaceState() : null;
|
|
42049
|
+
const activeCourse = wsResult?.state ?? null;
|
|
41422
42050
|
const lines = [
|
|
41423
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",
|
|
41424
42052
|
"\u2502 ToStudy CLI \u2014 Menu \u2502",
|
|
@@ -41473,9 +42101,9 @@ var init_menu = __esm({
|
|
|
41473
42101
|
});
|
|
41474
42102
|
|
|
41475
42103
|
// src/onboarding/learner-context.ts
|
|
41476
|
-
import
|
|
42104
|
+
import readline2 from "node:readline/promises";
|
|
41477
42105
|
import { stdin as input, stdout as output2 } from "node:process";
|
|
41478
|
-
async function
|
|
42106
|
+
async function askNonEmpty2(question, deps, defaultValue) {
|
|
41479
42107
|
while (true) {
|
|
41480
42108
|
const answer = (await deps.ask(question)).trim();
|
|
41481
42109
|
if (answer.length > 0) return answer;
|
|
@@ -41491,7 +42119,7 @@ async function askChoice(question, choices, deps, defaultChoice) {
|
|
|
41491
42119
|
}
|
|
41492
42120
|
}
|
|
41493
42121
|
function createPromptDeps() {
|
|
41494
|
-
const rl =
|
|
42122
|
+
const rl = readline2.createInterface({ input, output: output2 });
|
|
41495
42123
|
return {
|
|
41496
42124
|
ask: (question) => rl.question(question),
|
|
41497
42125
|
write: (content) => output2.write(content),
|
|
@@ -41524,32 +42152,32 @@ async function collectLearnerContextProfileWithDeps(input2, deps) {
|
|
|
41524
42152
|
"keep"
|
|
41525
42153
|
) : "edit";
|
|
41526
42154
|
const baseProfile = existingProfile;
|
|
41527
|
-
const segment = action === "keep" && baseProfile ? baseProfile.segment : await
|
|
42155
|
+
const segment = action === "keep" && baseProfile ? baseProfile.segment : await askNonEmpty2(
|
|
41528
42156
|
baseProfile ? `Segmento ou nicho do aluno [${baseProfile.segment}]: ` : "Segmento ou nicho do aluno: ",
|
|
41529
42157
|
deps,
|
|
41530
42158
|
baseProfile?.segment
|
|
41531
42159
|
);
|
|
41532
|
-
const company = action === "keep" && baseProfile ? baseProfile.company : await
|
|
42160
|
+
const company = action === "keep" && baseProfile ? baseProfile.company : await askNonEmpty2(
|
|
41533
42161
|
baseProfile ? `Empresa ou tipo de negocio [${baseProfile.company}]: ` : "Empresa ou tipo de negocio: ",
|
|
41534
42162
|
deps,
|
|
41535
42163
|
baseProfile?.company
|
|
41536
42164
|
);
|
|
41537
|
-
const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await
|
|
42165
|
+
const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await askNonEmpty2(
|
|
41538
42166
|
baseProfile ? `Produtos ou servicos principais [${baseProfile.productsOrServices}]: ` : "Produtos ou servicos principais: ",
|
|
41539
42167
|
deps,
|
|
41540
42168
|
baseProfile?.productsOrServices
|
|
41541
42169
|
);
|
|
41542
|
-
const region = action === "keep" && baseProfile ? baseProfile.region : await
|
|
42170
|
+
const region = action === "keep" && baseProfile ? baseProfile.region : await askNonEmpty2(
|
|
41543
42171
|
baseProfile ? `Regiao de atuacao [${baseProfile.region}]: ` : "Regiao de atuacao: ",
|
|
41544
42172
|
deps,
|
|
41545
42173
|
baseProfile?.region
|
|
41546
42174
|
);
|
|
41547
|
-
const team = action === "keep" && baseProfile ? baseProfile.team : await
|
|
42175
|
+
const team = action === "keep" && baseProfile ? baseProfile.team : await askNonEmpty2(
|
|
41548
42176
|
baseProfile ? `Equipe envolvida neste contexto [${baseProfile.team}]: ` : "Equipe envolvida neste contexto: ",
|
|
41549
42177
|
deps,
|
|
41550
42178
|
baseProfile?.team
|
|
41551
42179
|
);
|
|
41552
|
-
const goal = action === "keep" && baseProfile ? baseProfile.goal : await
|
|
42180
|
+
const goal = action === "keep" && baseProfile ? baseProfile.goal : await askNonEmpty2(
|
|
41553
42181
|
baseProfile ? `Objetivo principal com este curso [${baseProfile.goal}]: ` : "Objetivo principal com este curso: ",
|
|
41554
42182
|
deps,
|
|
41555
42183
|
baseProfile?.goal
|
|
@@ -41598,7 +42226,7 @@ function isCompleteProfile(flags) {
|
|
|
41598
42226
|
flags.segment && flags.company && flags.products && flags.region && flags.team && flags.goal && flags.level
|
|
41599
42227
|
);
|
|
41600
42228
|
}
|
|
41601
|
-
async function runInit(deps =
|
|
42229
|
+
async function runInit(deps = defaultDeps4, flags = {}) {
|
|
41602
42230
|
const session = await deps.getSession();
|
|
41603
42231
|
if (!session) {
|
|
41604
42232
|
deps.output("Nao autenticado. Rode `tostudy login` para comecar.", { json: false });
|
|
@@ -41606,7 +42234,8 @@ async function runInit(deps = defaultDeps3, flags = {}) {
|
|
|
41606
42234
|
}
|
|
41607
42235
|
const data = deps.createHttpProvider(session.apiUrl, session.token);
|
|
41608
42236
|
const apiDeps = { data, logger: deps.logger };
|
|
41609
|
-
const
|
|
42237
|
+
const wsResult = await deps.findWorkspaceState();
|
|
42238
|
+
const activeCourse = wsResult?.state ?? null;
|
|
41610
42239
|
if (!activeCourse) {
|
|
41611
42240
|
try {
|
|
41612
42241
|
const courses3 = await deps.listCourses({ userId: session.userId }, apiDeps);
|
|
@@ -41713,22 +42342,20 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
|
|
|
41713
42342
|
});
|
|
41714
42343
|
await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
|
|
41715
42344
|
try {
|
|
41716
|
-
|
|
41717
|
-
{
|
|
41718
|
-
|
|
41719
|
-
|
|
41720
|
-
progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0,
|
|
41721
|
-
moduleCount: progressData?.currentModule?.totalModules ?? 0,
|
|
41722
|
-
lessonCount: progressData?.currentLesson?.totalLessons ?? 0,
|
|
41723
|
-
currentModuleTitle: progressData?.currentModule?.title,
|
|
41724
|
-
currentLessonTitle: progressData?.currentLesson?.title,
|
|
41725
|
-
courseDescription: matchedCourse.description ?? void 0
|
|
41726
|
-
},
|
|
41727
|
-
learnerProfile
|
|
42345
|
+
const pipelineResult = await deps.resolveAndGenerate(
|
|
42346
|
+
{ courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
|
|
42347
|
+
{ forceRefresh: false, collectT1Interactive: true, collectT2Interactive: false },
|
|
42348
|
+
{ cwd: process.cwd(), deps: deps.buildPipelineDeps() }
|
|
41728
42349
|
);
|
|
41729
|
-
deps.logger.info("Instruction files
|
|
42350
|
+
deps.logger.info("Instruction files regenerated via pipeline", {
|
|
42351
|
+
wroteFiles: pipelineResult.wroteFiles,
|
|
42352
|
+
usedT0: pipelineResult.usedT0,
|
|
42353
|
+
usedT1: pipelineResult.usedT1,
|
|
42354
|
+
usedT2: pipelineResult.usedT2,
|
|
42355
|
+
bootstrapped: pipelineResult.bootstrapped
|
|
42356
|
+
});
|
|
41730
42357
|
} catch (err) {
|
|
41731
|
-
deps.logger.warn("Failed to
|
|
42358
|
+
deps.logger.warn("Failed to run instruction pipeline", {
|
|
41732
42359
|
error: err instanceof Error ? err.message : String(err)
|
|
41733
42360
|
});
|
|
41734
42361
|
}
|
|
@@ -41744,7 +42371,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
|
|
|
41744
42371
|
deps.output(artifacts.learnerBrief, { json: false });
|
|
41745
42372
|
}
|
|
41746
42373
|
}
|
|
41747
|
-
var
|
|
42374
|
+
var logger15, defaultDeps4, initCommand;
|
|
41748
42375
|
var init_init = __esm({
|
|
41749
42376
|
"src/commands/init.ts"() {
|
|
41750
42377
|
"use strict";
|
|
@@ -41752,15 +42379,17 @@ var init_init = __esm({
|
|
|
41752
42379
|
init_courses();
|
|
41753
42380
|
init_http2();
|
|
41754
42381
|
init_session();
|
|
42382
|
+
init_workspace_state();
|
|
41755
42383
|
init_formatter();
|
|
41756
42384
|
init_init_template();
|
|
41757
42385
|
init_learner_context();
|
|
41758
|
-
|
|
41759
|
-
|
|
41760
|
-
|
|
41761
|
-
|
|
42386
|
+
init_api3();
|
|
42387
|
+
init_instruction_pipeline();
|
|
42388
|
+
init_pipeline_deps();
|
|
42389
|
+
logger15 = createLogger("cli:init");
|
|
42390
|
+
defaultDeps4 = {
|
|
41762
42391
|
getSession,
|
|
41763
|
-
|
|
42392
|
+
findWorkspaceState,
|
|
41764
42393
|
listCourses,
|
|
41765
42394
|
getProgress,
|
|
41766
42395
|
getRemoteEnrollmentOnboarding,
|
|
@@ -41771,24 +42400,26 @@ var init_init = __esm({
|
|
|
41771
42400
|
saveCourseLearnerProfile,
|
|
41772
42401
|
buildInitArtifacts,
|
|
41773
42402
|
output,
|
|
41774
|
-
logger:
|
|
41775
|
-
createHttpProvider
|
|
42403
|
+
logger: logger15,
|
|
42404
|
+
createHttpProvider,
|
|
42405
|
+
resolveAndGenerate,
|
|
42406
|
+
buildPipelineDeps
|
|
41776
42407
|
};
|
|
41777
42408
|
initCommand = new Command15("init").description("Generate tutor instructions and learner brief for the active course").option("--segment <segment>", "Learner segment/niche").option("--company <company>", "Company or business type").option("--products <products>", "Main products or services").option("--region <region>", "Operating region").option("--team <team>", "Team involved").option("--goal <goal>", "Primary learning goal").option("--level <level>", "Learner level: beginner, intermediate, advanced").option("--adapt-context", "Adapt examples to learner's real context").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41778
|
-
await runInit(
|
|
42409
|
+
await runInit(defaultDeps4, opts);
|
|
41779
42410
|
});
|
|
41780
42411
|
}
|
|
41781
42412
|
});
|
|
41782
42413
|
|
|
41783
42414
|
// ../../packages/tostudy-core/src/workspace/setup-workspace.ts
|
|
41784
|
-
import
|
|
41785
|
-
import
|
|
42415
|
+
import fs10 from "node:fs/promises";
|
|
42416
|
+
import path11 from "node:path";
|
|
41786
42417
|
async function setupWorkspace(input2) {
|
|
41787
|
-
const workspacePath =
|
|
42418
|
+
const workspacePath = path11.join(input2.basePath, input2.courseSlug);
|
|
41788
42419
|
for (const dir of WORKSPACE_DIRS) {
|
|
41789
|
-
await
|
|
42420
|
+
await fs10.mkdir(path11.join(workspacePath, dir), { recursive: true });
|
|
41790
42421
|
}
|
|
41791
|
-
const configPath =
|
|
42422
|
+
const configPath = path11.join(workspacePath, ".ana-config.json");
|
|
41792
42423
|
const config2 = {
|
|
41793
42424
|
courseId: input2.courseId,
|
|
41794
42425
|
courseSlug: input2.courseSlug,
|
|
@@ -41798,7 +42429,7 @@ async function setupWorkspace(input2) {
|
|
|
41798
42429
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41799
42430
|
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41800
42431
|
};
|
|
41801
|
-
await
|
|
42432
|
+
await fs10.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
41802
42433
|
const readme = [
|
|
41803
42434
|
`# ${input2.courseName}`,
|
|
41804
42435
|
"",
|
|
@@ -41823,7 +42454,7 @@ async function setupWorkspace(input2) {
|
|
|
41823
42454
|
"tostudy vault sync # Sincronizar progresso",
|
|
41824
42455
|
"```"
|
|
41825
42456
|
].join("\n");
|
|
41826
|
-
await
|
|
42457
|
+
await fs10.writeFile(path11.join(workspacePath, "README.md"), readme, "utf-8");
|
|
41827
42458
|
return { workspacePath, directories: WORKSPACE_DIRS, configPath };
|
|
41828
42459
|
}
|
|
41829
42460
|
var WORKSPACE_DIRS;
|
|
@@ -41924,8 +42555,8 @@ var init_templates = __esm({
|
|
|
41924
42555
|
});
|
|
41925
42556
|
|
|
41926
42557
|
// ../../packages/tostudy-core/src/workspace/extract-exercise.ts
|
|
41927
|
-
import
|
|
41928
|
-
import
|
|
42558
|
+
import fs11 from "node:fs/promises";
|
|
42559
|
+
import path12 from "node:path";
|
|
41929
42560
|
function padOrder(n) {
|
|
41930
42561
|
return String(n).padStart(2, "0");
|
|
41931
42562
|
}
|
|
@@ -41949,16 +42580,16 @@ async function extractExercise(input2) {
|
|
|
41949
42580
|
const { lessonData, exerciseTier, workspacePath } = input2;
|
|
41950
42581
|
const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
|
|
41951
42582
|
const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
|
|
41952
|
-
const exercisePath =
|
|
41953
|
-
await
|
|
42583
|
+
const exercisePath = path12.join(workspacePath, "exercises", moduleDir, lessonDir);
|
|
42584
|
+
await fs11.mkdir(exercisePath, { recursive: true });
|
|
41954
42585
|
const extractedFiles = [];
|
|
41955
42586
|
let hasStarterCode = false;
|
|
41956
42587
|
if (lessonData.sandpackConfig?.files) {
|
|
41957
42588
|
for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
|
|
41958
42589
|
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
41959
|
-
const fullPath =
|
|
41960
|
-
await
|
|
41961
|
-
await
|
|
42590
|
+
const fullPath = path12.join(exercisePath, cleanPath);
|
|
42591
|
+
await fs11.mkdir(path12.dirname(fullPath), { recursive: true });
|
|
42592
|
+
await fs11.writeFile(fullPath, fileData.code, "utf-8");
|
|
41962
42593
|
extractedFiles.push(cleanPath);
|
|
41963
42594
|
hasStarterCode = true;
|
|
41964
42595
|
}
|
|
@@ -41966,13 +42597,13 @@ async function extractExercise(input2) {
|
|
|
41966
42597
|
const tierData = getTierData(lessonData.structuredData, exerciseTier);
|
|
41967
42598
|
const tierCode = tierData?.code;
|
|
41968
42599
|
if (tierCode) {
|
|
41969
|
-
await
|
|
42600
|
+
await fs11.writeFile(path12.join(exercisePath, "exercise.js"), tierCode, "utf-8");
|
|
41970
42601
|
extractedFiles.push("exercise.js");
|
|
41971
42602
|
hasStarterCode = true;
|
|
41972
42603
|
} else {
|
|
41973
42604
|
const starter = getStarterCode(lessonData.structuredData);
|
|
41974
42605
|
if (starter) {
|
|
41975
|
-
await
|
|
42606
|
+
await fs11.writeFile(path12.join(exercisePath, "exercise.js"), starter, "utf-8");
|
|
41976
42607
|
extractedFiles.push("exercise.js");
|
|
41977
42608
|
hasStarterCode = true;
|
|
41978
42609
|
}
|
|
@@ -41990,8 +42621,8 @@ async function extractExercise(input2) {
|
|
|
41990
42621
|
...exerciseDeps
|
|
41991
42622
|
}
|
|
41992
42623
|
};
|
|
41993
|
-
await
|
|
41994
|
-
|
|
42624
|
+
await fs11.writeFile(
|
|
42625
|
+
path12.join(exercisePath, "package.json"),
|
|
41995
42626
|
JSON.stringify(pkgJson, null, 2),
|
|
41996
42627
|
"utf-8"
|
|
41997
42628
|
);
|
|
@@ -42003,20 +42634,20 @@ async function extractExercise(input2) {
|
|
|
42003
42634
|
);
|
|
42004
42635
|
for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
|
|
42005
42636
|
if (!sandpackFileNames.has(configFile)) {
|
|
42006
|
-
await
|
|
42637
|
+
await fs11.writeFile(path12.join(exercisePath, configFile), configContent, "utf-8");
|
|
42007
42638
|
extractedFiles.push(configFile);
|
|
42008
42639
|
}
|
|
42009
42640
|
}
|
|
42010
42641
|
const setupSh = `#!/bin/sh
|
|
42011
42642
|
${scaffold.setupScript}
|
|
42012
42643
|
`;
|
|
42013
|
-
await
|
|
42644
|
+
await fs11.writeFile(path12.join(exercisePath, "setup.sh"), setupSh, "utf-8");
|
|
42014
42645
|
extractedFiles.push("setup.sh");
|
|
42015
42646
|
}
|
|
42016
42647
|
}
|
|
42017
42648
|
const readme = generateReadme(lessonData, exerciseTier);
|
|
42018
|
-
const readmePath =
|
|
42019
|
-
await
|
|
42649
|
+
const readmePath = path12.join(exercisePath, "README.md");
|
|
42650
|
+
await fs11.writeFile(readmePath, readme, "utf-8");
|
|
42020
42651
|
extractedFiles.push("README.md");
|
|
42021
42652
|
return {
|
|
42022
42653
|
exercisePath,
|
|
@@ -42088,10 +42719,10 @@ var init_workspace = __esm({
|
|
|
42088
42719
|
|
|
42089
42720
|
// src/commands/workspace.ts
|
|
42090
42721
|
import { Command as Command16 } from "commander";
|
|
42091
|
-
import
|
|
42092
|
-
import
|
|
42093
|
-
import
|
|
42094
|
-
var
|
|
42722
|
+
import path13 from "node:path";
|
|
42723
|
+
import os12 from "node:os";
|
|
42724
|
+
import fs12 from "node:fs/promises";
|
|
42725
|
+
var logger16, workspaceCommand;
|
|
42095
42726
|
var init_workspace2 = __esm({
|
|
42096
42727
|
"src/commands/workspace.ts"() {
|
|
42097
42728
|
"use strict";
|
|
@@ -42099,7 +42730,7 @@ var init_workspace2 = __esm({
|
|
|
42099
42730
|
init_workspace();
|
|
42100
42731
|
init_session();
|
|
42101
42732
|
init_resolve();
|
|
42102
|
-
|
|
42733
|
+
logger16 = createLogger("cli:workspace");
|
|
42103
42734
|
workspaceCommand = new Command16("workspace").description(
|
|
42104
42735
|
"Gerenciar workspace de estudo local"
|
|
42105
42736
|
);
|
|
@@ -42112,14 +42743,14 @@ var init_workspace2 = __esm({
|
|
|
42112
42743
|
let directories;
|
|
42113
42744
|
if (!opts.path && cwdIsWorkspace) {
|
|
42114
42745
|
const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
|
|
42115
|
-
workspacePath = resolvedCwd ??
|
|
42116
|
-
await
|
|
42746
|
+
workspacePath = resolvedCwd ?? path13.join(process.cwd(), ".tostudy");
|
|
42747
|
+
await fs12.mkdir(workspacePath, { recursive: true });
|
|
42117
42748
|
directories = ["exercises", "generated", "notes", "diagrams"];
|
|
42118
42749
|
for (const dir of directories) {
|
|
42119
|
-
await
|
|
42750
|
+
await fs12.mkdir(path13.join(workspacePath, dir), { recursive: true });
|
|
42120
42751
|
}
|
|
42121
|
-
const configPath =
|
|
42122
|
-
await
|
|
42752
|
+
const configPath = path13.join(workspacePath, ".ana-config.json");
|
|
42753
|
+
await fs12.writeFile(
|
|
42123
42754
|
configPath,
|
|
42124
42755
|
JSON.stringify(
|
|
42125
42756
|
{
|
|
@@ -42137,7 +42768,7 @@ var init_workspace2 = __esm({
|
|
|
42137
42768
|
"utf-8"
|
|
42138
42769
|
);
|
|
42139
42770
|
} else {
|
|
42140
|
-
const basePath = opts.path ??
|
|
42771
|
+
const basePath = opts.path ?? path13.join(os12.homedir(), "study");
|
|
42141
42772
|
const result2 = await setupWorkspace({
|
|
42142
42773
|
courseId: activeCourse.courseId,
|
|
42143
42774
|
courseSlug: courseSlug(activeCourse.courseTitle),
|
|
@@ -42165,13 +42796,13 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42165
42796
|
);
|
|
42166
42797
|
}
|
|
42167
42798
|
} catch (err) {
|
|
42168
|
-
|
|
42799
|
+
logger16.error("workspace setup failed", { error: err });
|
|
42169
42800
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42170
42801
|
`);
|
|
42171
42802
|
process.exit(1);
|
|
42172
42803
|
}
|
|
42173
42804
|
});
|
|
42174
|
-
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) => {
|
|
42175
42806
|
try {
|
|
42176
42807
|
const activeCourse = await requireActiveCourse();
|
|
42177
42808
|
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
@@ -42190,40 +42821,40 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42190
42821
|
const workspacePath = ws.workspacePath;
|
|
42191
42822
|
let configData = null;
|
|
42192
42823
|
try {
|
|
42193
|
-
const raw = await
|
|
42824
|
+
const raw = await fs12.readFile(path13.join(workspacePath, ".ana-config.json"), "utf-8");
|
|
42194
42825
|
configData = JSON.parse(raw);
|
|
42195
42826
|
} catch {
|
|
42196
42827
|
configData = null;
|
|
42197
42828
|
}
|
|
42198
|
-
const exercisesDir =
|
|
42829
|
+
const exercisesDir = path13.join(workspacePath, "exercises");
|
|
42199
42830
|
let exerciseCount = 0;
|
|
42200
42831
|
try {
|
|
42201
|
-
const moduleDirs = await
|
|
42832
|
+
const moduleDirs = await fs12.readdir(exercisesDir);
|
|
42202
42833
|
for (const modDir of moduleDirs) {
|
|
42203
|
-
const modPath =
|
|
42204
|
-
const stat = await
|
|
42834
|
+
const modPath = path13.join(exercisesDir, modDir);
|
|
42835
|
+
const stat = await fs12.stat(modPath);
|
|
42205
42836
|
if (stat.isDirectory()) {
|
|
42206
|
-
const lessonDirs = await
|
|
42837
|
+
const lessonDirs = await fs12.readdir(modPath);
|
|
42207
42838
|
for (const lessonDir of lessonDirs) {
|
|
42208
|
-
const lessonPath =
|
|
42209
|
-
const lstat = await
|
|
42839
|
+
const lessonPath = path13.join(modPath, lessonDir);
|
|
42840
|
+
const lstat = await fs12.stat(lessonPath);
|
|
42210
42841
|
if (lstat.isDirectory()) exerciseCount++;
|
|
42211
42842
|
}
|
|
42212
42843
|
}
|
|
42213
42844
|
}
|
|
42214
42845
|
} catch {
|
|
42215
42846
|
}
|
|
42216
|
-
const generatedDir =
|
|
42847
|
+
const generatedDir = path13.join(workspacePath, "generated");
|
|
42217
42848
|
let artifactCount = 0;
|
|
42218
42849
|
try {
|
|
42219
|
-
const files = await
|
|
42850
|
+
const files = await fs12.readdir(generatedDir);
|
|
42220
42851
|
artifactCount = files.length;
|
|
42221
42852
|
} catch {
|
|
42222
42853
|
}
|
|
42223
|
-
const diagramsDir =
|
|
42854
|
+
const diagramsDir = path13.join(workspacePath, "diagrams");
|
|
42224
42855
|
let diagramCount = 0;
|
|
42225
42856
|
try {
|
|
42226
|
-
const files = await
|
|
42857
|
+
const files = await fs12.readdir(diagramsDir);
|
|
42227
42858
|
diagramCount = files.length;
|
|
42228
42859
|
} catch {
|
|
42229
42860
|
}
|
|
@@ -42272,10 +42903,10 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42272
42903
|
|
|
42273
42904
|
// src/commands/export.ts
|
|
42274
42905
|
import { Command as Command17 } from "commander";
|
|
42275
|
-
import
|
|
42276
|
-
import
|
|
42277
|
-
import
|
|
42278
|
-
var
|
|
42906
|
+
import path14 from "node:path";
|
|
42907
|
+
import os13 from "node:os";
|
|
42908
|
+
import fs13 from "node:fs/promises";
|
|
42909
|
+
var logger17, exportCommand;
|
|
42279
42910
|
var init_export = __esm({
|
|
42280
42911
|
"src/commands/export.ts"() {
|
|
42281
42912
|
"use strict";
|
|
@@ -42284,8 +42915,8 @@ var init_export = __esm({
|
|
|
42284
42915
|
init_http2();
|
|
42285
42916
|
init_session();
|
|
42286
42917
|
init_resolve();
|
|
42287
|
-
|
|
42288
|
-
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",
|
|
42918
|
+
logger17 = createLogger("cli:export");
|
|
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) => {
|
|
42289
42920
|
try {
|
|
42290
42921
|
const session = await requireSession();
|
|
42291
42922
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42303,21 +42934,21 @@ var init_export = __esm({
|
|
|
42303
42934
|
opts.path
|
|
42304
42935
|
);
|
|
42305
42936
|
if (ws.found && ws.source === "cwd" && ws.workspacePath) {
|
|
42306
|
-
const configPath =
|
|
42937
|
+
const configPath = path14.join(ws.workspacePath, ".ana-config.json");
|
|
42307
42938
|
let hasConfig = false;
|
|
42308
42939
|
try {
|
|
42309
|
-
await
|
|
42940
|
+
await fs13.access(configPath);
|
|
42310
42941
|
hasConfig = true;
|
|
42311
42942
|
} catch {
|
|
42312
42943
|
}
|
|
42313
42944
|
if (!hasConfig) {
|
|
42314
42945
|
const slug = courseSlug(activeCourse.courseTitle);
|
|
42315
|
-
|
|
42316
|
-
await
|
|
42946
|
+
logger17.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
|
|
42947
|
+
await fs13.mkdir(ws.workspacePath, { recursive: true });
|
|
42317
42948
|
for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
|
|
42318
|
-
await
|
|
42949
|
+
await fs13.mkdir(path14.join(ws.workspacePath, dir), { recursive: true });
|
|
42319
42950
|
}
|
|
42320
|
-
await
|
|
42951
|
+
await fs13.writeFile(
|
|
42321
42952
|
configPath,
|
|
42322
42953
|
JSON.stringify(
|
|
42323
42954
|
{
|
|
@@ -42335,7 +42966,7 @@ var init_export = __esm({
|
|
|
42335
42966
|
"utf-8"
|
|
42336
42967
|
);
|
|
42337
42968
|
await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
|
|
42338
|
-
const isNamespaced = ws.workspacePath ===
|
|
42969
|
+
const isNamespaced = ws.workspacePath === path14.join(process.cwd(), ".tostudy");
|
|
42339
42970
|
process.stderr.write(
|
|
42340
42971
|
isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
|
|
42341
42972
|
` : `\u2728 Workspace inicializado nesta pasta.
|
|
@@ -42376,7 +43007,7 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
|
|
|
42376
43007
|
);
|
|
42377
43008
|
}
|
|
42378
43009
|
} catch (err) {
|
|
42379
|
-
|
|
43010
|
+
logger17.error("export failed", { error: err });
|
|
42380
43011
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42381
43012
|
`);
|
|
42382
43013
|
process.exit(1);
|
|
@@ -42388,17 +43019,17 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
|
|
|
42388
43019
|
// src/commands/open.ts
|
|
42389
43020
|
import { Command as Command18 } from "commander";
|
|
42390
43021
|
import { execFile as execFile3 } from "node:child_process";
|
|
42391
|
-
import
|
|
42392
|
-
import
|
|
42393
|
-
var
|
|
43022
|
+
import path15 from "node:path";
|
|
43023
|
+
import os14 from "node:os";
|
|
43024
|
+
var logger18, openCommand;
|
|
42394
43025
|
var init_open = __esm({
|
|
42395
43026
|
"src/commands/open.ts"() {
|
|
42396
43027
|
"use strict";
|
|
42397
43028
|
init_src();
|
|
42398
43029
|
init_session();
|
|
42399
43030
|
init_resolve();
|
|
42400
|
-
|
|
42401
|
-
openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
43031
|
+
logger18 = createLogger("cli:open");
|
|
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) => {
|
|
42402
43033
|
try {
|
|
42403
43034
|
const activeCourse = await requireActiveCourse();
|
|
42404
43035
|
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
@@ -42417,7 +43048,7 @@ var init_open = __esm({
|
|
|
42417
43048
|
const editor = process.env["EDITOR"] ?? "code";
|
|
42418
43049
|
execFile3(editor, [ws.workspacePath], (err) => {
|
|
42419
43050
|
if (err) {
|
|
42420
|
-
|
|
43051
|
+
logger18.error("open failed", { editor, workspacePath: ws.workspacePath });
|
|
42421
43052
|
process.stderr.write(`\u274C Falha ao abrir: ${err.message}
|
|
42422
43053
|
`);
|
|
42423
43054
|
process.exit(1);
|
|
@@ -42426,7 +43057,7 @@ var init_open = __esm({
|
|
|
42426
43057
|
`);
|
|
42427
43058
|
});
|
|
42428
43059
|
} catch (err) {
|
|
42429
|
-
|
|
43060
|
+
logger18.error("open command failed", { error: err });
|
|
42430
43061
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42431
43062
|
`);
|
|
42432
43063
|
process.exit(1);
|
|
@@ -42453,24 +43084,24 @@ var init_types3 = __esm({
|
|
|
42453
43084
|
});
|
|
42454
43085
|
|
|
42455
43086
|
// ../../packages/tostudy-core/src/vault/write-vault.ts
|
|
42456
|
-
import
|
|
42457
|
-
import
|
|
43087
|
+
import fs14 from "node:fs/promises";
|
|
43088
|
+
import path16 from "node:path";
|
|
42458
43089
|
async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
|
|
42459
43090
|
for (const file2 of files) {
|
|
42460
|
-
const fullPath =
|
|
42461
|
-
await
|
|
42462
|
-
await
|
|
43091
|
+
const fullPath = path16.join(outputPath, file2.relativePath);
|
|
43092
|
+
await fs14.mkdir(path16.dirname(fullPath), { recursive: true });
|
|
43093
|
+
await fs14.writeFile(fullPath, file2.content, "utf-8");
|
|
42463
43094
|
}
|
|
42464
|
-
const vaultPath =
|
|
43095
|
+
const vaultPath = path16.join(outputPath, courseSlug2);
|
|
42465
43096
|
const marker = {
|
|
42466
43097
|
courseId,
|
|
42467
43098
|
courseSlug: courseSlug2,
|
|
42468
43099
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42469
43100
|
version: VAULT_MARKER_VERSION
|
|
42470
43101
|
};
|
|
42471
|
-
await
|
|
42472
|
-
await
|
|
42473
|
-
|
|
43102
|
+
await fs14.mkdir(vaultPath, { recursive: true });
|
|
43103
|
+
await fs14.writeFile(
|
|
43104
|
+
path16.join(vaultPath, VAULT_MARKER_FILENAME),
|
|
42474
43105
|
JSON.stringify(marker, null, 2),
|
|
42475
43106
|
"utf-8"
|
|
42476
43107
|
);
|
|
@@ -42495,10 +43126,10 @@ var init_vault = __esm({
|
|
|
42495
43126
|
|
|
42496
43127
|
// src/commands/vault.ts
|
|
42497
43128
|
import { Command as Command19 } from "commander";
|
|
42498
|
-
import
|
|
42499
|
-
import
|
|
42500
|
-
import
|
|
42501
|
-
var
|
|
43129
|
+
import path17 from "node:path";
|
|
43130
|
+
import os15 from "node:os";
|
|
43131
|
+
import fs15 from "node:fs/promises";
|
|
43132
|
+
var logger19, vaultCommand;
|
|
42502
43133
|
var init_vault2 = __esm({
|
|
42503
43134
|
"src/commands/vault.ts"() {
|
|
42504
43135
|
"use strict";
|
|
@@ -42508,9 +43139,9 @@ var init_vault2 = __esm({
|
|
|
42508
43139
|
init_http2();
|
|
42509
43140
|
init_session();
|
|
42510
43141
|
init_resolve();
|
|
42511
|
-
|
|
43142
|
+
logger19 = createLogger("cli:vault");
|
|
42512
43143
|
vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
|
|
42513
|
-
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) => {
|
|
42514
43145
|
try {
|
|
42515
43146
|
const session = await requireSession();
|
|
42516
43147
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42555,7 +43186,7 @@ var init_vault2 = __esm({
|
|
|
42555
43186
|
activeCourse.courseId,
|
|
42556
43187
|
slug
|
|
42557
43188
|
);
|
|
42558
|
-
|
|
43189
|
+
logger19.info("Vault generated", {
|
|
42559
43190
|
courseId: activeCourse.courseId,
|
|
42560
43191
|
vaultPath: result.vaultPath,
|
|
42561
43192
|
filesWritten: result.filesWritten
|
|
@@ -42588,13 +43219,13 @@ Para visualizar:
|
|
|
42588
43219
|
);
|
|
42589
43220
|
}
|
|
42590
43221
|
} catch (err) {
|
|
42591
|
-
|
|
43222
|
+
logger19.error("vault init failed", { error: err });
|
|
42592
43223
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42593
43224
|
`);
|
|
42594
43225
|
process.exit(1);
|
|
42595
43226
|
}
|
|
42596
43227
|
});
|
|
42597
|
-
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) => {
|
|
42598
43229
|
try {
|
|
42599
43230
|
const session = await requireSession();
|
|
42600
43231
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42620,10 +43251,10 @@ Para visualizar:
|
|
|
42620
43251
|
process.exit(1);
|
|
42621
43252
|
}
|
|
42622
43253
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
42623
|
-
const deps = { data, logger:
|
|
43254
|
+
const deps = { data, logger: logger19 };
|
|
42624
43255
|
const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
42625
|
-
const markerPath =
|
|
42626
|
-
const markerRaw = await
|
|
43256
|
+
const markerPath = path17.join(vaultPath, ".ana-vault.json");
|
|
43257
|
+
const markerRaw = await fs15.readFile(markerPath, "utf-8");
|
|
42627
43258
|
const marker = JSON.parse(markerRaw);
|
|
42628
43259
|
marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42629
43260
|
marker.progress = {
|
|
@@ -42631,10 +43262,10 @@ Para visualizar:
|
|
|
42631
43262
|
currentModule: progress3.currentModule.title,
|
|
42632
43263
|
currentLesson: progress3.currentLesson.title
|
|
42633
43264
|
};
|
|
42634
|
-
await
|
|
42635
|
-
const courseIndexPath =
|
|
43265
|
+
await fs15.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
43266
|
+
const courseIndexPath = path17.join(vaultPath, slug, "index.md");
|
|
42636
43267
|
try {
|
|
42637
|
-
let indexContent = await
|
|
43268
|
+
let indexContent = await fs15.readFile(courseIndexPath, "utf-8");
|
|
42638
43269
|
indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
|
|
42639
43270
|
const titleEnd = indexContent.indexOf("\n");
|
|
42640
43271
|
if (titleEnd !== -1) {
|
|
@@ -42645,7 +43276,7 @@ Para visualizar:
|
|
|
42645
43276
|
`;
|
|
42646
43277
|
indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
|
|
42647
43278
|
}
|
|
42648
|
-
await
|
|
43279
|
+
await fs15.writeFile(courseIndexPath, indexContent, "utf-8");
|
|
42649
43280
|
} catch {
|
|
42650
43281
|
}
|
|
42651
43282
|
const syncedAt = marker.lastSyncedAt;
|
|
@@ -42674,7 +43305,7 @@ Para visualizar:
|
|
|
42674
43305
|
);
|
|
42675
43306
|
}
|
|
42676
43307
|
} catch (err) {
|
|
42677
|
-
|
|
43308
|
+
logger19.error("vault sync failed", { error: err });
|
|
42678
43309
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42679
43310
|
`);
|
|
42680
43311
|
process.exit(1);
|
|
@@ -42752,68 +43383,349 @@ var init_profile = __esm({
|
|
|
42752
43383
|
|
|
42753
43384
|
// src/commands/sync.ts
|
|
42754
43385
|
import { Command as Command21 } from "commander";
|
|
42755
|
-
var
|
|
43386
|
+
var logger20, syncCommand;
|
|
42756
43387
|
var init_sync = __esm({
|
|
42757
43388
|
"src/commands/sync.ts"() {
|
|
42758
43389
|
"use strict";
|
|
42759
43390
|
init_src();
|
|
42760
|
-
init_courses();
|
|
42761
|
-
init_http2();
|
|
42762
43391
|
init_session();
|
|
42763
43392
|
init_formatter();
|
|
42764
|
-
|
|
42765
|
-
|
|
42766
|
-
|
|
43393
|
+
init_instruction_pipeline();
|
|
43394
|
+
init_pipeline_deps();
|
|
43395
|
+
logger20 = createLogger("cli:sync");
|
|
42767
43396
|
syncCommand = new Command21("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").action(async (opts) => {
|
|
42768
43397
|
try {
|
|
42769
|
-
|
|
43398
|
+
await requireSession();
|
|
42770
43399
|
const activeCourse = await requireActiveCourse();
|
|
42771
|
-
const
|
|
42772
|
-
|
|
42773
|
-
|
|
42774
|
-
|
|
42775
|
-
getProgress({ enrollmentId: activeCourse.enrollmentId }, deps).catch(() => null)
|
|
42776
|
-
]);
|
|
42777
|
-
const matchedCourse = courses3.find((c) => c.courseId === activeCourse.courseId);
|
|
42778
|
-
if (!matchedCourse) {
|
|
42779
|
-
error("Curso ativo n\xE3o encontrado. Rode `tostudy courses` para verificar.");
|
|
42780
|
-
}
|
|
42781
|
-
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42782
|
-
const onboarding = await getCourseOnboardingStatus(activeCourse);
|
|
42783
|
-
const slug = generateInstructionFiles(
|
|
42784
|
-
{
|
|
42785
|
-
courseTitle: matchedCourse.title,
|
|
42786
|
-
courseId: activeCourse.courseId,
|
|
42787
|
-
progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0,
|
|
42788
|
-
moduleCount: progressData?.currentModule?.totalModules ?? 0,
|
|
42789
|
-
lessonCount: progressData?.currentLesson?.totalLessons ?? 0,
|
|
42790
|
-
currentModuleTitle: progressData?.currentModule?.title,
|
|
42791
|
-
currentLessonTitle: progressData?.currentLesson?.title,
|
|
42792
|
-
courseDescription: matchedCourse.description ?? void 0,
|
|
42793
|
-
workspaceReady: onboarding.workspaceReady,
|
|
42794
|
-
workspacePath: onboarding.workspacePath ?? void 0
|
|
42795
|
-
},
|
|
42796
|
-
onboardingState?.learnerProfile
|
|
43400
|
+
const pipelineResult = await resolveAndGenerate(
|
|
43401
|
+
{ courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
|
|
43402
|
+
{ forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
|
|
43403
|
+
{ cwd: process.cwd(), deps: buildPipelineDeps() }
|
|
42797
43404
|
);
|
|
42798
43405
|
if (opts.json) {
|
|
42799
43406
|
output(
|
|
42800
43407
|
{
|
|
42801
43408
|
status: "ok",
|
|
42802
43409
|
courseId: activeCourse.courseId,
|
|
42803
|
-
|
|
42804
|
-
|
|
43410
|
+
courseTitle: activeCourse.courseTitle,
|
|
43411
|
+
wroteFiles: pipelineResult.wroteFiles,
|
|
43412
|
+
markerPath: pipelineResult.markerPath,
|
|
43413
|
+
usedT0: pipelineResult.usedT0,
|
|
43414
|
+
usedT1: pipelineResult.usedT1,
|
|
43415
|
+
usedT2: pipelineResult.usedT2
|
|
42805
43416
|
},
|
|
42806
43417
|
{ json: true }
|
|
42807
43418
|
);
|
|
42808
43419
|
} else {
|
|
42809
|
-
|
|
42810
|
-
|
|
42811
|
-
|
|
42812
|
-
|
|
43420
|
+
output(
|
|
43421
|
+
[
|
|
43422
|
+
`\u2713 Instru\xE7\xF5es sincronizadas para "${activeCourse.courseTitle}"`,
|
|
43423
|
+
` - T0 (persona): ${pipelineResult.usedT0 ? "presente" : "ausente"}`,
|
|
43424
|
+
` - T1 (brief base): ${pipelineResult.usedT1 ? "presente" : "ausente"}`,
|
|
43425
|
+
` - T2 (curso): ${pipelineResult.usedT2 ? "presente" : "ausente"}`,
|
|
43426
|
+
` - Arquivos: ${pipelineResult.wroteFiles.join(", ")}`
|
|
43427
|
+
].join("\n"),
|
|
43428
|
+
{ json: false }
|
|
43429
|
+
);
|
|
43430
|
+
}
|
|
43431
|
+
} catch (err) {
|
|
43432
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43433
|
+
if (msg.includes("process.exit")) return;
|
|
43434
|
+
logger20.warn("sync failed", { error: msg });
|
|
43435
|
+
error(msg);
|
|
43436
|
+
}
|
|
43437
|
+
});
|
|
43438
|
+
}
|
|
43439
|
+
});
|
|
43440
|
+
|
|
43441
|
+
// src/commands/brief.ts
|
|
43442
|
+
import { Command as Command22 } from "commander";
|
|
43443
|
+
var logger21, briefCommand;
|
|
43444
|
+
var init_brief = __esm({
|
|
43445
|
+
"src/commands/brief.ts"() {
|
|
43446
|
+
"use strict";
|
|
43447
|
+
init_src();
|
|
43448
|
+
init_session();
|
|
43449
|
+
init_cache();
|
|
43450
|
+
init_api();
|
|
43451
|
+
init_formatter();
|
|
43452
|
+
logger21 = createLogger("cli:brief");
|
|
43453
|
+
briefCommand = new Command22("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
|
|
43454
|
+
try {
|
|
43455
|
+
const session = await requireSession();
|
|
43456
|
+
const cached2 = await readBriefCache();
|
|
43457
|
+
const remote = await fetchLearnerBrief({
|
|
43458
|
+
apiUrl: session.apiUrl,
|
|
43459
|
+
token: session.token
|
|
43460
|
+
});
|
|
43461
|
+
if (opts.json) {
|
|
43462
|
+
output(
|
|
43463
|
+
{
|
|
43464
|
+
cached: cached2?.brief ?? null,
|
|
43465
|
+
remote,
|
|
43466
|
+
cacheFetchedAt: cached2?.fetchedAt ?? null
|
|
43467
|
+
},
|
|
43468
|
+
{ json: true }
|
|
43469
|
+
);
|
|
43470
|
+
return;
|
|
43471
|
+
}
|
|
43472
|
+
if (!remote) {
|
|
43473
|
+
const lines2 = [
|
|
43474
|
+
"Voce ainda nao tem um brief base preenchido.",
|
|
43475
|
+
"Para criar: `tostudy brief-create` (terminal) ou `tostudy brief-open` (portal web)."
|
|
43476
|
+
];
|
|
43477
|
+
output(lines2.join("\n"), { json: false });
|
|
43478
|
+
return;
|
|
43479
|
+
}
|
|
43480
|
+
const lines = [`Brief base (atualizado em ${remote.updatedAt}):`, "", remote.text, ""];
|
|
43481
|
+
if (cached2 && cached2.brief && cached2.brief.updatedAt !== remote.updatedAt) {
|
|
43482
|
+
lines.push("Aviso: cache local esta desatualizado. Rode `tostudy sync` para atualizar.");
|
|
43483
|
+
}
|
|
43484
|
+
output(lines.join("\n"), { json: false });
|
|
43485
|
+
} catch (err) {
|
|
43486
|
+
logger21.error("Failed to show brief", { err });
|
|
43487
|
+
error(`Erro ao buscar brief: ${err instanceof Error ? err.message : String(err)}`);
|
|
43488
|
+
}
|
|
43489
|
+
});
|
|
43490
|
+
}
|
|
43491
|
+
});
|
|
43492
|
+
|
|
43493
|
+
// src/commands/brief-create.ts
|
|
43494
|
+
import { Command as Command23 } from "commander";
|
|
43495
|
+
var logger22, briefCreateCommand;
|
|
43496
|
+
var init_brief_create = __esm({
|
|
43497
|
+
"src/commands/brief-create.ts"() {
|
|
43498
|
+
"use strict";
|
|
43499
|
+
init_src();
|
|
43500
|
+
init_session();
|
|
43501
|
+
init_bootstrap();
|
|
43502
|
+
init_api();
|
|
43503
|
+
init_cache();
|
|
43504
|
+
init_formatter();
|
|
43505
|
+
logger22 = createLogger("cli:brief-create");
|
|
43506
|
+
briefCreateCommand = new Command23("brief-create").description("Create your base learner brief via interactive prompts (T1 bootstrap)").action(async () => {
|
|
43507
|
+
try {
|
|
43508
|
+
const session = await requireSession();
|
|
43509
|
+
const answers = await collectBootstrapAnswers({ userName: session.userName });
|
|
43510
|
+
const text2 = composeBriefFromAnswers(answers);
|
|
43511
|
+
const previewLines = ["", "Brief composto:", "---", text2, "---"];
|
|
43512
|
+
output(previewLines.join("\n"), { json: false });
|
|
43513
|
+
const brief = await upsertLearnerBrief({
|
|
43514
|
+
apiUrl: session.apiUrl,
|
|
43515
|
+
token: session.token,
|
|
43516
|
+
text: text2,
|
|
43517
|
+
source: "manual"
|
|
43518
|
+
});
|
|
43519
|
+
await writeBriefCache(void 0, {
|
|
43520
|
+
userId: session.userId,
|
|
43521
|
+
brief,
|
|
43522
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
43523
|
+
});
|
|
43524
|
+
const doneLines = [
|
|
43525
|
+
"",
|
|
43526
|
+
"Brief salvo. Rode `tostudy sync` para atualizar os instruction files."
|
|
43527
|
+
];
|
|
43528
|
+
output(doneLines.join("\n"), { json: false });
|
|
43529
|
+
} catch (err) {
|
|
43530
|
+
logger22.error("Failed to create brief", { err });
|
|
43531
|
+
error(`Erro ao criar brief: ${err instanceof Error ? err.message : String(err)}`);
|
|
43532
|
+
}
|
|
43533
|
+
});
|
|
43534
|
+
}
|
|
43535
|
+
});
|
|
43536
|
+
|
|
43537
|
+
// src/commands/brief-open.ts
|
|
43538
|
+
import { Command as Command24 } from "commander";
|
|
43539
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
43540
|
+
import { platform } from "node:process";
|
|
43541
|
+
function openUrl(url2) {
|
|
43542
|
+
let cmd;
|
|
43543
|
+
let args;
|
|
43544
|
+
if (platform === "darwin") {
|
|
43545
|
+
cmd = "open";
|
|
43546
|
+
args = [url2];
|
|
43547
|
+
} else if (platform === "win32") {
|
|
43548
|
+
cmd = "cmd";
|
|
43549
|
+
args = ["/c", "start", "", url2];
|
|
43550
|
+
} else {
|
|
43551
|
+
cmd = "xdg-open";
|
|
43552
|
+
args = [url2];
|
|
43553
|
+
}
|
|
43554
|
+
execFile4(cmd, args, (err) => {
|
|
43555
|
+
if (err) {
|
|
43556
|
+
output(`Nao consegui abrir o navegador. Acesse manualmente: ${url2}`, { json: false });
|
|
43557
|
+
}
|
|
43558
|
+
});
|
|
43559
|
+
}
|
|
43560
|
+
var BRIEF_URL, briefOpenCommand;
|
|
43561
|
+
var init_brief_open = __esm({
|
|
43562
|
+
"src/commands/brief-open.ts"() {
|
|
43563
|
+
"use strict";
|
|
43564
|
+
init_session();
|
|
43565
|
+
init_formatter();
|
|
43566
|
+
BRIEF_URL = "https://tostudy.ai/student/settings/learner-brief";
|
|
43567
|
+
briefOpenCommand = new Command24("brief-open").description("Open the learner brief editor in your web browser").action(async () => {
|
|
43568
|
+
await requireSession();
|
|
43569
|
+
output(`Abrindo ${BRIEF_URL} no navegador...`, { json: false });
|
|
43570
|
+
openUrl(BRIEF_URL);
|
|
43571
|
+
});
|
|
43572
|
+
}
|
|
43573
|
+
});
|
|
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 });
|
|
42813
43723
|
}
|
|
42814
43724
|
} catch (err) {
|
|
42815
43725
|
const msg = err instanceof Error ? err.message : String(err);
|
|
42816
43726
|
if (msg.includes("process.exit")) return;
|
|
43727
|
+
logger25.warn("context failed", { error: msg });
|
|
43728
|
+
if (opts.json) jsonError(msg);
|
|
42817
43729
|
error(msg);
|
|
42818
43730
|
}
|
|
42819
43731
|
});
|
|
@@ -42826,9 +43738,9 @@ __export(cli_exports, {
|
|
|
42826
43738
|
CLI_VERSION: () => CLI_VERSION,
|
|
42827
43739
|
createProgram: () => createProgram
|
|
42828
43740
|
});
|
|
42829
|
-
import { Command as
|
|
43741
|
+
import { Command as Command27 } from "commander";
|
|
42830
43742
|
function createProgram() {
|
|
42831
|
-
const program2 = new
|
|
43743
|
+
const program2 = new Command27();
|
|
42832
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");
|
|
42833
43745
|
program2.addCommand(setupCommand);
|
|
42834
43746
|
program2.addCommand(doctorCommand);
|
|
@@ -42846,11 +43758,16 @@ function createProgram() {
|
|
|
42846
43758
|
program2.addCommand(validateCommand);
|
|
42847
43759
|
program2.addCommand(menuCommand);
|
|
42848
43760
|
program2.addCommand(profileCommand);
|
|
43761
|
+
program2.addCommand(briefCommand);
|
|
43762
|
+
program2.addCommand(briefCreateCommand);
|
|
43763
|
+
program2.addCommand(briefOpenCommand);
|
|
42849
43764
|
program2.addCommand(workspaceCommand);
|
|
42850
43765
|
program2.addCommand(syncCommand);
|
|
42851
43766
|
program2.addCommand(exportCommand);
|
|
42852
43767
|
program2.addCommand(openCommand);
|
|
42853
43768
|
program2.addCommand(vaultCommand);
|
|
43769
|
+
program2.addCommand(compactCommand);
|
|
43770
|
+
program2.addCommand(contextCommand);
|
|
42854
43771
|
return program2;
|
|
42855
43772
|
}
|
|
42856
43773
|
var CLI_VERSION;
|
|
@@ -42878,7 +43795,12 @@ var init_cli = __esm({
|
|
|
42878
43795
|
init_vault2();
|
|
42879
43796
|
init_profile();
|
|
42880
43797
|
init_sync();
|
|
42881
|
-
|
|
43798
|
+
init_brief();
|
|
43799
|
+
init_brief_create();
|
|
43800
|
+
init_brief_open();
|
|
43801
|
+
init_compact();
|
|
43802
|
+
init_context2();
|
|
43803
|
+
CLI_VERSION = true ? "0.10.0" : "0.7.1";
|
|
42882
43804
|
}
|
|
42883
43805
|
});
|
|
42884
43806
|
|