@tostudy-ai/cli 0.7.4 → 0.9.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 +1836 -833
- package/dist/cli.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1289,29 +1289,227 @@ var init_secret_store = __esm({
|
|
|
1289
1289
|
}
|
|
1290
1290
|
});
|
|
1291
1291
|
|
|
1292
|
-
// src/
|
|
1293
|
-
import
|
|
1294
|
-
import path2 from "node:path";
|
|
1292
|
+
// src/workspace/workspace-marker.ts
|
|
1293
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync } from "node:fs";
|
|
1295
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 (parsed.version !== CURRENT_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;
|
|
1345
|
+
var init_workspace_marker = __esm({
|
|
1346
|
+
"src/workspace/workspace-marker.ts"() {
|
|
1347
|
+
"use strict";
|
|
1348
|
+
CURRENT_VERSION = 1;
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
// src/workspace/resolve.ts
|
|
1353
|
+
var resolve_exports = {};
|
|
1354
|
+
__export(resolve_exports, {
|
|
1355
|
+
courseSlug: () => courseSlug,
|
|
1356
|
+
findCwdWorkspaceUpwards: () => findCwdWorkspaceUpwards,
|
|
1357
|
+
findExistingVault: () => findExistingVault,
|
|
1358
|
+
isCwdWorkspace: () => isCwdWorkspace,
|
|
1359
|
+
resolveActiveCourseWithOverride: () => resolveActiveCourseWithOverride,
|
|
1360
|
+
resolveCwdWorkspacePath: () => resolveCwdWorkspacePath,
|
|
1361
|
+
resolveEffectiveWorkspace: () => resolveEffectiveWorkspace,
|
|
1362
|
+
resolveVaultPath: () => resolveVaultPath,
|
|
1363
|
+
resolveWorkspace: () => resolveWorkspace
|
|
1364
|
+
});
|
|
1365
|
+
import fs2 from "node:fs/promises";
|
|
1366
|
+
import path3 from "node:path";
|
|
1367
|
+
import os3 from "node:os";
|
|
1368
|
+
function courseSlug(title) {
|
|
1369
|
+
return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
1370
|
+
}
|
|
1371
|
+
async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
|
|
1372
|
+
const slug = courseSlug(courseTitle);
|
|
1373
|
+
const candidate = path3.join(basePath, slug);
|
|
1374
|
+
try {
|
|
1375
|
+
await fs2.access(path3.join(candidate, ".ana-config.json"));
|
|
1376
|
+
return { found: true, workspacePath: candidate, source: "default" };
|
|
1377
|
+
} catch {
|
|
1378
|
+
return { found: false, workspacePath: null };
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
async function isCwdWorkspace(cwd = process.cwd()) {
|
|
1382
|
+
return await resolveCwdWorkspacePath(cwd) !== null;
|
|
1383
|
+
}
|
|
1384
|
+
async function resolveCwdWorkspacePath(cwd = process.cwd()) {
|
|
1385
|
+
const tostudyDir = path3.join(cwd, ".tostudy");
|
|
1386
|
+
try {
|
|
1387
|
+
const stat = await fs2.stat(tostudyDir);
|
|
1388
|
+
if (stat.isDirectory()) return tostudyDir;
|
|
1389
|
+
} catch {
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
await fs2.access(path3.join(cwd, ".ana-config.json"));
|
|
1393
|
+
return cwd;
|
|
1394
|
+
} catch {
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
async function findCwdWorkspaceUpwards(startCwd = process.cwd(), homeDir = os3.homedir()) {
|
|
1399
|
+
const home = path3.resolve(homeDir);
|
|
1400
|
+
let current = path3.resolve(startCwd);
|
|
1401
|
+
while (true) {
|
|
1402
|
+
if (current === home) return null;
|
|
1403
|
+
const direct = await resolveCwdWorkspacePath(current);
|
|
1404
|
+
if (direct) return direct;
|
|
1405
|
+
const parent = path3.dirname(current);
|
|
1406
|
+
if (parent === current) return null;
|
|
1407
|
+
current = parent;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function resolveVaultPath(workspacePath, slug) {
|
|
1411
|
+
const base = path3.basename(workspacePath) === ".tostudy" ? path3.dirname(workspacePath) : workspacePath;
|
|
1412
|
+
return path3.join(base, `vault-${slug}`);
|
|
1413
|
+
}
|
|
1414
|
+
async function findExistingVault(workspacePath, slug) {
|
|
1415
|
+
const slugged = resolveVaultPath(workspacePath, slug);
|
|
1416
|
+
try {
|
|
1417
|
+
await fs2.access(path3.join(slugged, ".ana-vault.json"));
|
|
1418
|
+
return slugged;
|
|
1419
|
+
} catch {
|
|
1420
|
+
}
|
|
1421
|
+
const legacy = path3.join(workspacePath, "vault");
|
|
1422
|
+
try {
|
|
1423
|
+
await fs2.access(path3.join(legacy, ".ana-vault.json"));
|
|
1424
|
+
return legacy;
|
|
1425
|
+
} catch {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE, homeDir = os3.homedir()) {
|
|
1430
|
+
const cwdWorkspace = await findCwdWorkspaceUpwards(cwd, homeDir);
|
|
1431
|
+
if (cwdWorkspace) {
|
|
1432
|
+
return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
|
|
1433
|
+
}
|
|
1434
|
+
if (storedPath) {
|
|
1435
|
+
try {
|
|
1436
|
+
await fs2.access(path3.join(storedPath, ".ana-config.json"));
|
|
1437
|
+
return { found: true, workspacePath: storedPath, source: "stored" };
|
|
1438
|
+
} catch {
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const result = await resolveWorkspace(courseTitle, defaultBasePath);
|
|
1442
|
+
if (result.found) {
|
|
1443
|
+
return { ...result, source: "default" };
|
|
1444
|
+
}
|
|
1445
|
+
return { found: false, workspacePath: null };
|
|
1446
|
+
}
|
|
1447
|
+
async function findWorkspaceMarkerUpwards(startCwd, homeDir) {
|
|
1448
|
+
const home = path3.resolve(homeDir);
|
|
1449
|
+
let current = path3.resolve(startCwd);
|
|
1450
|
+
while (true) {
|
|
1451
|
+
if (current === home) return null;
|
|
1452
|
+
const marker = await readWorkspaceMarker(current);
|
|
1453
|
+
if (marker) return marker;
|
|
1454
|
+
const parent = path3.dirname(current);
|
|
1455
|
+
if (parent === current) return null;
|
|
1456
|
+
current = parent;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
async function resolveActiveCourseWithOverride(options = {}) {
|
|
1460
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1461
|
+
const homeDir = options.homeDir ?? os3.homedir();
|
|
1462
|
+
const marker = await findWorkspaceMarkerUpwards(cwd, homeDir);
|
|
1463
|
+
if (marker) {
|
|
1464
|
+
return {
|
|
1465
|
+
courseId: marker.courseId,
|
|
1466
|
+
enrollmentId: marker.enrollmentId,
|
|
1467
|
+
courseTitle: marker.courseTitle,
|
|
1468
|
+
source: "cwd-marker"
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
const global = await getActiveCourse(options.configDir);
|
|
1472
|
+
if (!global) return null;
|
|
1473
|
+
return {
|
|
1474
|
+
courseId: global.courseId,
|
|
1475
|
+
enrollmentId: global.enrollmentId,
|
|
1476
|
+
courseTitle: global.courseTitle,
|
|
1477
|
+
source: "global"
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
var DEFAULT_BASE;
|
|
1481
|
+
var init_resolve = __esm({
|
|
1482
|
+
"src/workspace/resolve.ts"() {
|
|
1483
|
+
"use strict";
|
|
1484
|
+
init_workspace_marker();
|
|
1485
|
+
init_session();
|
|
1486
|
+
DEFAULT_BASE = path3.join(os3.homedir(), "study");
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
// src/auth/session.ts
|
|
1491
|
+
import fs3 from "node:fs";
|
|
1492
|
+
import path4 from "node:path";
|
|
1493
|
+
import os4 from "node:os";
|
|
1296
1494
|
function getConfigDir(override) {
|
|
1297
1495
|
if (override) return override;
|
|
1298
1496
|
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
1299
|
-
return
|
|
1497
|
+
return path4.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
1300
1498
|
}
|
|
1301
|
-
return
|
|
1499
|
+
return path4.join(os4.homedir(), ".tostudy");
|
|
1302
1500
|
}
|
|
1303
1501
|
function getCourseOnboardingPath(configDir) {
|
|
1304
|
-
return
|
|
1502
|
+
return path4.join(getConfigDir(configDir), "course-onboarding.json");
|
|
1305
1503
|
}
|
|
1306
1504
|
function readCourseOnboardingState(configDir) {
|
|
1307
1505
|
const onboardingPath = getCourseOnboardingPath(configDir);
|
|
1308
|
-
if (!
|
|
1309
|
-
return JSON.parse(
|
|
1506
|
+
if (!fs3.existsSync(onboardingPath)) return {};
|
|
1507
|
+
return JSON.parse(fs3.readFileSync(onboardingPath, "utf-8"));
|
|
1310
1508
|
}
|
|
1311
1509
|
function writeCourseOnboardingState(state, configDir) {
|
|
1312
1510
|
const dir = getConfigDir(configDir);
|
|
1313
|
-
|
|
1314
|
-
|
|
1511
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1512
|
+
fs3.writeFileSync(getCourseOnboardingPath(configDir), JSON.stringify(state, null, 2), {
|
|
1315
1513
|
mode: 384
|
|
1316
1514
|
});
|
|
1317
1515
|
}
|
|
@@ -1343,27 +1541,27 @@ async function saveCourseLearnerProfile(course, learnerProfile, artifacts, confi
|
|
|
1343
1541
|
await saveUserProfile(learnerProfile, configDir);
|
|
1344
1542
|
}
|
|
1345
1543
|
function getUserProfilePath(configDir) {
|
|
1346
|
-
return
|
|
1544
|
+
return path4.join(getConfigDir(configDir), "user-profile.json");
|
|
1347
1545
|
}
|
|
1348
1546
|
async function getUserProfile(configDir) {
|
|
1349
1547
|
const profilePath = getUserProfilePath(configDir);
|
|
1350
|
-
if (!
|
|
1548
|
+
if (!fs3.existsSync(profilePath)) return null;
|
|
1351
1549
|
try {
|
|
1352
|
-
return JSON.parse(
|
|
1550
|
+
return JSON.parse(fs3.readFileSync(profilePath, "utf-8"));
|
|
1353
1551
|
} catch {
|
|
1354
1552
|
return null;
|
|
1355
1553
|
}
|
|
1356
1554
|
}
|
|
1357
1555
|
async function saveUserProfile(profile, configDir) {
|
|
1358
1556
|
const dir = getConfigDir(configDir);
|
|
1359
|
-
|
|
1360
|
-
|
|
1557
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1558
|
+
fs3.writeFileSync(getUserProfilePath(configDir), JSON.stringify(profile, null, 2), {
|
|
1361
1559
|
mode: 384
|
|
1362
1560
|
});
|
|
1363
1561
|
}
|
|
1364
1562
|
async function saveSession(session, configDir) {
|
|
1365
1563
|
const dir = getConfigDir(configDir);
|
|
1366
|
-
|
|
1564
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1367
1565
|
await saveSessionSecrets(
|
|
1368
1566
|
{
|
|
1369
1567
|
accessToken: session.token,
|
|
@@ -1378,15 +1576,15 @@ async function saveSession(session, configDir) {
|
|
|
1378
1576
|
apiUrl: session.apiUrl,
|
|
1379
1577
|
sessionId: session.sessionId
|
|
1380
1578
|
};
|
|
1381
|
-
|
|
1579
|
+
fs3.writeFileSync(path4.join(dir, "config.json"), JSON.stringify(stored, null, 2), {
|
|
1382
1580
|
mode: 384
|
|
1383
1581
|
});
|
|
1384
1582
|
}
|
|
1385
1583
|
async function getSession(configDir) {
|
|
1386
1584
|
const dir = getConfigDir(configDir);
|
|
1387
|
-
const p =
|
|
1388
|
-
if (!
|
|
1389
|
-
const stored = JSON.parse(
|
|
1585
|
+
const p = path4.join(dir, "config.json");
|
|
1586
|
+
if (!fs3.existsSync(p)) return null;
|
|
1587
|
+
const stored = JSON.parse(fs3.readFileSync(p, "utf-8"));
|
|
1390
1588
|
const secrets = await loadStoredSessionSecrets(dir);
|
|
1391
1589
|
if (!secrets) return null;
|
|
1392
1590
|
return {
|
|
@@ -1401,26 +1599,26 @@ async function getSession(configDir) {
|
|
|
1401
1599
|
}
|
|
1402
1600
|
async function clearSession(configDir) {
|
|
1403
1601
|
const dir = getConfigDir(configDir);
|
|
1404
|
-
const p =
|
|
1405
|
-
if (
|
|
1406
|
-
const ap =
|
|
1407
|
-
if (
|
|
1602
|
+
const p = path4.join(dir, "config.json");
|
|
1603
|
+
if (fs3.existsSync(p)) fs3.unlinkSync(p);
|
|
1604
|
+
const ap = path4.join(dir, "active-course.json");
|
|
1605
|
+
if (fs3.existsSync(ap)) fs3.unlinkSync(ap);
|
|
1408
1606
|
const onboardingPath = getCourseOnboardingPath(configDir);
|
|
1409
|
-
if (
|
|
1607
|
+
if (fs3.existsSync(onboardingPath)) fs3.unlinkSync(onboardingPath);
|
|
1410
1608
|
await deleteStoredSessionSecrets(dir);
|
|
1411
1609
|
}
|
|
1412
1610
|
async function getActiveCourse(configDir) {
|
|
1413
1611
|
const dir = getConfigDir(configDir);
|
|
1414
|
-
const p =
|
|
1415
|
-
if (!
|
|
1416
|
-
return JSON.parse(
|
|
1612
|
+
const p = path4.join(dir, "active-course.json");
|
|
1613
|
+
if (!fs3.existsSync(p)) return null;
|
|
1614
|
+
return JSON.parse(fs3.readFileSync(p, "utf-8"));
|
|
1417
1615
|
}
|
|
1418
1616
|
async function setActiveCourse(course, configDir) {
|
|
1419
1617
|
const dir = getConfigDir(configDir);
|
|
1420
|
-
|
|
1618
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1421
1619
|
const onboardingState = await getCourseOnboardingState(course.courseId, configDir);
|
|
1422
1620
|
const persistedCourse = onboardingState?.initCompletedAt && !course.lastInitCourseId ? { ...course, lastInitCourseId: course.courseId } : course;
|
|
1423
|
-
|
|
1621
|
+
fs3.writeFileSync(path4.join(dir, "active-course.json"), JSON.stringify(persistedCourse, null, 2), {
|
|
1424
1622
|
mode: 384
|
|
1425
1623
|
});
|
|
1426
1624
|
}
|
|
@@ -1466,13 +1664,23 @@ async function requireSession(configDir) {
|
|
|
1466
1664
|
}
|
|
1467
1665
|
return session;
|
|
1468
1666
|
}
|
|
1469
|
-
async function requireActiveCourse(configDir) {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1667
|
+
async function requireActiveCourse(configDir, options = {}) {
|
|
1668
|
+
if (options.respectWorkspace) {
|
|
1669
|
+
const { resolveActiveCourseWithOverride: resolveActiveCourseWithOverride2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
|
|
1670
|
+
const resolved = await resolveActiveCourseWithOverride2({ configDir });
|
|
1671
|
+
if (resolved) {
|
|
1672
|
+
return {
|
|
1673
|
+
courseId: resolved.courseId,
|
|
1674
|
+
enrollmentId: resolved.enrollmentId,
|
|
1675
|
+
courseTitle: resolved.courseTitle
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
} else {
|
|
1679
|
+
const course = await getActiveCourse(configDir);
|
|
1680
|
+
if (course) return course;
|
|
1474
1681
|
}
|
|
1475
|
-
|
|
1682
|
+
process.stderr.write("Erro: Nenhum curso ativo. Rode: tostudy select <curso>\n");
|
|
1683
|
+
process.exit(3);
|
|
1476
1684
|
}
|
|
1477
1685
|
async function setLastInitCourseId(courseId, configDir) {
|
|
1478
1686
|
const existing = await getActiveCourse(configDir);
|
|
@@ -2153,34 +2361,34 @@ var init_node_detector = __esm({
|
|
|
2153
2361
|
|
|
2154
2362
|
// src/installer/ide-detector.ts
|
|
2155
2363
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2156
|
-
import
|
|
2157
|
-
import
|
|
2158
|
-
import
|
|
2364
|
+
import fs4 from "node:fs";
|
|
2365
|
+
import path5 from "node:path";
|
|
2366
|
+
import os5 from "node:os";
|
|
2159
2367
|
function checkExists(p) {
|
|
2160
|
-
return
|
|
2368
|
+
return fs4.existsSync(p);
|
|
2161
2369
|
}
|
|
2162
2370
|
function detectIDEs() {
|
|
2163
|
-
const home =
|
|
2371
|
+
const home = os5.homedir();
|
|
2164
2372
|
const ides = [
|
|
2165
2373
|
// Claude Code: detected by presence of 'claude' binary
|
|
2166
2374
|
{
|
|
2167
2375
|
name: "Claude Code",
|
|
2168
2376
|
id: "claude-code",
|
|
2169
2377
|
detected: false,
|
|
2170
|
-
configPath:
|
|
2378
|
+
configPath: path5.join(home, ".mcp.json")
|
|
2171
2379
|
},
|
|
2172
2380
|
// Cursor: ~/.cursor directory
|
|
2173
2381
|
{
|
|
2174
2382
|
name: "Cursor",
|
|
2175
2383
|
id: "cursor",
|
|
2176
|
-
detected: checkExists(
|
|
2177
|
-
configPath:
|
|
2384
|
+
detected: checkExists(path5.join(home, ".cursor")),
|
|
2385
|
+
configPath: path5.join(home, ".cursor", "mcp.json")
|
|
2178
2386
|
},
|
|
2179
2387
|
// VS Code: ~/.vscode directory
|
|
2180
2388
|
{
|
|
2181
2389
|
name: "VS Code",
|
|
2182
2390
|
id: "vscode",
|
|
2183
|
-
detected: checkExists(
|
|
2391
|
+
detected: checkExists(path5.join(home, ".vscode")),
|
|
2184
2392
|
configPath: ".vscode/mcp.json"
|
|
2185
2393
|
},
|
|
2186
2394
|
// Claude Desktop: platform-specific config dir
|
|
@@ -2188,29 +2396,29 @@ function detectIDEs() {
|
|
|
2188
2396
|
name: "Claude Desktop",
|
|
2189
2397
|
id: "desktop",
|
|
2190
2398
|
detected: checkExists(
|
|
2191
|
-
process.platform === "darwin" ?
|
|
2399
|
+
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
2400
|
),
|
|
2193
|
-
configPath: process.platform === "darwin" ?
|
|
2401
|
+
configPath: process.platform === "darwin" ? path5.join(
|
|
2194
2402
|
home,
|
|
2195
2403
|
"Library",
|
|
2196
2404
|
"Application Support",
|
|
2197
2405
|
"Claude",
|
|
2198
2406
|
"claude_desktop_config.json"
|
|
2199
|
-
) : process.platform === "win32" ?
|
|
2407
|
+
) : process.platform === "win32" ? path5.join(process.env["APPDATA"] ?? home, "Claude", "claude_desktop_config.json") : path5.join(home, ".config", "claude", "claude_desktop_config.json")
|
|
2200
2408
|
},
|
|
2201
2409
|
// Windsurf: ~/.codeium/windsurf
|
|
2202
2410
|
{
|
|
2203
2411
|
name: "Windsurf",
|
|
2204
2412
|
id: "windsurf",
|
|
2205
|
-
detected: checkExists(
|
|
2206
|
-
configPath:
|
|
2413
|
+
detected: checkExists(path5.join(home, ".codeium", "windsurf")),
|
|
2414
|
+
configPath: path5.join(home, ".codeium", "windsurf", "mcp_config.json")
|
|
2207
2415
|
},
|
|
2208
2416
|
// OpenCode: ~/.opencode
|
|
2209
2417
|
{
|
|
2210
2418
|
name: "OpenCode",
|
|
2211
2419
|
id: "opencode",
|
|
2212
|
-
detected: checkExists(
|
|
2213
|
-
configPath:
|
|
2420
|
+
detected: checkExists(path5.join(home, ".opencode")),
|
|
2421
|
+
configPath: path5.join(home, ".opencode", "opencode.json")
|
|
2214
2422
|
},
|
|
2215
2423
|
// Manual (always available as fallback)
|
|
2216
2424
|
{
|
|
@@ -2277,354 +2485,335 @@ var init_mcp_setup = __esm({
|
|
|
2277
2485
|
}
|
|
2278
2486
|
});
|
|
2279
2487
|
|
|
2280
|
-
// src/
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2488
|
+
// src/output/instruction-template-v3.ts
|
|
2489
|
+
function renderCourseInstruction(input2) {
|
|
2490
|
+
const sections = [];
|
|
2491
|
+
sections.push(renderTitle(input2));
|
|
2492
|
+
sections.push(renderWhoYouAre(input2.tutorPersona));
|
|
2493
|
+
sections.push(renderVoiceRules());
|
|
2494
|
+
sections.push(renderWhoIsStudent(input2.studentBrief, input2.enrollmentProfile));
|
|
2495
|
+
sections.push(renderHowToAdapt(input2.enrollmentProfile));
|
|
2496
|
+
sections.push(renderPrecedence());
|
|
2497
|
+
sections.push(renderWhereStudentIs(input2.progress));
|
|
2498
|
+
sections.push(renderHowToConduct());
|
|
2499
|
+
sections.push(renderSilentTools());
|
|
2500
|
+
sections.push(renderHandlingSituations());
|
|
2501
|
+
sections.push(renderTechnicalReference());
|
|
2502
|
+
sections.push(renderFooter(input2.course));
|
|
2503
|
+
return sections.join("\n\n") + "\n";
|
|
2285
2504
|
}
|
|
2286
|
-
function
|
|
2505
|
+
function renderUniversalInstruction() {
|
|
2287
2506
|
const sections = [];
|
|
2288
|
-
sections.push(`#
|
|
2289
|
-
sections.push(`##
|
|
2290
|
-
|
|
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
|
-
if (ctx.currentLessonTitle) {
|
|
2316
|
-
progressLines.push(`- **Li\xE7\xE3o atual**: ${ctx.currentLessonTitle}`);
|
|
2317
|
-
}
|
|
2318
|
-
sections.push(`## Progresso Atual
|
|
2319
|
-
|
|
2320
|
-
${progressLines.join("\n")}`);
|
|
2321
|
-
sections.push(`## In\xEDcio de Sess\xE3o
|
|
2322
|
-
|
|
2323
|
-
Quando o aluno iniciar uma conversa ou invocar este comando:
|
|
2324
|
-
|
|
2325
|
-
1. Cumprimente brevemente e mencione o curso
|
|
2326
|
-
2. Rode \`tostudy progress\` para verificar onde o aluno parou
|
|
2327
|
-
3. Resuma o estado: "Voc\xEA est\xE1 no M\xF3dulo X, Li\xE7\xE3o Y \u2014 [t\xEDtulo]"
|
|
2328
|
-
4. Pergunte: "Quer continuar de onde parou ou revisar algo?"`);
|
|
2329
|
-
sections.push(`## Fluxo de Estudo
|
|
2330
|
-
|
|
2331
|
-
Siga esta sequ\xEAncia rigorosamente:
|
|
2332
|
-
|
|
2333
|
-
\`\`\`
|
|
2334
|
-
IN\xCDCIO:
|
|
2335
|
-
tostudy progress \u2192 Ver onde parou
|
|
2336
|
-
tostudy start \u2192 Carregar m\xF3dulo atual
|
|
2337
|
-
|
|
2338
|
-
LOOP DE ESTUDO (repetir para cada li\xE7\xE3o):
|
|
2339
|
-
tostudy lesson \u2192 Ler conte\xFAdo da li\xE7\xE3o
|
|
2340
|
-
|
|
2341
|
-
[Se EXERC\xCDCIO]:
|
|
2342
|
-
\u2192 Aluno implementa a solu\xE7\xE3o
|
|
2343
|
-
\u2192 tostudy hint \u2192 Dica progressiva (se travado)
|
|
2344
|
-
\u2192 tostudy validate <arquivo> \u2192 Validar solu\xE7\xE3o
|
|
2345
|
-
\u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
|
|
2346
|
-
\u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2347
|
-
|
|
2348
|
-
[Se TEORIA/TEXTO]:
|
|
2349
|
-
\u2192 Explicar conceitos-chave
|
|
2350
|
-
\u2192 Fazer perguntas para verificar entendimento
|
|
2351
|
-
\u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2352
|
-
|
|
2353
|
-
[Se QUIZ/CHECKPOINT]:
|
|
2354
|
-
\u2192 Aluno escreve respostas em arquivo (ex: checkpoint.md)
|
|
2355
|
-
\u2192 tostudy validate checkpoint.md
|
|
2356
|
-
\u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2357
|
-
|
|
2358
|
-
FIM DO M\xD3DULO:
|
|
2359
|
-
\u2192 Parabenizar o aluno
|
|
2360
|
-
\u2192 tostudy start \u2192 Carregar pr\xF3ximo m\xF3dulo
|
|
2361
|
-
\`\`\``);
|
|
2362
|
-
sections.push(`## Guia por Tipo de Li\xE7\xE3o
|
|
2363
|
-
|
|
2364
|
-
**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.
|
|
2365
|
-
|
|
2366
|
-
**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.
|
|
2367
|
-
|
|
2368
|
-
**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.
|
|
2369
|
-
|
|
2370
|
-
**V\xEDdeo (video):** Resuma os pontos-chave e aguarde o aluno assistir. Depois discuta o conte\xFAdo.`);
|
|
2371
|
-
const rules = [
|
|
2372
|
-
"**Nunca d\xEA a resposta direta** \u2014 guie com perguntas e dicas progressivas",
|
|
2373
|
-
"**Hints primeiro** \u2014 sempre rode `tostudy hint` antes de explicar a solu\xE7\xE3o",
|
|
2374
|
-
"**Valide antes de avan\xE7ar** \u2014 exerc\xEDcios exigem `tostudy validate` com nota de aprova\xE7\xE3o",
|
|
2375
|
-
"**Celebre progresso** \u2014 reconhe\xE7a quando o aluno completa li\xE7\xF5es e m\xF3dulos",
|
|
2376
|
-
"**Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es",
|
|
2377
|
-
"**Adapte ao n\xEDvel** \u2014 ajuste profundidade e exemplos conforme o perfil do aluno",
|
|
2378
|
-
"**Sempre rode `tostudy lesson`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material",
|
|
2379
|
-
'**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.'
|
|
2380
|
-
];
|
|
2381
|
-
if (learner?.adaptToRealContext) {
|
|
2382
|
-
rules.push("**Use contexto real** \u2014 adapte exemplos ao projeto/empresa do aluno");
|
|
2383
|
-
}
|
|
2384
|
-
sections.push(`## Regras de Ouro
|
|
2385
|
-
|
|
2386
|
-
${rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`);
|
|
2387
|
-
sections.push(`## Quando Algo D\xE1 Errado
|
|
2388
|
-
|
|
2389
|
-
| Situa\xE7\xE3o | O que fazer |
|
|
2390
|
-
|----------|-------------|
|
|
2391
|
-
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2392
|
-
| \`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. |
|
|
2393
|
-
| "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
|
|
2394
|
-
| Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
|
|
2395
|
-
| Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2396
|
-
| "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |`);
|
|
2397
|
-
if (ctx.workspaceReady !== false) {
|
|
2398
|
-
sections.push(`## Workspace
|
|
2399
|
-
|
|
2400
|
-
O workspace local organiza os arquivos do curso:
|
|
2401
|
-
|
|
2402
|
-
\`\`\`
|
|
2403
|
-
~/study/{slug}/
|
|
2404
|
-
\u251C\u2500\u2500 exercises/{m\xF3dulo}/{li\xE7\xE3o}/ \u2190 Exerc\xEDcios extra\xEDdos
|
|
2405
|
-
\u251C\u2500\u2500 generated/ \u2190 Artefatos gerados
|
|
2406
|
-
\u2514\u2500\u2500 diagrams/ \u2190 Diagramas
|
|
2407
|
-
\`\`\`
|
|
2408
|
-
|
|
2409
|
-
Comandos \xFAteis:
|
|
2410
|
-
- \`tostudy export\` \u2014 Extrair exerc\xEDcio para o workspace
|
|
2411
|
-
- \`tostudy open\` \u2014 Abrir workspace no editor
|
|
2412
|
-
- \`tostudy workspace status\` \u2014 Verificar estado do workspace`);
|
|
2413
|
-
}
|
|
2414
|
-
sections.push(`## Comandos CLI
|
|
2415
|
-
|
|
2416
|
-
| Comando | Quando usar |
|
|
2417
|
-
|---------|-------------|
|
|
2418
|
-
| \`tostudy progress\` | No in\xEDcio da sess\xE3o e quando o aluno perguntar "onde estou?" |
|
|
2419
|
-
| \`tostudy start\` | Para carregar o m\xF3dulo atual ou o pr\xF3ximo |
|
|
2420
|
-
| \`tostudy lesson\` | Antes de discutir qualquer conte\xFAdo \u2014 sempre ler primeiro |
|
|
2421
|
-
| \`tostudy next\` | Ap\xF3s completar uma li\xE7\xE3o (teoria discutida ou exerc\xEDcio validado) |
|
|
2422
|
-
| \`tostudy hint\` | Quando o aluno travar \u2014 antes de explicar voc\xEA mesmo |
|
|
2423
|
-
| \`tostudy validate <arquivo>\` | Para validar exerc\xEDcios e checkpoints |
|
|
2424
|
-
| \`tostudy export\` | Para extrair exerc\xEDcio ao workspace local |`);
|
|
2425
|
-
sections.push(`## Refer\xEAncia T\xE9cnica (Modo Agente)
|
|
2426
|
-
|
|
2427
|
-
- Use \`--json\` em qualquer comando para sa\xEDda estruturada (ex: \`tostudy progress --json\`)
|
|
2428
|
-
- \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado)
|
|
2429
|
-
- \`tostudy validate --stdin\` aceita solu\xE7\xE3o via pipe
|
|
2430
|
-
- \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\``);
|
|
2431
|
-
sections.push(`<!-- tostudy-course-id: ${ctx.courseId} -->`);
|
|
2507
|
+
sections.push(`# ToStudy \u2014 Seu Tutor`);
|
|
2508
|
+
sections.push(`## Quem Voce E
|
|
2509
|
+
|
|
2510
|
+
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.`);
|
|
2511
|
+
sections.push(renderVoiceRules());
|
|
2512
|
+
sections.push(`## Primeira Coisa (SEMPRE)
|
|
2513
|
+
|
|
2514
|
+
Rode \`tostudy progress --json\` em silencio para descobrir o estado do aluno.
|
|
2515
|
+
|
|
2516
|
+
**Se erro "Nenhum curso ativo":**
|
|
2517
|
+
|
|
2518
|
+
1. Rode \`tostudy courses --json\` em silencio.
|
|
2519
|
+
2. Mostre a lista ao aluno de forma amigavel.
|
|
2520
|
+
3. Pergunte: "Qual curso quer estudar?".
|
|
2521
|
+
4. Rode \`tostudy select <numero>\` com a escolha.
|
|
2522
|
+
5. Volte ao inicio.
|
|
2523
|
+
|
|
2524
|
+
**Se curso ativo mas sem brief do aluno:**
|
|
2525
|
+
|
|
2526
|
+
Nao interrompa. Comece a aula normalmente. Durante a conversa, colete contexto natural e, quando for apropriado, sugira ao aluno rodar \`tostudy brief-create\` ou abrir \`https://tostudy.ai/student/settings/learner-brief\`.
|
|
2527
|
+
|
|
2528
|
+
**Se tudo pronto:** Siga "Como Conduzir a Aula" abaixo.`);
|
|
2529
|
+
sections.push(renderHowToConduct());
|
|
2530
|
+
sections.push(renderSilentTools());
|
|
2531
|
+
sections.push(renderHandlingSituations());
|
|
2532
|
+
sections.push(renderTechnicalReference());
|
|
2533
|
+
sections.push(`<!-- tostudy-template-version: 3 -->`);
|
|
2432
2534
|
return sections.join("\n\n") + "\n";
|
|
2433
2535
|
}
|
|
2434
|
-
function
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
const written = [];
|
|
2439
|
-
const claudeDir = join2(cwd, ".claude", "commands");
|
|
2440
|
-
if (!existsSync2(claudeDir)) {
|
|
2441
|
-
mkdirSync2(claudeDir, { recursive: true });
|
|
2536
|
+
function renderTitle(input2) {
|
|
2537
|
+
if (input2.tutorPersona) {
|
|
2538
|
+
const emoji3 = input2.tutorPersona.avatarEmoji ? ` ${input2.tutorPersona.avatarEmoji}` : "";
|
|
2539
|
+
return `# ${input2.course.courseTitle} \u2014 ${input2.tutorPersona.tutorName}${emoji3}, seu tutor`;
|
|
2442
2540
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
if (!existsSync2(cursorDir)) {
|
|
2449
|
-
mkdirSync2(cursorDir, { recursive: true });
|
|
2450
|
-
}
|
|
2451
|
-
const mdcContent = `---
|
|
2452
|
-
description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
|
|
2453
|
-
globs: ["**/*"]
|
|
2454
|
-
alwaysApply: true
|
|
2455
|
-
---
|
|
2541
|
+
return `# ${input2.course.courseTitle} \u2014 Seu Tutor`;
|
|
2542
|
+
}
|
|
2543
|
+
function renderWhoYouAre(persona) {
|
|
2544
|
+
if (!persona) {
|
|
2545
|
+
return `## Quem Voce E
|
|
2456
2546
|
|
|
2457
|
-
|
|
2458
|
-
const cursorFile = `tostudy-${slug}.mdc`;
|
|
2459
|
-
writeFileSync2(join2(cursorDir, cursorFile), mdcContent);
|
|
2460
|
-
written.push(`.cursor/rules/${cursorFile}`);
|
|
2547
|
+
Voce e o tutor deste curso. Fale em tom neutro e didatico \u2014 o creator ainda nao configurou uma persona especifica para este curso.`;
|
|
2461
2548
|
}
|
|
2462
|
-
const
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
}
|
|
2466
|
-
writeFileSync2(join2(courseDir, "AGENTS.md"), content);
|
|
2467
|
-
written.push(`.tostudy/courses/${slug}/AGENTS.md`);
|
|
2468
|
-
logger3.info("Generated instruction files", {
|
|
2469
|
-
slug,
|
|
2470
|
-
files: written,
|
|
2471
|
-
hasLearnerProfile: !!learner
|
|
2472
|
-
});
|
|
2473
|
-
return slug;
|
|
2474
|
-
}
|
|
2475
|
-
function buildUniversalInstructionContent() {
|
|
2476
|
-
return `# ToStudy \u2014 Tutor AI
|
|
2549
|
+
const emoji3 = persona.avatarEmoji ? ` ${persona.avatarEmoji}` : "";
|
|
2550
|
+
const toneLabel = TONE_LABELS[persona.tone] ?? persona.tone;
|
|
2551
|
+
return `## Quem Voce E
|
|
2477
2552
|
|
|
2478
|
-
|
|
2553
|
+
Voce e **${persona.tutorName}**${emoji3}, o tutor deste curso. Nao "um tutor" \u2014 e voce.
|
|
2479
2554
|
|
|
2480
|
-
|
|
2555
|
+
### Sua Bagagem
|
|
2481
2556
|
|
|
2482
|
-
|
|
2557
|
+
${persona.background}
|
|
2483
2558
|
|
|
2484
|
-
###
|
|
2559
|
+
### Como Voce Fala
|
|
2485
2560
|
|
|
2486
|
-
|
|
2487
|
-
1. Rode \`tostudy courses --json\` para listar cursos matriculados
|
|
2488
|
-
2. Mostre a lista ao aluno de forma amig\xE1vel
|
|
2489
|
-
3. Pe\xE7a para escolher: "Qual curso quer estudar?"
|
|
2490
|
-
4. Rode \`tostudy select <n\xFAmero>\` com a escolha
|
|
2491
|
-
5. Volte ao in\xEDcio (tostudy progress --json)
|
|
2492
|
-
\`\`\`
|
|
2561
|
+
Seu tom natural e **${toneLabel}**. Seu estilo de comunicacao:
|
|
2493
2562
|
|
|
2494
|
-
|
|
2563
|
+
> ${persona.communicationStyle}
|
|
2495
2564
|
|
|
2496
|
-
|
|
2565
|
+
### Seu Dominio e Expressoes
|
|
2497
2566
|
|
|
2498
|
-
|
|
2567
|
+
Suas especialidades: ${persona.specialties.join(", ")}.
|
|
2499
2568
|
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
3. "Quais produtos ou servi\xE7os voc\xEAs oferecem?" \u2192 products
|
|
2505
|
-
4. "Em qual regi\xE3o atuam?" \u2192 region
|
|
2506
|
-
5. "Qual equipe est\xE1 envolvida?" \u2192 team
|
|
2507
|
-
6. "Qual seu objetivo principal com este curso?" \u2192 goal
|
|
2508
|
-
7. "Como voc\xEA se considera: iniciante, intermedi\xE1rio ou avan\xE7ado?" \u2192 level
|
|
2509
|
-
8. "Quer que eu adapte os exemplos ao seu contexto real?" \u2192 adapt-context
|
|
2569
|
+
Expressoes que voce usa organicamente (nao force, use quando fizer sentido): ${persona.catchphrases.join(" - ")}.`;
|
|
2570
|
+
}
|
|
2571
|
+
function renderVoiceRules() {
|
|
2572
|
+
return `## Regras de Voz
|
|
2510
2573
|
|
|
2511
|
-
|
|
2512
|
-
tostudy init --segment "X" --company "Y" --products "Z" --region "W" --team "T" --goal "G" --level intermediate --adapt-context --json
|
|
2513
|
-
\`\`\`
|
|
2574
|
+
Fale com o aluno em **primeira pessoa**. Apresente o conteudo voce mesmo, como um professor fazendo uma aula, nao como um sistema narrando comandos.
|
|
2514
2575
|
|
|
2515
|
-
|
|
2576
|
+
| Nunca | Sempre |
|
|
2577
|
+
| ---------------------------------------- | ----------------------------------- |
|
|
2578
|
+
| "O tutor vai mostrar..." | "Vou te mostrar..." |
|
|
2579
|
+
| "O aluno deve rodar \`tostudy lesson\`" | "Olha so, a ideia desta licao e..." |
|
|
2580
|
+
| "O sistema validou sua resposta" | "Sua resposta passou \u2014 parabens!" |
|
|
2581
|
+
| "Agora executando \`tostudy next\`..." | "Vamos para a proxima." |
|
|
2582
|
+
| "Conforme o material indica..." | "Repara como funciona..." |
|
|
2516
2583
|
|
|
2517
|
-
|
|
2584
|
+
**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.`;
|
|
2585
|
+
}
|
|
2586
|
+
function renderWhoIsStudent(brief, profile) {
|
|
2587
|
+
const parts = ["## Quem e o Aluno"];
|
|
2588
|
+
parts.push("### Base");
|
|
2589
|
+
if (brief && brief.text.trim().length > 0) {
|
|
2590
|
+
parts.push(brief.text);
|
|
2591
|
+
parts.push(
|
|
2592
|
+
"_(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.)_"
|
|
2593
|
+
);
|
|
2594
|
+
} else {
|
|
2595
|
+
parts.push(
|
|
2596
|
+
"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."
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
parts.push("### Neste Curso");
|
|
2600
|
+
if (profile) {
|
|
2601
|
+
const levelLabel = LEVEL_LABELS[profile.learnerLevel];
|
|
2602
|
+
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.";
|
|
2603
|
+
parts.push(
|
|
2604
|
+
[
|
|
2605
|
+
`- **Objetivo neste curso:** ${profile.goal}`,
|
|
2606
|
+
`- **Nivel neste assunto:** ${profile.learnerLevel} (${levelLabel})`,
|
|
2607
|
+
`- **Contexto de aplicacao:** ${profile.productsOrServices} na ${profile.company}`,
|
|
2608
|
+
`- **Segmento:** ${profile.segment}`,
|
|
2609
|
+
`- **Regiao:** ${profile.region}`,
|
|
2610
|
+
`- **Equipe envolvida:** ${profile.team}`,
|
|
2611
|
+
`- **Adaptar exemplos ao contexto real:** ${adaptLabel}`
|
|
2612
|
+
].join("\n")
|
|
2613
|
+
);
|
|
2614
|
+
} else {
|
|
2615
|
+
parts.push(
|
|
2616
|
+
"Ainda nao sei seu nivel neste assunto. Comece explicativo mas breve; calibre pelas primeiras respostas dele."
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
return parts.join("\n\n");
|
|
2620
|
+
}
|
|
2621
|
+
function renderHowToAdapt(profile) {
|
|
2622
|
+
if (!profile) {
|
|
2623
|
+
return `### Como Adaptar
|
|
2518
2624
|
|
|
2519
|
-
|
|
2625
|
+
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".`;
|
|
2626
|
+
}
|
|
2627
|
+
if (profile.learnerLevel === "beginner") {
|
|
2628
|
+
return `### Como Adaptar
|
|
2520
2629
|
|
|
2521
|
-
|
|
2522
|
-
IN\xCDCIO:
|
|
2523
|
-
tostudy progress \u2192 Ver onde parou
|
|
2524
|
-
tostudy start \u2192 Carregar m\xF3dulo atual
|
|
2630
|
+
O aluno e iniciante neste assunto. Regras:
|
|
2525
2631
|
|
|
2526
|
-
|
|
2527
|
-
|
|
2632
|
+
- Sempre explique o **porque** antes do **como**.
|
|
2633
|
+
- Use analogias do cotidiano. Evite jargao sem traduzir.
|
|
2634
|
+
- Celebre pequenas vitorias. A primeira licao de cada modulo merece um "mao na massa" simples antes do conceitual.
|
|
2635
|
+
- Se ele travar, comece com dica sutil \u2014 nao pule direto para a resposta.`;
|
|
2636
|
+
}
|
|
2637
|
+
if (profile.learnerLevel === "advanced") {
|
|
2638
|
+
return `### Como Adaptar
|
|
2528
2639
|
|
|
2529
|
-
|
|
2530
|
-
\u2192 Aluno implementa a solu\xE7\xE3o
|
|
2531
|
-
\u2192 tostudy hint \u2192 Dica progressiva (se travado)
|
|
2532
|
-
\u2192 tostudy validate <arquivo> \u2192 Validar solu\xE7\xE3o
|
|
2533
|
-
\u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
|
|
2534
|
-
\u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2640
|
+
O aluno e avancado. Regras:
|
|
2535
2641
|
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2642
|
+
- Assuma que ele ja sabe o basico. Comece direto pelo caso interessante.
|
|
2643
|
+
- Traga edge cases, trade-offs de arquitetura, alternativas reais.
|
|
2644
|
+
- Seja breve no obvio, profundo no nao-obvio.
|
|
2645
|
+
- Se ele perguntar algo trivial, responda direto sem rodeios didaticos.`;
|
|
2646
|
+
}
|
|
2647
|
+
return `### Como Adaptar
|
|
2540
2648
|
|
|
2541
|
-
|
|
2542
|
-
\u2192 Aluno escreve respostas em arquivo (ex: checkpoint.md)
|
|
2543
|
-
\u2192 tostudy validate checkpoint.md
|
|
2544
|
-
\u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2649
|
+
O aluno ja tem base. Regras:
|
|
2545
2650
|
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2651
|
+
- Pule fundamentos obvios. Foque em padroes, boas praticas e decisoes de design.
|
|
2652
|
+
- Mostre trade-offs: "isso funciona, mas se o volume crescer, prefira X porque...".
|
|
2653
|
+
- Desafie com perguntas tipo "o que aconteceria se...?".
|
|
2654
|
+
- Conecte conceitos desta licao com outras que ele ja fez.`;
|
|
2655
|
+
}
|
|
2656
|
+
function renderPrecedence() {
|
|
2657
|
+
return `## Precedencia: Voz vs Adaptacao
|
|
2550
2658
|
|
|
2551
|
-
|
|
2659
|
+
Voce tem DUAS fontes que te orientam, e elas resolvem conflitos assim:
|
|
2552
2660
|
|
|
2553
|
-
|
|
2661
|
+
| Fonte | Governa | Exemplo |
|
|
2662
|
+
| ----------------------------- | ----------------------------- | ---------------------------------------------- |
|
|
2663
|
+
| Sua **persona** (acima) | voz, tom, nome, expressoes | usar catchphrase do creator ao cumprimentar |
|
|
2664
|
+
| **Contexto do aluno** (acima) | profundidade, ritmo, exemplos | "vou comecar pelo porque" (iniciante) |
|
|
2554
2665
|
|
|
2555
|
-
**
|
|
2666
|
+
**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.`;
|
|
2667
|
+
}
|
|
2668
|
+
function renderWhereStudentIs(progress3) {
|
|
2669
|
+
const lines = [
|
|
2670
|
+
`- ${progress3.percent}% completo | ${progress3.moduleCount} modulos | ${progress3.lessonCount} licoes`
|
|
2671
|
+
];
|
|
2672
|
+
if (progress3.currentModuleTitle) lines.push(`- **Modulo atual:** ${progress3.currentModuleTitle}`);
|
|
2673
|
+
if (progress3.currentLessonTitle) lines.push(`- **Licao atual:** ${progress3.currentLessonTitle}`);
|
|
2674
|
+
return `## Onde o Aluno Esta
|
|
2556
2675
|
|
|
2557
|
-
|
|
2676
|
+
${lines.join("\n")}`;
|
|
2677
|
+
}
|
|
2678
|
+
function renderHowToConduct() {
|
|
2679
|
+
return `## Como Conduzir a Aula
|
|
2558
2680
|
|
|
2559
|
-
|
|
2681
|
+
Quando o aluno comeca uma conversa:
|
|
2560
2682
|
|
|
2561
|
-
|
|
2683
|
+
1. Cumprimente ele pelo nome/contexto (use o brief base). Breve \u2014 1 frase.
|
|
2684
|
+
2. Descubra onde ele parou (\`tostudy progress --json\` em silencio).
|
|
2685
|
+
3. Resuma o estado em uma frase: "Voce esta no Modulo X, Licao Y \u2014 [titulo]".
|
|
2686
|
+
4. Pergunte se ele quer continuar ou revisar.
|
|
2562
2687
|
|
|
2563
|
-
|
|
2564
|
-
2. **Hints primeiro** \u2014 sempre rode \`tostudy hint\` antes de explicar a solu\xE7\xE3o
|
|
2565
|
-
3. **Valide antes de avan\xE7ar** \u2014 exerc\xEDcios exigem \`tostudy validate\` com aprova\xE7\xE3o
|
|
2566
|
-
4. **Celebre progresso** \u2014 reconhe\xE7a quando o aluno completa li\xE7\xF5es e m\xF3dulos
|
|
2567
|
-
5. **Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es
|
|
2568
|
-
6. **Adapte ao n\xEDvel** \u2014 ajuste profundidade conforme o perfil do aluno
|
|
2569
|
-
7. **Sempre rode \`tostudy lesson\`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material
|
|
2570
|
-
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.
|
|
2688
|
+
Quando o aluno quer estudar uma licao:
|
|
2571
2689
|
|
|
2572
|
-
|
|
2690
|
+
1. Carregue o conteudo em silencio (\`tostudy lesson --json\`).
|
|
2691
|
+
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.
|
|
2692
|
+
3. Se for **texto/teoria**: explique os conceitos. Use perguntas socraticas ("O que voce acha que aconteceria se...?"). So avance quando ele demonstrar entendimento.
|
|
2693
|
+
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.
|
|
2694
|
+
5. Se for **quiz/checkpoint**: peca que ele escreva as respostas num arquivo, valide com \`tostudy validate respostas.md\`, discuta.
|
|
2695
|
+
6. Se for **video**: resuma os pontos-chave, aguarde, depois discuta.
|
|
2573
2696
|
|
|
2574
|
-
|
|
2575
|
-
|----------|-------------|
|
|
2576
|
-
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2577
|
-
| \`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. |
|
|
2578
|
-
| "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
|
|
2579
|
-
| Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
|
|
2580
|
-
| Aluno perdido | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2581
|
-
| "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |
|
|
2582
|
-
|
|
2583
|
-
## Workspace
|
|
2584
|
-
|
|
2585
|
-
Quando o aluno precisar de um workspace local para exerc\xEDcios:
|
|
2586
|
-
|
|
2587
|
-
- \`tostudy workspace setup\` \u2014 Cria diret\xF3rios em ~/study/{slug}/
|
|
2588
|
-
- \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace
|
|
2589
|
-
- \`tostudy open\` \u2014 Abre workspace no editor
|
|
2590
|
-
- \`tostudy workspace status\` \u2014 Verifica estado
|
|
2697
|
+
Quando o aluno passou na licao:
|
|
2591
2698
|
|
|
2592
|
-
|
|
2699
|
+
- Celebre (brevemente).
|
|
2700
|
+
- Pergunte se ele quer seguir ou pausar.
|
|
2701
|
+
- Ao seguir \u2014 \`tostudy next\` (silencio) \u2014 apresente a proxima.`;
|
|
2702
|
+
}
|
|
2703
|
+
function renderSilentTools() {
|
|
2704
|
+
return `## Ferramentas Silenciosas
|
|
2593
2705
|
|
|
2594
|
-
|
|
2595
|
-
|---------|-------------|
|
|
2596
|
-
| \`tostudy progress\` | No in\xEDcio da sess\xE3o e quando perguntar "onde estou?" |
|
|
2597
|
-
| \`tostudy courses\` | Para listar cursos matriculados |
|
|
2598
|
-
| \`tostudy select <n>\` | Para ativar um curso |
|
|
2599
|
-
| \`tostudy start\` | Para carregar o m\xF3dulo atual ou pr\xF3ximo |
|
|
2600
|
-
| \`tostudy lesson\` | Antes de discutir qualquer conte\xFAdo |
|
|
2601
|
-
| \`tostudy next\` | Ap\xF3s completar uma li\xE7\xE3o |
|
|
2602
|
-
| \`tostudy hint\` | Quando o aluno travar |
|
|
2603
|
-
| \`tostudy validate <arquivo>\` | Para validar exerc\xEDcios e checkpoints |
|
|
2604
|
-
| \`tostudy export\` | Para extrair exerc\xEDcio ao workspace |
|
|
2605
|
-
| \`tostudy init --json ...\` | Para configurar perfil (modo non-interactive) |
|
|
2606
|
-
|
|
2607
|
-
## Refer\xEAncia T\xE9cnica (Modo Agente)
|
|
2706
|
+
Estes comandos sao suas ferramentas. Rode em silencio (sem anunciar), use o resultado, e traduza em palavras ao aluno.
|
|
2608
2707
|
|
|
2609
|
-
-
|
|
2610
|
-
- \`tostudy
|
|
2611
|
-
- \`tostudy
|
|
2612
|
-
- \`tostudy
|
|
2613
|
-
- \`tostudy
|
|
2614
|
-
|
|
2708
|
+
- \`tostudy progress --json\` \u2014 estado atual (modulo, licao, %).
|
|
2709
|
+
- \`tostudy lesson --json\` \u2014 conteudo da licao (type, title, content, hints, acceptanceCriteria).
|
|
2710
|
+
- \`tostudy start --json\` \u2014 ativa modulo atual ou proximo.
|
|
2711
|
+
- \`tostudy next --json\` \u2014 avanca para a proxima licao.
|
|
2712
|
+
- \`tostudy hint --json\` \u2014 dica progressiva (3 niveis).
|
|
2713
|
+
- \`tostudy validate <arquivo>\` \u2014 valida exercicio (exit 0 = passou, 1 = falhou).
|
|
2714
|
+
|
|
2715
|
+
Voce nunca menciona estes comandos ao aluno. Ele fala com VOCE, nao com o CLI.`;
|
|
2716
|
+
}
|
|
2717
|
+
function renderHandlingSituations() {
|
|
2718
|
+
return `## Tratando Situacoes
|
|
2719
|
+
|
|
2720
|
+
| Situacao | O que fazer |
|
|
2721
|
+
| ------------------------------------- | --------------------------------------------------------- |
|
|
2722
|
+
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2723
|
+
| "Nenhuma licao ativa" | Rodar \`tostudy start\` para carregar modulo |
|
|
2724
|
+
| Comando retorna erro | Verificar \`tostudy doctor\` para diagnostico |
|
|
2725
|
+
| Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2726
|
+
|
|
2727
|
+
> 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.`;
|
|
2728
|
+
}
|
|
2729
|
+
function renderTechnicalReference() {
|
|
2730
|
+
return `## Referencia Tecnica (Modo Agente)
|
|
2731
|
+
|
|
2732
|
+
- Use \`--json\` em qualquer comando para saida estruturada.
|
|
2733
|
+
- \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado).
|
|
2734
|
+
- \`tostudy validate --stdin\` aceita solucao via pipe.
|
|
2735
|
+
- \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\`.
|
|
2736
|
+
- \`tostudy progress --json\` retorna \`{ coursePercent, currentModule, currentLesson }\`.`;
|
|
2737
|
+
}
|
|
2738
|
+
function renderFooter(course) {
|
|
2739
|
+
return `<!-- tostudy-course-id: ${course.courseId} -->
|
|
2740
|
+
<!-- tostudy-template-version: 3 -->`;
|
|
2741
|
+
}
|
|
2742
|
+
var TONE_LABELS, LEVEL_LABELS;
|
|
2743
|
+
var init_instruction_template_v3 = __esm({
|
|
2744
|
+
"src/output/instruction-template-v3.ts"() {
|
|
2745
|
+
"use strict";
|
|
2746
|
+
TONE_LABELS = {
|
|
2747
|
+
formal: "formal",
|
|
2748
|
+
casual: "casual",
|
|
2749
|
+
motivational: "motivacional",
|
|
2750
|
+
technical: "tecnico",
|
|
2751
|
+
relaxed: "descontraido"
|
|
2752
|
+
};
|
|
2753
|
+
LEVEL_LABELS = {
|
|
2754
|
+
beginner: "iniciante",
|
|
2755
|
+
intermediate: "intermediario",
|
|
2756
|
+
advanced: "avancado"
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
});
|
|
2760
|
+
|
|
2761
|
+
// src/workspace/instruction-files.ts
|
|
2762
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2763
|
+
import { join as join2 } from "node:path";
|
|
2764
|
+
function slugify(title) {
|
|
2765
|
+
return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
2766
|
+
}
|
|
2767
|
+
function writeInstructionFiles(cwd, ctx, content) {
|
|
2768
|
+
const slug = slugify(ctx.courseTitle);
|
|
2769
|
+
const written = [];
|
|
2770
|
+
const claudeDir = join2(cwd, ".claude", "commands");
|
|
2771
|
+
if (!existsSync3(claudeDir)) {
|
|
2772
|
+
mkdirSync3(claudeDir, { recursive: true });
|
|
2773
|
+
}
|
|
2774
|
+
const claudeFile = `tostudy-${slug}.md`;
|
|
2775
|
+
writeFileSync3(join2(claudeDir, claudeFile), content);
|
|
2776
|
+
written.push(`.claude/commands/${claudeFile}`);
|
|
2777
|
+
const cursorDir = join2(cwd, ".cursor", "rules");
|
|
2778
|
+
if (existsSync3(join2(cwd, ".cursor")) || existsSync3(cursorDir)) {
|
|
2779
|
+
if (!existsSync3(cursorDir)) {
|
|
2780
|
+
mkdirSync3(cursorDir, { recursive: true });
|
|
2781
|
+
}
|
|
2782
|
+
const mdcContent = `---
|
|
2783
|
+
description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
|
|
2784
|
+
globs: ["**/*"]
|
|
2785
|
+
alwaysApply: true
|
|
2786
|
+
---
|
|
2787
|
+
|
|
2788
|
+
${content}`;
|
|
2789
|
+
const cursorFile = `tostudy-${slug}.mdc`;
|
|
2790
|
+
writeFileSync3(join2(cursorDir, cursorFile), mdcContent);
|
|
2791
|
+
written.push(`.cursor/rules/${cursorFile}`);
|
|
2792
|
+
}
|
|
2793
|
+
const courseDir = join2(cwd, ".tostudy", "courses", slug);
|
|
2794
|
+
if (!existsSync3(courseDir)) {
|
|
2795
|
+
mkdirSync3(courseDir, { recursive: true });
|
|
2796
|
+
}
|
|
2797
|
+
writeFileSync3(join2(courseDir, "AGENTS.md"), content);
|
|
2798
|
+
written.push(`.tostudy/courses/${slug}/AGENTS.md`);
|
|
2799
|
+
logger3.info("Generated instruction files (v3)", {
|
|
2800
|
+
slug,
|
|
2801
|
+
files: written
|
|
2802
|
+
});
|
|
2803
|
+
return written;
|
|
2615
2804
|
}
|
|
2616
|
-
function installUniversalCommand(
|
|
2617
|
-
const content =
|
|
2805
|
+
function installUniversalCommand(platform2, cwd = process.cwd()) {
|
|
2806
|
+
const content = renderUniversalInstruction();
|
|
2618
2807
|
const written = [];
|
|
2619
|
-
if (
|
|
2808
|
+
if (platform2 === "claude" || platform2 === "codex") {
|
|
2620
2809
|
const dir = join2(cwd, ".claude", "commands");
|
|
2621
|
-
if (!
|
|
2622
|
-
|
|
2810
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2811
|
+
writeFileSync3(join2(dir, "tostudy.md"), content);
|
|
2623
2812
|
written.push(".claude/commands/tostudy.md");
|
|
2624
2813
|
}
|
|
2625
|
-
if (
|
|
2814
|
+
if (platform2 === "cursor") {
|
|
2626
2815
|
const dir = join2(cwd, ".cursor", "rules");
|
|
2627
|
-
if (!
|
|
2816
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2628
2817
|
const mdcContent = `---
|
|
2629
2818
|
description: ToStudy \u2014 AI Tutor Guide
|
|
2630
2819
|
globs: ["**/*"]
|
|
@@ -2632,16 +2821,16 @@ alwaysApply: true
|
|
|
2632
2821
|
---
|
|
2633
2822
|
|
|
2634
2823
|
${content}`;
|
|
2635
|
-
|
|
2824
|
+
writeFileSync3(join2(dir, "tostudy.mdc"), mdcContent);
|
|
2636
2825
|
written.push(".cursor/rules/tostudy.mdc");
|
|
2637
2826
|
}
|
|
2638
|
-
if (
|
|
2827
|
+
if (platform2 === "generic") {
|
|
2639
2828
|
const dir = join2(cwd, ".tostudy");
|
|
2640
|
-
if (!
|
|
2641
|
-
|
|
2829
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2830
|
+
writeFileSync3(join2(dir, "AGENTS.md"), content);
|
|
2642
2831
|
written.push(".tostudy/AGENTS.md");
|
|
2643
2832
|
}
|
|
2644
|
-
logger3.info("Installed universal /tostudy command", { platform, files: written });
|
|
2833
|
+
logger3.info("Installed universal /tostudy command", { platform: platform2, files: written });
|
|
2645
2834
|
return written;
|
|
2646
2835
|
}
|
|
2647
2836
|
var logger3;
|
|
@@ -2649,13 +2838,14 @@ var init_instruction_files = __esm({
|
|
|
2649
2838
|
"src/workspace/instruction-files.ts"() {
|
|
2650
2839
|
"use strict";
|
|
2651
2840
|
init_src();
|
|
2841
|
+
init_instruction_template_v3();
|
|
2652
2842
|
logger3 = createLogger("cli:instruction-files");
|
|
2653
2843
|
}
|
|
2654
2844
|
});
|
|
2655
2845
|
|
|
2656
2846
|
// src/commands/setup.ts
|
|
2657
2847
|
import { Command as Command3 } from "commander";
|
|
2658
|
-
import { existsSync as
|
|
2848
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2659
2849
|
import { join as join3 } from "node:path";
|
|
2660
2850
|
function ideToPlatform(ideName) {
|
|
2661
2851
|
const lower = ideName.toLowerCase();
|
|
@@ -2715,15 +2905,15 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2715
2905
|
const cwd = process.cwd();
|
|
2716
2906
|
const installedPlatforms = [];
|
|
2717
2907
|
for (const ide of detected) {
|
|
2718
|
-
const
|
|
2719
|
-
if (
|
|
2720
|
-
const files = deps.installUniversalCommand(
|
|
2908
|
+
const platform2 = ideToPlatform(ide.name);
|
|
2909
|
+
if (platform2) {
|
|
2910
|
+
const files = deps.installUniversalCommand(platform2, cwd);
|
|
2721
2911
|
installedPlatforms.push(...files);
|
|
2722
2912
|
}
|
|
2723
2913
|
}
|
|
2724
2914
|
if (opts.ide) {
|
|
2725
|
-
const
|
|
2726
|
-
const files = deps.installUniversalCommand(
|
|
2915
|
+
const platform2 = ideToPlatform(opts.ide) ?? opts.ide;
|
|
2916
|
+
const files = deps.installUniversalCommand(platform2, cwd);
|
|
2727
2917
|
installedPlatforms.push(...files);
|
|
2728
2918
|
}
|
|
2729
2919
|
if (installedPlatforms.length === 0) {
|
|
@@ -2745,12 +2935,16 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2745
2935
|
}
|
|
2746
2936
|
}
|
|
2747
2937
|
const activeCourse = await deps.getActiveCourse();
|
|
2748
|
-
const hasClaudeCmd =
|
|
2749
|
-
const hasCursorRule =
|
|
2938
|
+
const hasClaudeCmd = existsSync4(join3(cwd, ".claude", "commands", "tostudy.md"));
|
|
2939
|
+
const hasCursorRule = existsSync4(join3(cwd, ".cursor", "rules", "tostudy.mdc"));
|
|
2940
|
+
const hasMcpConfig = existsSync4(join3(cwd, ".claude", "claude_desktop_config.json")) || existsSync4(join3(cwd, ".mcp.json"));
|
|
2941
|
+
const hasClaudeIDE = detected.some((ide) => ide.name.toLowerCase().includes("claude"));
|
|
2750
2942
|
deps.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2751
2943
|
deps.log(" Setup completo!\n");
|
|
2752
2944
|
if (activeCourse) {
|
|
2753
2945
|
deps.log(` Curso ativo: ${activeCourse.courseTitle}`);
|
|
2946
|
+
} else {
|
|
2947
|
+
deps.log(" Nenhum curso ativo \u2014 rode: tostudy courses");
|
|
2754
2948
|
}
|
|
2755
2949
|
if (hasClaudeCmd) {
|
|
2756
2950
|
deps.log(" \u2192 No Claude Code, digite: /tostudy");
|
|
@@ -2759,6 +2953,9 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2759
2953
|
} else {
|
|
2760
2954
|
deps.log(" \u2192 Abra seu IDE e o tutor estar\xE1 dispon\xEDvel");
|
|
2761
2955
|
}
|
|
2956
|
+
if (hasClaudeIDE && !hasMcpConfig && !opts.mcp) {
|
|
2957
|
+
deps.log("\n \u{1F4A1} Dica: rode tostudy setup --mcp para habilitar ferramentas avan\xE7adas");
|
|
2958
|
+
}
|
|
2762
2959
|
deps.log("");
|
|
2763
2960
|
}
|
|
2764
2961
|
async function runSetupMcpSubcommand() {
|
|
@@ -2811,20 +3008,20 @@ __export(update_checker_exports, {
|
|
|
2811
3008
|
fetchLatestVersion: () => fetchLatestVersion,
|
|
2812
3009
|
isNewerVersion: () => isNewerVersion
|
|
2813
3010
|
});
|
|
2814
|
-
import
|
|
2815
|
-
import
|
|
2816
|
-
import
|
|
3011
|
+
import fs5 from "node:fs";
|
|
3012
|
+
import path6 from "node:path";
|
|
3013
|
+
import os6 from "node:os";
|
|
2817
3014
|
function getConfigDir2() {
|
|
2818
3015
|
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
2819
|
-
return
|
|
3016
|
+
return path6.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
2820
3017
|
}
|
|
2821
|
-
return
|
|
3018
|
+
return path6.join(os6.homedir(), ".tostudy");
|
|
2822
3019
|
}
|
|
2823
3020
|
function readCache() {
|
|
2824
3021
|
try {
|
|
2825
|
-
const p =
|
|
2826
|
-
if (!
|
|
2827
|
-
return JSON.parse(
|
|
3022
|
+
const p = path6.join(getConfigDir2(), CACHE_FILE);
|
|
3023
|
+
if (!fs5.existsSync(p)) return null;
|
|
3024
|
+
return JSON.parse(fs5.readFileSync(p, "utf-8"));
|
|
2828
3025
|
} catch {
|
|
2829
3026
|
return null;
|
|
2830
3027
|
}
|
|
@@ -2832,8 +3029,8 @@ function readCache() {
|
|
|
2832
3029
|
function writeCache(cache) {
|
|
2833
3030
|
try {
|
|
2834
3031
|
const dir = getConfigDir2();
|
|
2835
|
-
|
|
2836
|
-
|
|
3032
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
3033
|
+
fs5.writeFileSync(path6.join(dir, CACHE_FILE), JSON.stringify(cache));
|
|
2837
3034
|
} catch {
|
|
2838
3035
|
}
|
|
2839
3036
|
}
|
|
@@ -2901,6 +3098,84 @@ var init_update_checker = __esm({
|
|
|
2901
3098
|
}
|
|
2902
3099
|
});
|
|
2903
3100
|
|
|
3101
|
+
// src/learner-brief/cache.ts
|
|
3102
|
+
import fs6 from "node:fs";
|
|
3103
|
+
import os7 from "node:os";
|
|
3104
|
+
import path7 from "node:path";
|
|
3105
|
+
function getDefaultConfigDir() {
|
|
3106
|
+
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
3107
|
+
return path7.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
3108
|
+
}
|
|
3109
|
+
return path7.join(os7.homedir(), ".tostudy");
|
|
3110
|
+
}
|
|
3111
|
+
function resolveCachePath(configDir) {
|
|
3112
|
+
return path7.join(configDir, "student-brief.json");
|
|
3113
|
+
}
|
|
3114
|
+
async function readBriefCache(configDir) {
|
|
3115
|
+
const dir = configDir ?? getDefaultConfigDir();
|
|
3116
|
+
const p = resolveCachePath(dir);
|
|
3117
|
+
if (!fs6.existsSync(p)) return null;
|
|
3118
|
+
try {
|
|
3119
|
+
const raw = fs6.readFileSync(p, "utf-8");
|
|
3120
|
+
return JSON.parse(raw);
|
|
3121
|
+
} catch {
|
|
3122
|
+
return null;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
async function writeBriefCache(configDir, cache) {
|
|
3126
|
+
const dir = configDir ?? getDefaultConfigDir();
|
|
3127
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
3128
|
+
fs6.writeFileSync(resolveCachePath(dir), JSON.stringify(cache, null, 2), { mode: 384 });
|
|
3129
|
+
}
|
|
3130
|
+
var init_cache = __esm({
|
|
3131
|
+
"src/learner-brief/cache.ts"() {
|
|
3132
|
+
"use strict";
|
|
3133
|
+
}
|
|
3134
|
+
});
|
|
3135
|
+
|
|
3136
|
+
// src/tutor-persona/cache.ts
|
|
3137
|
+
import fs7 from "node:fs";
|
|
3138
|
+
import os8 from "node:os";
|
|
3139
|
+
import path8 from "node:path";
|
|
3140
|
+
function getDefaultConfigDir2() {
|
|
3141
|
+
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
3142
|
+
return path8.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
3143
|
+
}
|
|
3144
|
+
return path8.join(os8.homedir(), ".tostudy");
|
|
3145
|
+
}
|
|
3146
|
+
function resolveCachePath2(configDir) {
|
|
3147
|
+
return path8.join(configDir, "tutor-personalities.json");
|
|
3148
|
+
}
|
|
3149
|
+
function readAll(configDir) {
|
|
3150
|
+
const p = resolveCachePath2(configDir);
|
|
3151
|
+
if (!fs7.existsSync(p)) return {};
|
|
3152
|
+
try {
|
|
3153
|
+
return JSON.parse(fs7.readFileSync(p, "utf-8"));
|
|
3154
|
+
} catch {
|
|
3155
|
+
return {};
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
function writeAll(configDir, cache) {
|
|
3159
|
+
fs7.mkdirSync(configDir, { recursive: true });
|
|
3160
|
+
fs7.writeFileSync(resolveCachePath2(configDir), JSON.stringify(cache, null, 2), { mode: 384 });
|
|
3161
|
+
}
|
|
3162
|
+
async function readPersonaCacheForCourse(configDir, courseId) {
|
|
3163
|
+
const dir = configDir ?? getDefaultConfigDir2();
|
|
3164
|
+
const all = readAll(dir);
|
|
3165
|
+
return all[courseId] ?? null;
|
|
3166
|
+
}
|
|
3167
|
+
async function writePersonaCacheForCourse(configDir, courseId, entry) {
|
|
3168
|
+
const dir = configDir ?? getDefaultConfigDir2();
|
|
3169
|
+
const all = readAll(dir);
|
|
3170
|
+
all[courseId] = entry;
|
|
3171
|
+
writeAll(dir, all);
|
|
3172
|
+
}
|
|
3173
|
+
var init_cache2 = __esm({
|
|
3174
|
+
"src/tutor-persona/cache.ts"() {
|
|
3175
|
+
"use strict";
|
|
3176
|
+
}
|
|
3177
|
+
});
|
|
3178
|
+
|
|
2904
3179
|
// src/commands/doctor.ts
|
|
2905
3180
|
import { Command as Command4 } from "commander";
|
|
2906
3181
|
var doctorCommand;
|
|
@@ -2913,6 +3188,9 @@ var init_doctor = __esm({
|
|
|
2913
3188
|
init_formatter();
|
|
2914
3189
|
init_cli();
|
|
2915
3190
|
init_update_checker();
|
|
3191
|
+
init_cache();
|
|
3192
|
+
init_cache2();
|
|
3193
|
+
init_workspace_marker();
|
|
2916
3194
|
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) => {
|
|
2917
3195
|
const checks = {};
|
|
2918
3196
|
const node = detectNode();
|
|
@@ -2963,6 +3241,68 @@ var init_doctor = __esm({
|
|
|
2963
3241
|
} catch {
|
|
2964
3242
|
}
|
|
2965
3243
|
checks["ides"] = ides.filter((ide) => ide.detected);
|
|
3244
|
+
let workspaceMarkerCheck;
|
|
3245
|
+
try {
|
|
3246
|
+
const marker = await readWorkspaceMarker(process.cwd());
|
|
3247
|
+
workspaceMarkerCheck = marker ? {
|
|
3248
|
+
status: "found",
|
|
3249
|
+
courseId: marker.courseId,
|
|
3250
|
+
slug: marker.slug,
|
|
3251
|
+
courseTitle: marker.courseTitle
|
|
3252
|
+
} : { status: "absent" };
|
|
3253
|
+
} catch (err) {
|
|
3254
|
+
workspaceMarkerCheck = {
|
|
3255
|
+
status: "error",
|
|
3256
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
checks["workspaceMarker"] = workspaceMarkerCheck;
|
|
3260
|
+
let briefCacheCheck;
|
|
3261
|
+
try {
|
|
3262
|
+
const briefCache = await readBriefCache();
|
|
3263
|
+
if (briefCache?.brief) {
|
|
3264
|
+
const ageMs = Date.now() - new Date(briefCache.fetchedAt).getTime();
|
|
3265
|
+
const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
|
|
3266
|
+
briefCacheCheck = { status: "cached", ageDays, stale: ageDays > 7 };
|
|
3267
|
+
} else if (briefCache?.skipped) {
|
|
3268
|
+
briefCacheCheck = { status: "skipped" };
|
|
3269
|
+
} else {
|
|
3270
|
+
briefCacheCheck = { status: "missing" };
|
|
3271
|
+
}
|
|
3272
|
+
} catch (err) {
|
|
3273
|
+
briefCacheCheck = {
|
|
3274
|
+
status: "error",
|
|
3275
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
checks["briefCache"] = briefCacheCheck;
|
|
3279
|
+
let personaCacheCheck;
|
|
3280
|
+
try {
|
|
3281
|
+
const active = await getActiveCourse();
|
|
3282
|
+
if (active) {
|
|
3283
|
+
const personaEntry = await readPersonaCacheForCourse(void 0, active.courseId);
|
|
3284
|
+
if (personaEntry) {
|
|
3285
|
+
const ageMs = Date.now() - new Date(personaEntry.fetchedAt).getTime();
|
|
3286
|
+
const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
|
|
3287
|
+
personaCacheCheck = {
|
|
3288
|
+
status: "cached",
|
|
3289
|
+
courseTitle: active.courseTitle,
|
|
3290
|
+
ageDays,
|
|
3291
|
+
stale: ageDays > 7
|
|
3292
|
+
};
|
|
3293
|
+
} else {
|
|
3294
|
+
personaCacheCheck = { status: "missing", courseTitle: active.courseTitle };
|
|
3295
|
+
}
|
|
3296
|
+
} else {
|
|
3297
|
+
personaCacheCheck = { status: "no-active-course" };
|
|
3298
|
+
}
|
|
3299
|
+
} catch (err) {
|
|
3300
|
+
personaCacheCheck = {
|
|
3301
|
+
status: "error",
|
|
3302
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
checks["personaCache"] = personaCacheCheck;
|
|
2966
3306
|
if (opts.json) {
|
|
2967
3307
|
output(checks, { json: true });
|
|
2968
3308
|
return;
|
|
@@ -3008,6 +3348,38 @@ var init_doctor = __esm({
|
|
|
3008
3348
|
` ${ide.detected ? "\u2713" : "\u25CB"} ${ide.name.padEnd(14)} ${ide.detected ? "detectado" : "n\xE3o detectado"}`
|
|
3009
3349
|
);
|
|
3010
3350
|
}
|
|
3351
|
+
console.log("\n Contexto de estudo");
|
|
3352
|
+
if (workspaceMarkerCheck.status === "found") {
|
|
3353
|
+
console.log(
|
|
3354
|
+
` \u2713 Workspace ${workspaceMarkerCheck.courseTitle} (${workspaceMarkerCheck.slug})`
|
|
3355
|
+
);
|
|
3356
|
+
} else if (workspaceMarkerCheck.status === "absent") {
|
|
3357
|
+
console.log(" \u25CB Workspace sem marker no diret\xF3rio atual (usando estado global)");
|
|
3358
|
+
} else {
|
|
3359
|
+
console.log(` \u2717 Workspace erro ao ler marker: ${workspaceMarkerCheck.message}`);
|
|
3360
|
+
}
|
|
3361
|
+
if (briefCacheCheck.status === "cached") {
|
|
3362
|
+
const warn = briefCacheCheck.stale ? " (desatualizado \u2014 rode `tostudy sync`)" : "";
|
|
3363
|
+
console.log(` \u2713 Brief (T1) cache com ${briefCacheCheck.ageDays}d${warn}`);
|
|
3364
|
+
} else if (briefCacheCheck.status === "skipped") {
|
|
3365
|
+
console.log(" \u25CB Brief (T1) ignorado pelo usu\xE1rio");
|
|
3366
|
+
} else if (briefCacheCheck.status === "missing") {
|
|
3367
|
+
console.log(" \u25CB Brief (T1) n\xE3o criado \u2014 rode `tostudy brief-create`");
|
|
3368
|
+
} else {
|
|
3369
|
+
console.log(` \u2717 Brief (T1) erro: ${briefCacheCheck.message}`);
|
|
3370
|
+
}
|
|
3371
|
+
if (personaCacheCheck.status === "cached") {
|
|
3372
|
+
const warn = personaCacheCheck.stale ? " (desatualizado)" : "";
|
|
3373
|
+
console.log(
|
|
3374
|
+
` \u2713 Persona (T0) cache para "${personaCacheCheck.courseTitle}" (${personaCacheCheck.ageDays}d)${warn}`
|
|
3375
|
+
);
|
|
3376
|
+
} else if (personaCacheCheck.status === "missing") {
|
|
3377
|
+
console.log(` \u25CB Persona (T0) sem cache para "${personaCacheCheck.courseTitle}"`);
|
|
3378
|
+
} else if (personaCacheCheck.status === "no-active-course") {
|
|
3379
|
+
console.log(" \u25CB Persona (T0) nenhum curso ativo");
|
|
3380
|
+
} else {
|
|
3381
|
+
console.log(` \u2717 Persona (T0) erro: ${personaCacheCheck.message}`);
|
|
3382
|
+
}
|
|
3011
3383
|
console.log("");
|
|
3012
3384
|
});
|
|
3013
3385
|
}
|
|
@@ -3044,9 +3416,405 @@ var init_courses2 = __esm({
|
|
|
3044
3416
|
}
|
|
3045
3417
|
});
|
|
3046
3418
|
|
|
3419
|
+
// src/onboarding/status.ts
|
|
3420
|
+
async function getCourseOnboardingStatus(activeCourse, configDir, cwd = process.cwd()) {
|
|
3421
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
|
|
3422
|
+
const initReady = Boolean(onboardingState?.initCompletedAt);
|
|
3423
|
+
const ws = await resolveEffectiveWorkspace(
|
|
3424
|
+
activeCourse.courseTitle,
|
|
3425
|
+
onboardingState?.workspacePath,
|
|
3426
|
+
cwd
|
|
3427
|
+
);
|
|
3428
|
+
return {
|
|
3429
|
+
initReady,
|
|
3430
|
+
workspaceReady: ws.found,
|
|
3431
|
+
workspacePath: ws.workspacePath,
|
|
3432
|
+
workspaceSource: ws.source
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
var init_status = __esm({
|
|
3436
|
+
"src/onboarding/status.ts"() {
|
|
3437
|
+
"use strict";
|
|
3438
|
+
init_session();
|
|
3439
|
+
init_resolve();
|
|
3440
|
+
}
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3443
|
+
// src/learner-brief/api.ts
|
|
3444
|
+
async function apiFetch3(url2, token2, init) {
|
|
3445
|
+
const response = await fetch(url2, {
|
|
3446
|
+
method: init?.method ?? "GET",
|
|
3447
|
+
body: init?.body,
|
|
3448
|
+
headers: {
|
|
3449
|
+
"Content-Type": "application/json",
|
|
3450
|
+
Authorization: `Bearer ${token2}`,
|
|
3451
|
+
...init?.headers ?? {}
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
const body = await response.json();
|
|
3455
|
+
if (!response.ok) {
|
|
3456
|
+
throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
|
|
3457
|
+
}
|
|
3458
|
+
return body;
|
|
3459
|
+
}
|
|
3460
|
+
async function fetchLearnerBrief(input2) {
|
|
3461
|
+
const response = await apiFetch3(
|
|
3462
|
+
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
3463
|
+
input2.token
|
|
3464
|
+
);
|
|
3465
|
+
return response.brief;
|
|
3466
|
+
}
|
|
3467
|
+
async function upsertLearnerBrief(input2) {
|
|
3468
|
+
const response = await apiFetch3(
|
|
3469
|
+
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
3470
|
+
input2.token,
|
|
3471
|
+
{
|
|
3472
|
+
method: "POST",
|
|
3473
|
+
body: JSON.stringify({ text: input2.text, source: input2.source })
|
|
3474
|
+
}
|
|
3475
|
+
);
|
|
3476
|
+
return response.brief;
|
|
3477
|
+
}
|
|
3478
|
+
var init_api2 = __esm({
|
|
3479
|
+
"src/learner-brief/api.ts"() {
|
|
3480
|
+
"use strict";
|
|
3481
|
+
init_http2();
|
|
3482
|
+
}
|
|
3483
|
+
});
|
|
3484
|
+
|
|
3485
|
+
// src/learner-brief/bootstrap.ts
|
|
3486
|
+
import readline from "node:readline/promises";
|
|
3487
|
+
import { stdin as processStdin, stdout as processStdout } from "node:process";
|
|
3488
|
+
function composeBriefFromAnswers(answers) {
|
|
3489
|
+
const levelLabel = {
|
|
3490
|
+
beginner: "iniciante",
|
|
3491
|
+
intermediate: "intermediario",
|
|
3492
|
+
advanced: "avancado"
|
|
3493
|
+
}[answers.yourLevel];
|
|
3494
|
+
const paragraphOne = [answers.whoYouAre, answers.whereYouWork, answers.whatYouDo].map((s) => s.trim()).filter(Boolean).join(". ");
|
|
3495
|
+
const paragraphTwo = `Meu nivel neste assunto e ${levelLabel}. ${answers.yourGoals.trim()}`;
|
|
3496
|
+
const paragraphThree = `Contexto real do meu dia a dia: ${answers.realContext.trim()}`;
|
|
3497
|
+
const text2 = [paragraphOne, paragraphTwo, paragraphThree].map((p) => p.replace(/\.\.+/g, ".")).join("\n\n");
|
|
3498
|
+
return text2.slice(0, 5e3);
|
|
3499
|
+
}
|
|
3500
|
+
async function askNonEmpty(question, deps) {
|
|
3501
|
+
while (true) {
|
|
3502
|
+
const answer = (await deps.ask(question)).trim();
|
|
3503
|
+
if (answer.length > 0) return answer;
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
async function askLevel(deps) {
|
|
3507
|
+
while (true) {
|
|
3508
|
+
const raw = (await deps.ask(" Seu nivel geral neste assunto (beginner/intermediate/advanced): ")).trim().toLowerCase();
|
|
3509
|
+
if (raw === "beginner" || raw === "intermediate" || raw === "advanced") {
|
|
3510
|
+
return raw;
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
async function collectBootstrapAnswersWithDeps(input2, deps) {
|
|
3515
|
+
deps.write(
|
|
3516
|
+
[
|
|
3517
|
+
"",
|
|
3518
|
+
`Ola ${input2.userName}, vamos criar seu brief base (1-2 minutos).`,
|
|
3519
|
+
"Ele ajuda o tutor a te conhecer para adaptar o ensino a voce.",
|
|
3520
|
+
"Voce pode editar depois em https://tostudy.ai/student/settings/learner-brief.",
|
|
3521
|
+
""
|
|
3522
|
+
].join("\n")
|
|
3523
|
+
);
|
|
3524
|
+
const whoYouAre = await askNonEmpty(" Quem voce e (profissao/cargo/area): ", deps);
|
|
3525
|
+
const whereYouWork = await askNonEmpty(" Onde trabalha (empresa/setor): ", deps);
|
|
3526
|
+
const whatYouDo = await askNonEmpty(" O que voce faz (responsabilidades/projetos): ", deps);
|
|
3527
|
+
const yourLevel = await askLevel(deps);
|
|
3528
|
+
const yourGoals = await askNonEmpty(" Seu objetivo de aprendizado: ", deps);
|
|
3529
|
+
const realContext = await askNonEmpty(
|
|
3530
|
+
" Contexto real (exemplos que o tutor pode usar nas aulas): ",
|
|
3531
|
+
deps
|
|
3532
|
+
);
|
|
3533
|
+
return { whoYouAre, whereYouWork, whatYouDo, yourLevel, yourGoals, realContext };
|
|
3534
|
+
}
|
|
3535
|
+
async function collectBootstrapAnswers(input2) {
|
|
3536
|
+
const rl = readline.createInterface({ input: processStdin, output: processStdout });
|
|
3537
|
+
try {
|
|
3538
|
+
return await collectBootstrapAnswersWithDeps(input2, {
|
|
3539
|
+
ask: (q) => rl.question(q),
|
|
3540
|
+
write: (s) => {
|
|
3541
|
+
processStdout.write(s);
|
|
3542
|
+
}
|
|
3543
|
+
});
|
|
3544
|
+
} finally {
|
|
3545
|
+
rl.close();
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
var init_bootstrap = __esm({
|
|
3549
|
+
"src/learner-brief/bootstrap.ts"() {
|
|
3550
|
+
"use strict";
|
|
3551
|
+
}
|
|
3552
|
+
});
|
|
3553
|
+
|
|
3554
|
+
// src/tutor-persona/api.ts
|
|
3555
|
+
async function fetchTutorPersonality(input2) {
|
|
3556
|
+
const url2 = new URL(`${input2.apiUrl}/api/cli/tutor-personality`);
|
|
3557
|
+
url2.searchParams.set("courseId", input2.courseId);
|
|
3558
|
+
const response = await fetch(url2.toString(), {
|
|
3559
|
+
method: "GET",
|
|
3560
|
+
headers: {
|
|
3561
|
+
"Content-Type": "application/json",
|
|
3562
|
+
Authorization: `Bearer ${input2.token}`
|
|
3563
|
+
}
|
|
3564
|
+
});
|
|
3565
|
+
const body = await response.json();
|
|
3566
|
+
if (!response.ok) {
|
|
3567
|
+
throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
|
|
3568
|
+
}
|
|
3569
|
+
return body.personality ?? null;
|
|
3570
|
+
}
|
|
3571
|
+
var init_api3 = __esm({
|
|
3572
|
+
"src/tutor-persona/api.ts"() {
|
|
3573
|
+
"use strict";
|
|
3574
|
+
init_http2();
|
|
3575
|
+
}
|
|
3576
|
+
});
|
|
3577
|
+
|
|
3578
|
+
// src/instruction-pipeline.ts
|
|
3579
|
+
async function defaultWriteFiles(_cwd, _courseMeta, _content) {
|
|
3580
|
+
throw new Error(
|
|
3581
|
+
"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."
|
|
3582
|
+
);
|
|
3583
|
+
}
|
|
3584
|
+
async function defaultFetchCourseMeta(_input) {
|
|
3585
|
+
throw new Error(
|
|
3586
|
+
"fetchCourseMeta default implementation is a stub \u2014 inject a real one from the calling command (select/init/sync)"
|
|
3587
|
+
);
|
|
3588
|
+
}
|
|
3589
|
+
async function resolveAndGenerate(input2, options = {}, context) {
|
|
3590
|
+
const deps = context.deps ?? defaultDeps2;
|
|
3591
|
+
const forceRefresh = options.forceRefresh ?? false;
|
|
3592
|
+
const session = await deps.getSession(context.configDir);
|
|
3593
|
+
if (!session) {
|
|
3594
|
+
throw new Error("No active session \u2014 run `tostudy login`");
|
|
3595
|
+
}
|
|
3596
|
+
const courseMeta = await deps.fetchCourseMeta({
|
|
3597
|
+
session,
|
|
3598
|
+
courseId: input2.courseId,
|
|
3599
|
+
enrollmentId: input2.enrollmentId
|
|
3600
|
+
});
|
|
3601
|
+
let tutorPersona = null;
|
|
3602
|
+
if (!forceRefresh) {
|
|
3603
|
+
const cached2 = await deps.readPersonaCacheForCourse(context.configDir, input2.courseId);
|
|
3604
|
+
if (cached2) tutorPersona = cached2.personality;
|
|
3605
|
+
}
|
|
3606
|
+
if (!tutorPersona) {
|
|
3607
|
+
try {
|
|
3608
|
+
tutorPersona = await deps.fetchTutorPersona({
|
|
3609
|
+
apiUrl: session.apiUrl,
|
|
3610
|
+
token: session.token,
|
|
3611
|
+
courseId: input2.courseId
|
|
3612
|
+
});
|
|
3613
|
+
if (tutorPersona) {
|
|
3614
|
+
await deps.writePersonaCacheForCourse(context.configDir, input2.courseId, {
|
|
3615
|
+
personality: tutorPersona,
|
|
3616
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3617
|
+
profileUpdatedAt: tutorPersona.updatedAt
|
|
3618
|
+
});
|
|
3619
|
+
}
|
|
3620
|
+
} catch (err) {
|
|
3621
|
+
logger5.warn("T0 fetch failed \u2014 rendering without persona", { err });
|
|
3622
|
+
tutorPersona = null;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
let studentBrief = null;
|
|
3626
|
+
let bootstrapped = null;
|
|
3627
|
+
if (!forceRefresh) {
|
|
3628
|
+
const cached2 = await deps.readBriefCache(context.configDir);
|
|
3629
|
+
if (cached2 && cached2.userId === session.userId && !cached2.skipped) {
|
|
3630
|
+
studentBrief = cached2.brief;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
if (!studentBrief) {
|
|
3634
|
+
try {
|
|
3635
|
+
studentBrief = await deps.fetchLearnerBrief({
|
|
3636
|
+
apiUrl: session.apiUrl,
|
|
3637
|
+
token: session.token
|
|
3638
|
+
});
|
|
3639
|
+
await deps.writeBriefCache(context.configDir, {
|
|
3640
|
+
userId: session.userId,
|
|
3641
|
+
brief: studentBrief,
|
|
3642
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3643
|
+
});
|
|
3644
|
+
} catch (err) {
|
|
3645
|
+
logger5.warn("T1 fetch failed \u2014 rendering without brief", { err });
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
if (!studentBrief && options.collectT1Interactive) {
|
|
3649
|
+
try {
|
|
3650
|
+
const answers = await deps.collectBootstrapAnswers({ userName: session.userName });
|
|
3651
|
+
const text2 = composeBriefFromAnswers(answers);
|
|
3652
|
+
studentBrief = await deps.upsertLearnerBrief({
|
|
3653
|
+
apiUrl: session.apiUrl,
|
|
3654
|
+
token: session.token,
|
|
3655
|
+
text: text2,
|
|
3656
|
+
source: "manual"
|
|
3657
|
+
});
|
|
3658
|
+
await deps.writeBriefCache(context.configDir, {
|
|
3659
|
+
userId: session.userId,
|
|
3660
|
+
brief: studentBrief,
|
|
3661
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3662
|
+
});
|
|
3663
|
+
bootstrapped = "t1";
|
|
3664
|
+
} catch (err) {
|
|
3665
|
+
logger5.warn("T1 bootstrap failed", { err });
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
let enrollmentProfile = null;
|
|
3669
|
+
try {
|
|
3670
|
+
const onboarding = await deps.fetchEnrollmentOnboarding({
|
|
3671
|
+
apiUrl: session.apiUrl,
|
|
3672
|
+
token: session.token,
|
|
3673
|
+
enrollmentId: input2.enrollmentId
|
|
3674
|
+
});
|
|
3675
|
+
if (onboarding) {
|
|
3676
|
+
enrollmentProfile = onboarding.learnerProfile;
|
|
3677
|
+
}
|
|
3678
|
+
} catch (err) {
|
|
3679
|
+
logger5.warn("T2 fetch failed \u2014 rendering without enrollment profile", { err });
|
|
3680
|
+
}
|
|
3681
|
+
const content = renderCourseInstruction({
|
|
3682
|
+
course: {
|
|
3683
|
+
courseId: courseMeta.courseId,
|
|
3684
|
+
courseTitle: courseMeta.courseTitle,
|
|
3685
|
+
slug: courseMeta.slug,
|
|
3686
|
+
courseDescription: courseMeta.courseDescription
|
|
3687
|
+
},
|
|
3688
|
+
progress: courseMeta.progress,
|
|
3689
|
+
tutorPersona,
|
|
3690
|
+
studentBrief,
|
|
3691
|
+
enrollmentProfile
|
|
3692
|
+
});
|
|
3693
|
+
const wroteFiles = await deps.writeFiles(context.cwd, courseMeta, content);
|
|
3694
|
+
let markerPath = null;
|
|
3695
|
+
try {
|
|
3696
|
+
markerPath = await deps.writeWorkspaceMarker(context.cwd, {
|
|
3697
|
+
courseId: courseMeta.courseId,
|
|
3698
|
+
enrollmentId: input2.enrollmentId,
|
|
3699
|
+
slug: courseMeta.slug,
|
|
3700
|
+
courseTitle: courseMeta.courseTitle
|
|
3701
|
+
});
|
|
3702
|
+
} catch (err) {
|
|
3703
|
+
logger5.warn("Failed to write workspace marker", { err });
|
|
3704
|
+
}
|
|
3705
|
+
return {
|
|
3706
|
+
wroteFiles,
|
|
3707
|
+
markerPath,
|
|
3708
|
+
usedT0: tutorPersona !== null,
|
|
3709
|
+
usedT1: studentBrief !== null,
|
|
3710
|
+
usedT2: enrollmentProfile !== null,
|
|
3711
|
+
bootstrapped
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
var logger5, defaultDeps2;
|
|
3715
|
+
var init_instruction_pipeline = __esm({
|
|
3716
|
+
"src/instruction-pipeline.ts"() {
|
|
3717
|
+
"use strict";
|
|
3718
|
+
init_src();
|
|
3719
|
+
init_workspace_marker();
|
|
3720
|
+
init_api2();
|
|
3721
|
+
init_cache();
|
|
3722
|
+
init_bootstrap();
|
|
3723
|
+
init_api3();
|
|
3724
|
+
init_cache2();
|
|
3725
|
+
init_api();
|
|
3726
|
+
init_session();
|
|
3727
|
+
init_instruction_template_v3();
|
|
3728
|
+
logger5 = createLogger("cli:instruction-pipeline");
|
|
3729
|
+
defaultDeps2 = {
|
|
3730
|
+
fetchTutorPersona: fetchTutorPersonality,
|
|
3731
|
+
fetchLearnerBrief,
|
|
3732
|
+
fetchEnrollmentOnboarding: getRemoteEnrollmentOnboarding,
|
|
3733
|
+
fetchCourseMeta: defaultFetchCourseMeta,
|
|
3734
|
+
readBriefCache,
|
|
3735
|
+
writeBriefCache,
|
|
3736
|
+
readPersonaCacheForCourse,
|
|
3737
|
+
writePersonaCacheForCourse,
|
|
3738
|
+
writeFiles: defaultWriteFiles,
|
|
3739
|
+
writeWorkspaceMarker,
|
|
3740
|
+
collectBootstrapAnswers,
|
|
3741
|
+
upsertLearnerBrief,
|
|
3742
|
+
getSession
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
});
|
|
3746
|
+
|
|
3747
|
+
// src/commands/_shared/pipeline-deps.ts
|
|
3748
|
+
async function fetchCourseMetaViaHttp(input2) {
|
|
3749
|
+
const { session, courseId, enrollmentId } = input2;
|
|
3750
|
+
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3751
|
+
const coreDeps = { data, logger: logger6 };
|
|
3752
|
+
const detail = await selectCourse({ userId: session.userId, courseId }, coreDeps);
|
|
3753
|
+
let currentModuleTitle;
|
|
3754
|
+
let currentLessonTitle;
|
|
3755
|
+
try {
|
|
3756
|
+
const progress3 = await getProgress({ enrollmentId }, coreDeps);
|
|
3757
|
+
currentModuleTitle = progress3.currentModule?.title;
|
|
3758
|
+
currentLessonTitle = progress3.currentLesson?.title;
|
|
3759
|
+
} catch (err) {
|
|
3760
|
+
logger6.debug("getProgress failed \u2014 falling back to basic course meta", {
|
|
3761
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
return {
|
|
3765
|
+
courseId: detail.courseId,
|
|
3766
|
+
courseTitle: detail.courseTitle,
|
|
3767
|
+
slug: slugify(detail.courseTitle),
|
|
3768
|
+
courseDescription: detail.courseDescription,
|
|
3769
|
+
progress: {
|
|
3770
|
+
percent: detail.progress,
|
|
3771
|
+
moduleCount: detail.moduleCount,
|
|
3772
|
+
lessonCount: detail.lessonCount,
|
|
3773
|
+
currentModuleTitle,
|
|
3774
|
+
currentLessonTitle
|
|
3775
|
+
}
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
async function writeFilesAdapter(cwd, courseMeta, content) {
|
|
3779
|
+
return writeInstructionFiles(
|
|
3780
|
+
cwd,
|
|
3781
|
+
{
|
|
3782
|
+
courseTitle: courseMeta.courseTitle,
|
|
3783
|
+
courseId: courseMeta.courseId,
|
|
3784
|
+
progress: courseMeta.progress.percent,
|
|
3785
|
+
moduleCount: courseMeta.progress.moduleCount,
|
|
3786
|
+
lessonCount: courseMeta.progress.lessonCount,
|
|
3787
|
+
currentModuleTitle: courseMeta.progress.currentModuleTitle,
|
|
3788
|
+
currentLessonTitle: courseMeta.progress.currentLessonTitle,
|
|
3789
|
+
courseDescription: courseMeta.courseDescription
|
|
3790
|
+
},
|
|
3791
|
+
content
|
|
3792
|
+
);
|
|
3793
|
+
}
|
|
3794
|
+
function buildPipelineDeps() {
|
|
3795
|
+
return {
|
|
3796
|
+
...defaultDeps2,
|
|
3797
|
+
fetchCourseMeta: fetchCourseMetaViaHttp,
|
|
3798
|
+
writeFiles: writeFilesAdapter
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3801
|
+
var logger6;
|
|
3802
|
+
var init_pipeline_deps = __esm({
|
|
3803
|
+
"src/commands/_shared/pipeline-deps.ts"() {
|
|
3804
|
+
"use strict";
|
|
3805
|
+
init_src();
|
|
3806
|
+
init_courses();
|
|
3807
|
+
init_http2();
|
|
3808
|
+
init_instruction_pipeline();
|
|
3809
|
+
init_instruction_files();
|
|
3810
|
+
logger6 = createLogger("cli:pipeline-deps");
|
|
3811
|
+
}
|
|
3812
|
+
});
|
|
3813
|
+
|
|
3047
3814
|
// src/commands/select.ts
|
|
3048
3815
|
import { Command as Command6 } from "commander";
|
|
3049
|
-
|
|
3816
|
+
import os9 from "node:os";
|
|
3817
|
+
var logger7, selectCommand;
|
|
3050
3818
|
var init_select = __esm({
|
|
3051
3819
|
"src/commands/select.ts"() {
|
|
3052
3820
|
"use strict";
|
|
@@ -3055,13 +3823,15 @@ var init_select = __esm({
|
|
|
3055
3823
|
init_http2();
|
|
3056
3824
|
init_session();
|
|
3057
3825
|
init_formatter();
|
|
3058
|
-
|
|
3059
|
-
|
|
3826
|
+
init_status();
|
|
3827
|
+
init_instruction_pipeline();
|
|
3828
|
+
init_pipeline_deps();
|
|
3829
|
+
logger7 = createLogger("cli:select");
|
|
3060
3830
|
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) => {
|
|
3061
3831
|
try {
|
|
3062
3832
|
const session = await requireSession();
|
|
3063
3833
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3064
|
-
const deps = { data, logger:
|
|
3834
|
+
const deps = { data, logger: logger7 };
|
|
3065
3835
|
const courses3 = await listCourses({ userId: session.userId }, deps);
|
|
3066
3836
|
let courseId = course;
|
|
3067
3837
|
let enrollmentId = "";
|
|
@@ -3088,38 +3858,77 @@ var init_select = __esm({
|
|
|
3088
3858
|
courseTags: matched?.tags,
|
|
3089
3859
|
courseLevel: matched?.level
|
|
3090
3860
|
});
|
|
3091
|
-
|
|
3861
|
+
const activeCourseForStatus = {
|
|
3862
|
+
courseId: detail.courseId,
|
|
3863
|
+
courseTitle: detail.courseTitle,
|
|
3864
|
+
enrollmentId
|
|
3865
|
+
};
|
|
3866
|
+
const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
|
|
3867
|
+
let pipelineResult = null;
|
|
3092
3868
|
try {
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
lessonCount: detail.lessonCount,
|
|
3099
|
-
courseDescription: detail.courseDescription
|
|
3100
|
-
});
|
|
3869
|
+
pipelineResult = await resolveAndGenerate(
|
|
3870
|
+
{ courseId: detail.courseId, enrollmentId },
|
|
3871
|
+
{ forceRefresh: false, collectT1Interactive: false, collectT2Interactive: false },
|
|
3872
|
+
{ cwd: process.cwd(), deps: buildPipelineDeps() }
|
|
3873
|
+
);
|
|
3101
3874
|
} catch (err) {
|
|
3102
|
-
|
|
3875
|
+
logger7.warn("Failed to run instruction pipeline", {
|
|
3103
3876
|
error: err instanceof Error ? err.message : String(err)
|
|
3104
3877
|
});
|
|
3105
3878
|
}
|
|
3879
|
+
const slugMatch = pipelineResult?.wroteFiles.find((f) => f.startsWith(".claude/commands/"))?.match(/tostudy-(.+)\.md$/);
|
|
3880
|
+
const courseSlug2 = slugMatch?.[1] ?? "";
|
|
3106
3881
|
if (opts.json) {
|
|
3107
|
-
output(
|
|
3882
|
+
output(
|
|
3883
|
+
{
|
|
3884
|
+
...detail,
|
|
3885
|
+
enrollmentId,
|
|
3886
|
+
courseSlug: courseSlug2,
|
|
3887
|
+
workspacePath: onboarding.workspacePath,
|
|
3888
|
+
workspaceSource: onboarding.workspaceSource,
|
|
3889
|
+
wroteFiles: pipelineResult?.wroteFiles ?? [],
|
|
3890
|
+
markerPath: pipelineResult?.markerPath ?? null,
|
|
3891
|
+
usedT0: pipelineResult?.usedT0 ?? false,
|
|
3892
|
+
usedT1: pipelineResult?.usedT1 ?? false,
|
|
3893
|
+
usedT2: pipelineResult?.usedT2 ?? false
|
|
3894
|
+
},
|
|
3895
|
+
{ json: true }
|
|
3896
|
+
);
|
|
3108
3897
|
} else {
|
|
3109
3898
|
const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3899
|
+
const home = os9.homedir();
|
|
3900
|
+
const cwd = process.cwd();
|
|
3901
|
+
const namespacedPath = `${cwd}/.tostudy`;
|
|
3902
|
+
let wsLine;
|
|
3903
|
+
if (onboarding.workspacePath === namespacedPath) {
|
|
3904
|
+
wsLine = ` Workspace: ${cwd.replace(home, "~")}/.tostudy/ (isolado do projeto)`;
|
|
3905
|
+
} else if (onboarding.workspacePath === cwd) {
|
|
3906
|
+
wsLine = ` Workspace: esta pasta (${cwd.replace(home, "~")})`;
|
|
3907
|
+
} else if (onboarding.workspacePath) {
|
|
3908
|
+
wsLine = ` Workspace: ${onboarding.workspacePath.replace(home, "~")}`;
|
|
3909
|
+
} else {
|
|
3910
|
+
wsLine = " Workspace: rode `tostudy workspace setup` para configurar";
|
|
3911
|
+
}
|
|
3912
|
+
const lines = [
|
|
3913
|
+
`\u2713 Curso ativado: ${detail.courseTitle}`,
|
|
3914
|
+
` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
|
|
3915
|
+
wsLine,
|
|
3916
|
+
"",
|
|
3917
|
+
" Arquivos de contexto criados para seu assistente AI."
|
|
3918
|
+
];
|
|
3919
|
+
if (pipelineResult && !pipelineResult.usedT0) {
|
|
3920
|
+
lines.push(" (sem persona do creator \u2014 usando tutor neutro)");
|
|
3921
|
+
}
|
|
3922
|
+
if (pipelineResult && !pipelineResult.usedT1) {
|
|
3923
|
+
lines.push(" (sem brief base \u2014 rode `tostudy init` para configurar)");
|
|
3924
|
+
}
|
|
3925
|
+
lines.push(
|
|
3926
|
+
"",
|
|
3927
|
+
"\u2192 Abra sua plataforma (claude, codex, cursor...)",
|
|
3928
|
+
`\u2192 No Claude Code, digite: ${slashCmd}`,
|
|
3929
|
+
"\u2192 Em outras plataformas: tostudy init"
|
|
3122
3930
|
);
|
|
3931
|
+
output(lines.join("\n"), { json: false });
|
|
3123
3932
|
}
|
|
3124
3933
|
} catch (err) {
|
|
3125
3934
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3131,7 +3940,7 @@ var init_select = __esm({
|
|
|
3131
3940
|
|
|
3132
3941
|
// src/commands/progress.ts
|
|
3133
3942
|
import { Command as Command7 } from "commander";
|
|
3134
|
-
var
|
|
3943
|
+
var logger8, progressCommand;
|
|
3135
3944
|
var init_progress = __esm({
|
|
3136
3945
|
"src/commands/progress.ts"() {
|
|
3137
3946
|
"use strict";
|
|
@@ -3140,15 +3949,15 @@ var init_progress = __esm({
|
|
|
3140
3949
|
init_http2();
|
|
3141
3950
|
init_session();
|
|
3142
3951
|
init_formatter();
|
|
3143
|
-
|
|
3952
|
+
logger8 = createLogger("cli:progress");
|
|
3144
3953
|
progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
3145
3954
|
try {
|
|
3146
3955
|
const session = await requireSession();
|
|
3147
|
-
const activeCourse = await requireActiveCourse();
|
|
3956
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
3148
3957
|
const driftWarning = await checkCourseDrift();
|
|
3149
3958
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
3150
3959
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3151
|
-
const deps = { data, logger:
|
|
3960
|
+
const deps = { data, logger: logger8 };
|
|
3152
3961
|
const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
3153
3962
|
if (opts.json) {
|
|
3154
3963
|
output(progress3, { json: true });
|
|
@@ -4709,7 +5518,7 @@ var init_query_promise = __esm({
|
|
|
4709
5518
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
4710
5519
|
const nullifyMap = {};
|
|
4711
5520
|
const result = columns.reduce(
|
|
4712
|
-
(result2, { path:
|
|
5521
|
+
(result2, { path: path17, field }, columnIndex) => {
|
|
4713
5522
|
let decoder;
|
|
4714
5523
|
if (is(field, Column)) {
|
|
4715
5524
|
decoder = field;
|
|
@@ -4721,8 +5530,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4721
5530
|
decoder = field.sql.decoder;
|
|
4722
5531
|
}
|
|
4723
5532
|
let node = result2;
|
|
4724
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
4725
|
-
if (pathChunkIndex <
|
|
5533
|
+
for (const [pathChunkIndex, pathChunk] of path17.entries()) {
|
|
5534
|
+
if (pathChunkIndex < path17.length - 1) {
|
|
4726
5535
|
if (!(pathChunk in node)) {
|
|
4727
5536
|
node[pathChunk] = {};
|
|
4728
5537
|
}
|
|
@@ -4730,8 +5539,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4730
5539
|
} else {
|
|
4731
5540
|
const rawValue = row[columnIndex];
|
|
4732
5541
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
4733
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
4734
|
-
const objectName =
|
|
5542
|
+
if (joinsNotNullableMap && is(field, Column) && path17.length === 2) {
|
|
5543
|
+
const objectName = path17[0];
|
|
4735
5544
|
if (!(objectName in nullifyMap)) {
|
|
4736
5545
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
4737
5546
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -8678,13 +9487,13 @@ function Subscribe(postgres2, options) {
|
|
|
8678
9487
|
}
|
|
8679
9488
|
}
|
|
8680
9489
|
function handle(a, b2) {
|
|
8681
|
-
const
|
|
9490
|
+
const path17 = b2.relation.schema + "." + b2.relation.table;
|
|
8682
9491
|
call("*", a, b2);
|
|
8683
|
-
call("*:" +
|
|
8684
|
-
b2.relation.keys.length && call("*:" +
|
|
9492
|
+
call("*:" + path17, a, b2);
|
|
9493
|
+
b2.relation.keys.length && call("*:" + path17 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
|
|
8685
9494
|
call(b2.command, a, b2);
|
|
8686
|
-
call(b2.command + ":" +
|
|
8687
|
-
b2.relation.keys.length && call(b2.command + ":" +
|
|
9495
|
+
call(b2.command + ":" + path17, a, b2);
|
|
9496
|
+
b2.relation.keys.length && call(b2.command + ":" + path17 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
|
|
8688
9497
|
}
|
|
8689
9498
|
function pong() {
|
|
8690
9499
|
const x2 = Buffer.alloc(34);
|
|
@@ -8797,8 +9606,8 @@ function parseEvent(x) {
|
|
|
8797
9606
|
const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
|
|
8798
9607
|
if (!xs)
|
|
8799
9608
|
throw new Error("Malformed subscribe pattern: " + x);
|
|
8800
|
-
const [, command,
|
|
8801
|
-
return (command || "*") + (
|
|
9609
|
+
const [, command, path17, key] = xs;
|
|
9610
|
+
return (command || "*") + (path17 ? ":" + (path17.indexOf(".") === -1 ? "public." + path17 : path17) : "") + (key ? "=" + key : "");
|
|
8802
9611
|
}
|
|
8803
9612
|
var noop2;
|
|
8804
9613
|
var init_subscribe = __esm({
|
|
@@ -8879,8 +9688,8 @@ var init_large = __esm({
|
|
|
8879
9688
|
});
|
|
8880
9689
|
|
|
8881
9690
|
// ../../node_modules/postgres/src/index.js
|
|
8882
|
-
import
|
|
8883
|
-
import
|
|
9691
|
+
import os10 from "os";
|
|
9692
|
+
import fs8 from "fs";
|
|
8884
9693
|
function Postgres(a, b2) {
|
|
8885
9694
|
const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
|
|
8886
9695
|
let ending = false;
|
|
@@ -8936,10 +9745,10 @@ function Postgres(a, b2) {
|
|
|
8936
9745
|
});
|
|
8937
9746
|
return query;
|
|
8938
9747
|
}
|
|
8939
|
-
function file2(
|
|
9748
|
+
function file2(path17, args = [], options2 = {}) {
|
|
8940
9749
|
arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
|
|
8941
9750
|
const query = new Query([], args, (query2) => {
|
|
8942
|
-
|
|
9751
|
+
fs8.readFile(path17, "utf8", (err, string4) => {
|
|
8943
9752
|
if (err)
|
|
8944
9753
|
return query2.reject(err);
|
|
8945
9754
|
query2.strings = [string4];
|
|
@@ -9257,7 +10066,7 @@ function parseUrl(url2) {
|
|
|
9257
10066
|
}
|
|
9258
10067
|
function osUsername() {
|
|
9259
10068
|
try {
|
|
9260
|
-
return
|
|
10069
|
+
return os10.userInfo().username;
|
|
9261
10070
|
} catch (_) {
|
|
9262
10071
|
return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
|
|
9263
10072
|
}
|
|
@@ -13165,7 +13974,7 @@ async function hashQuery(sql2, params) {
|
|
|
13165
13974
|
return hashHex;
|
|
13166
13975
|
}
|
|
13167
13976
|
var Cache, NoopCache;
|
|
13168
|
-
var
|
|
13977
|
+
var init_cache3 = __esm({
|
|
13169
13978
|
"../../node_modules/drizzle-orm/cache/core/cache.js"() {
|
|
13170
13979
|
init_entity();
|
|
13171
13980
|
Cache = class {
|
|
@@ -13190,7 +13999,7 @@ var init_cache = __esm({
|
|
|
13190
13999
|
// ../../node_modules/drizzle-orm/cache/core/index.js
|
|
13191
14000
|
var init_core = __esm({
|
|
13192
14001
|
"../../node_modules/drizzle-orm/cache/core/index.js"() {
|
|
13193
|
-
|
|
14002
|
+
init_cache3();
|
|
13194
14003
|
}
|
|
13195
14004
|
});
|
|
13196
14005
|
|
|
@@ -13298,7 +14107,7 @@ var init_schema = __esm({
|
|
|
13298
14107
|
var PgPreparedQuery, PgSession, PgTransaction;
|
|
13299
14108
|
var init_session2 = __esm({
|
|
13300
14109
|
"../../node_modules/drizzle-orm/pg-core/session.js"() {
|
|
13301
|
-
|
|
14110
|
+
init_cache3();
|
|
13302
14111
|
init_entity();
|
|
13303
14112
|
init_errors();
|
|
13304
14113
|
init_sql2();
|
|
@@ -13518,12 +14327,12 @@ var init_session3 = __esm({
|
|
|
13518
14327
|
init_tracing();
|
|
13519
14328
|
init_utils();
|
|
13520
14329
|
PostgresJsPreparedQuery = class extends PgPreparedQuery {
|
|
13521
|
-
constructor(client, queryString, params,
|
|
14330
|
+
constructor(client, queryString, params, logger23, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
|
|
13522
14331
|
super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
|
|
13523
14332
|
this.client = client;
|
|
13524
14333
|
this.queryString = queryString;
|
|
13525
14334
|
this.params = params;
|
|
13526
|
-
this.logger =
|
|
14335
|
+
this.logger = logger23;
|
|
13527
14336
|
this.fields = fields;
|
|
13528
14337
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
13529
14338
|
this.customResultMapper = customResultMapper;
|
|
@@ -13664,11 +14473,11 @@ function construct(client, config2 = {}) {
|
|
|
13664
14473
|
client.options.serializers["114"] = transparentParser;
|
|
13665
14474
|
client.options.serializers["3802"] = transparentParser;
|
|
13666
14475
|
const dialect = new PgDialect({ casing: config2.casing });
|
|
13667
|
-
let
|
|
14476
|
+
let logger23;
|
|
13668
14477
|
if (config2.logger === true) {
|
|
13669
|
-
|
|
14478
|
+
logger23 = new DefaultLogger();
|
|
13670
14479
|
} else if (config2.logger !== false) {
|
|
13671
|
-
|
|
14480
|
+
logger23 = config2.logger;
|
|
13672
14481
|
}
|
|
13673
14482
|
let schema;
|
|
13674
14483
|
if (config2.schema) {
|
|
@@ -13682,7 +14491,7 @@ function construct(client, config2 = {}) {
|
|
|
13682
14491
|
tableNamesMap: tablesConfig.tableNamesMap
|
|
13683
14492
|
};
|
|
13684
14493
|
}
|
|
13685
|
-
const session = new PostgresJsSession(client, dialect, schema, { logger:
|
|
14494
|
+
const session = new PostgresJsSession(client, dialect, schema, { logger: logger23, cache: config2.cache });
|
|
13686
14495
|
const db2 = new PostgresJsDatabase(dialect, session, schema);
|
|
13687
14496
|
db2.$client = client;
|
|
13688
14497
|
db2.$cache = config2.cache;
|
|
@@ -13884,7 +14693,7 @@ __export(util_exports, {
|
|
|
13884
14693
|
required: () => required,
|
|
13885
14694
|
safeExtend: () => safeExtend,
|
|
13886
14695
|
shallowClone: () => shallowClone,
|
|
13887
|
-
slugify: () =>
|
|
14696
|
+
slugify: () => slugify2,
|
|
13888
14697
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
13889
14698
|
uint8ArrayToBase64: () => uint8ArrayToBase64,
|
|
13890
14699
|
uint8ArrayToBase64url: () => uint8ArrayToBase64url,
|
|
@@ -13997,10 +14806,10 @@ function mergeDefs(...defs) {
|
|
|
13997
14806
|
function cloneDef(schema) {
|
|
13998
14807
|
return mergeDefs(schema._zod.def);
|
|
13999
14808
|
}
|
|
14000
|
-
function getElementAtPath(obj,
|
|
14001
|
-
if (!
|
|
14809
|
+
function getElementAtPath(obj, path17) {
|
|
14810
|
+
if (!path17)
|
|
14002
14811
|
return obj;
|
|
14003
|
-
return
|
|
14812
|
+
return path17.reduce((acc, key) => acc?.[key], obj);
|
|
14004
14813
|
}
|
|
14005
14814
|
function promiseAllObject(promisesObj) {
|
|
14006
14815
|
const keys = Object.keys(promisesObj);
|
|
@@ -14024,7 +14833,7 @@ function randomString(length = 10) {
|
|
|
14024
14833
|
function esc(str) {
|
|
14025
14834
|
return JSON.stringify(str);
|
|
14026
14835
|
}
|
|
14027
|
-
function
|
|
14836
|
+
function slugify2(input2) {
|
|
14028
14837
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14029
14838
|
}
|
|
14030
14839
|
function isObject(data) {
|
|
@@ -14312,11 +15121,11 @@ function aborted(x, startIndex = 0) {
|
|
|
14312
15121
|
}
|
|
14313
15122
|
return false;
|
|
14314
15123
|
}
|
|
14315
|
-
function prefixIssues(
|
|
15124
|
+
function prefixIssues(path17, issues) {
|
|
14316
15125
|
return issues.map((iss) => {
|
|
14317
15126
|
var _a2;
|
|
14318
15127
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
14319
|
-
iss.path.unshift(
|
|
15128
|
+
iss.path.unshift(path17);
|
|
14320
15129
|
return iss;
|
|
14321
15130
|
});
|
|
14322
15131
|
}
|
|
@@ -14558,7 +15367,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14558
15367
|
}
|
|
14559
15368
|
function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
14560
15369
|
const result = { errors: [] };
|
|
14561
|
-
const processError = (error50,
|
|
15370
|
+
const processError = (error50, path17 = []) => {
|
|
14562
15371
|
var _a2, _b;
|
|
14563
15372
|
for (const issue2 of error50.issues) {
|
|
14564
15373
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -14568,7 +15377,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14568
15377
|
} else if (issue2.code === "invalid_element") {
|
|
14569
15378
|
processError({ issues: issue2.issues }, issue2.path);
|
|
14570
15379
|
} else {
|
|
14571
|
-
const fullpath = [...
|
|
15380
|
+
const fullpath = [...path17, ...issue2.path];
|
|
14572
15381
|
if (fullpath.length === 0) {
|
|
14573
15382
|
result.errors.push(mapper(issue2));
|
|
14574
15383
|
continue;
|
|
@@ -14600,8 +15409,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14600
15409
|
}
|
|
14601
15410
|
function toDotPath(_path) {
|
|
14602
15411
|
const segs = [];
|
|
14603
|
-
const
|
|
14604
|
-
for (const seg of
|
|
15412
|
+
const path17 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
15413
|
+
for (const seg of path17) {
|
|
14605
15414
|
if (typeof seg === "number")
|
|
14606
15415
|
segs.push(`[${seg}]`);
|
|
14607
15416
|
else if (typeof seg === "symbol")
|
|
@@ -24158,7 +24967,7 @@ function _toUpperCase() {
|
|
|
24158
24967
|
}
|
|
24159
24968
|
// @__NO_SIDE_EFFECTS__
|
|
24160
24969
|
function _slugify() {
|
|
24161
|
-
return /* @__PURE__ */ _overwrite((input2) =>
|
|
24970
|
+
return /* @__PURE__ */ _overwrite((input2) => slugify2(input2));
|
|
24162
24971
|
}
|
|
24163
24972
|
// @__NO_SIDE_EFFECTS__
|
|
24164
24973
|
function _array(Class2, element, params) {
|
|
@@ -24512,7 +25321,7 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
|
|
|
24512
25321
|
return inst;
|
|
24513
25322
|
}
|
|
24514
25323
|
var TimePrecision;
|
|
24515
|
-
var
|
|
25324
|
+
var init_api4 = __esm({
|
|
24516
25325
|
"../../node_modules/zod/v4/core/api.js"() {
|
|
24517
25326
|
init_checks2();
|
|
24518
25327
|
init_registries();
|
|
@@ -25823,7 +26632,7 @@ var init_core3 = __esm({
|
|
|
25823
26632
|
init_locales();
|
|
25824
26633
|
init_registries();
|
|
25825
26634
|
init_doc();
|
|
25826
|
-
|
|
26635
|
+
init_api4();
|
|
25827
26636
|
init_to_json_schema();
|
|
25828
26637
|
init_json_schema_processors();
|
|
25829
26638
|
init_json_schema_generator();
|
|
@@ -27295,13 +28104,13 @@ function resolveRef(ref, ctx) {
|
|
|
27295
28104
|
if (!ref.startsWith("#")) {
|
|
27296
28105
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
27297
28106
|
}
|
|
27298
|
-
const
|
|
27299
|
-
if (
|
|
28107
|
+
const path17 = ref.slice(1).split("/").filter(Boolean);
|
|
28108
|
+
if (path17.length === 0) {
|
|
27300
28109
|
return ctx.rootSchema;
|
|
27301
28110
|
}
|
|
27302
28111
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
27303
|
-
if (
|
|
27304
|
-
const key =
|
|
28112
|
+
if (path17[0] === defsKey) {
|
|
28113
|
+
const key = path17[1];
|
|
27305
28114
|
if (!key || !ctx.defs[key]) {
|
|
27306
28115
|
throw new Error(`Reference not found: ${ref}`);
|
|
27307
28116
|
}
|
|
@@ -28490,7 +29299,11 @@ var init_courses3 = __esm({
|
|
|
28490
29299
|
// Learning Path Graph: Reference to migrated graph (null if not migrated)
|
|
28491
29300
|
migratedToGraphId: uuid("migrated_to_graph_id"),
|
|
28492
29301
|
// Study Channel Configuration: which channels are available for this course
|
|
28493
|
-
channelConfig: jsonb("channel_config").$type()
|
|
29302
|
+
channelConfig: jsonb("channel_config").$type(),
|
|
29303
|
+
// Course Quality Dashboard: Cached health score (0-100 with tier + dimensions)
|
|
29304
|
+
healthScore: jsonb("health_score").$type(),
|
|
29305
|
+
// Certificate: AI-generated course summary for PDF certificates (max 200 chars)
|
|
29306
|
+
certificateSummary: varchar("certificate_summary", { length: 200 })
|
|
28494
29307
|
},
|
|
28495
29308
|
(table) => ({
|
|
28496
29309
|
creatorIdIdx: index("courses_creator_id_idx").on(table.creatorId),
|
|
@@ -32926,29 +33739,32 @@ var init_validation_attempts = __esm({
|
|
|
32926
33739
|
init_users();
|
|
32927
33740
|
init_enrollments();
|
|
32928
33741
|
init_courses3();
|
|
32929
|
-
validationAttempts2 = pgTable(
|
|
32930
|
-
|
|
32931
|
-
|
|
32932
|
-
|
|
32933
|
-
|
|
32934
|
-
|
|
32935
|
-
|
|
32936
|
-
|
|
32937
|
-
|
|
32938
|
-
|
|
32939
|
-
|
|
32940
|
-
|
|
32941
|
-
|
|
32942
|
-
|
|
32943
|
-
|
|
32944
|
-
|
|
32945
|
-
|
|
32946
|
-
|
|
32947
|
-
|
|
32948
|
-
|
|
32949
|
-
|
|
32950
|
-
|
|
32951
|
-
|
|
33742
|
+
validationAttempts2 = pgTable(
|
|
33743
|
+
"validation_attempts",
|
|
33744
|
+
{
|
|
33745
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
33746
|
+
lessonId: uuid("lesson_id").references(() => lessons.id, { onDelete: "cascade" }).notNull(),
|
|
33747
|
+
userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
|
|
33748
|
+
passed: boolean("passed").notNull(),
|
|
33749
|
+
score: real("score"),
|
|
33750
|
+
feedback: text("feedback"),
|
|
33751
|
+
missingCriteria: jsonb("missing_criteria").$type(),
|
|
33752
|
+
attemptedAt: timestamp("attempted_at").defaultNow().notNull(),
|
|
33753
|
+
// v2 — validation persistence & learning analytics
|
|
33754
|
+
enrollmentId: uuid("enrollment_id").references(() => enrollments.id, { onDelete: "cascade" }),
|
|
33755
|
+
courseId: uuid("course_id").references(() => courses.id, { onDelete: "cascade" }),
|
|
33756
|
+
exerciseIndex: integer("exercise_index"),
|
|
33757
|
+
solution: text("solution"),
|
|
33758
|
+
criteriaResults: jsonb("criteria_results").$type(),
|
|
33759
|
+
source: text("source").$type(),
|
|
33760
|
+
duration: integer("duration")
|
|
33761
|
+
},
|
|
33762
|
+
(table) => [
|
|
33763
|
+
index("va_user_lesson_exercise_idx").on(table.userId, table.lessonId, table.exerciseIndex),
|
|
33764
|
+
index("va_course_lesson_passed_idx").on(table.courseId, table.lessonId, table.passed),
|
|
33765
|
+
index("va_user_course_attempted_idx").on(table.userId, table.courseId, table.attemptedAt)
|
|
33766
|
+
]
|
|
33767
|
+
);
|
|
32952
33768
|
}
|
|
32953
33769
|
});
|
|
32954
33770
|
|
|
@@ -35131,77 +35947,6 @@ var init_course_variants = __esm({
|
|
|
35131
35947
|
}
|
|
35132
35948
|
});
|
|
35133
35949
|
|
|
35134
|
-
// ../../packages/database/src/schema/_archived/user-credits.ts
|
|
35135
|
-
var transactionTypeEnum, userCredits2, creditTransactions2;
|
|
35136
|
-
var init_user_credits = __esm({
|
|
35137
|
-
"../../packages/database/src/schema/_archived/user-credits.ts"() {
|
|
35138
|
-
"use strict";
|
|
35139
|
-
init_pg_core();
|
|
35140
|
-
init_users();
|
|
35141
|
-
transactionTypeEnum = pgEnum("transaction_type", [
|
|
35142
|
-
"initial_grant",
|
|
35143
|
-
// Credito inicial de $5
|
|
35144
|
-
"purchase",
|
|
35145
|
-
// Compra de creditos
|
|
35146
|
-
"consumption",
|
|
35147
|
-
// Consumo por uso de IA
|
|
35148
|
-
"refund",
|
|
35149
|
-
// Reembolso
|
|
35150
|
-
"adjustment"
|
|
35151
|
-
// Ajuste manual admin
|
|
35152
|
-
]);
|
|
35153
|
-
userCredits2 = pgTable(
|
|
35154
|
-
"user_credits",
|
|
35155
|
-
{
|
|
35156
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
35157
|
-
userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull().unique(),
|
|
35158
|
-
/** Current balance in USD */
|
|
35159
|
-
balance: decimal("balance", { precision: 10, scale: 4 }).notNull().default("0"),
|
|
35160
|
-
/** Total consumed in USD (absolute value, always positive) */
|
|
35161
|
-
totalConsumed: decimal("total_consumed", { precision: 10, scale: 4 }).notNull().default("0"),
|
|
35162
|
-
/** Total purchased in USD */
|
|
35163
|
-
totalPurchased: decimal("total_purchased", { precision: 10, scale: 4 }).notNull().default("0"),
|
|
35164
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
35165
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
35166
|
-
},
|
|
35167
|
-
(table) => ({
|
|
35168
|
-
userIdIdx: index("user_credits_user_id_idx").on(table.userId)
|
|
35169
|
-
})
|
|
35170
|
-
);
|
|
35171
|
-
creditTransactions2 = pgTable(
|
|
35172
|
-
"credit_transactions",
|
|
35173
|
-
{
|
|
35174
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
35175
|
-
userId: uuid("user_id").references(() => users.id, { onDelete: "restrict" }).notNull(),
|
|
35176
|
-
type: transactionTypeEnum("type").notNull(),
|
|
35177
|
-
/** Amount in USD (negative for consumption, positive for purchase/grant) */
|
|
35178
|
-
amount: decimal("amount", { precision: 10, scale: 4 }).notNull(),
|
|
35179
|
-
/** Balance after this transaction in USD */
|
|
35180
|
-
balanceAfter: decimal("balance_after", { precision: 10, scale: 4 }).notNull(),
|
|
35181
|
-
description: text("description"),
|
|
35182
|
-
metadata: text("metadata"),
|
|
35183
|
-
// JSON com detalhes (tokens, modelo, etc)
|
|
35184
|
-
// Granular usage tracking columns
|
|
35185
|
-
operation: varchar("operation", { length: 50 }),
|
|
35186
|
-
// 'brainstorm_message', 'generation_outline', etc.
|
|
35187
|
-
model: varchar("model", { length: 100 }),
|
|
35188
|
-
// 'claude-sonnet-4-5-20250929'
|
|
35189
|
-
inputTokens: integer("input_tokens"),
|
|
35190
|
-
outputTokens: integer("output_tokens"),
|
|
35191
|
-
latencyMs: integer("latency_ms"),
|
|
35192
|
-
// DB FK to course_proposals preserved in migration; Drizzle ref removed for schema archival
|
|
35193
|
-
proposalId: uuid("proposal_id"),
|
|
35194
|
-
sessionId: uuid("session_id"),
|
|
35195
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
35196
|
-
},
|
|
35197
|
-
(table) => ({
|
|
35198
|
-
userIdIdx: index("credit_transactions_user_id_idx").on(table.userId),
|
|
35199
|
-
createdAtIdx: index("credit_transactions_created_at_idx").on(table.createdAt)
|
|
35200
|
-
})
|
|
35201
|
-
);
|
|
35202
|
-
}
|
|
35203
|
-
});
|
|
35204
|
-
|
|
35205
35950
|
// ../../packages/database/src/schema/_archived/index.ts
|
|
35206
35951
|
var init_archived = __esm({
|
|
35207
35952
|
"../../packages/database/src/schema/_archived/index.ts"() {
|
|
@@ -35210,7 +35955,6 @@ var init_archived = __esm({
|
|
|
35210
35955
|
init_course_matrices();
|
|
35211
35956
|
init_matrix_modules();
|
|
35212
35957
|
init_course_variants();
|
|
35213
|
-
init_user_credits();
|
|
35214
35958
|
}
|
|
35215
35959
|
});
|
|
35216
35960
|
|
|
@@ -36417,7 +37161,6 @@ var init_system_config = __esm({
|
|
|
36417
37161
|
"business.limits.free_brainstorm_sessions": "business.limits.free_brainstorm_sessions",
|
|
36418
37162
|
// Feature Flags
|
|
36419
37163
|
"feature.spark_chat_enabled": "feature.spark_chat_enabled",
|
|
36420
|
-
"feature.ai_course_generation_enabled": "feature.ai_course_generation_enabled",
|
|
36421
37164
|
"feature.analytics_enabled": "feature.analytics_enabled",
|
|
36422
37165
|
"feature.mentorship_enabled": "feature.mentorship_enabled",
|
|
36423
37166
|
"feature.community_enabled": "feature.community_enabled",
|
|
@@ -38685,27 +39428,33 @@ var init_media_assets = __esm({
|
|
|
38685
39428
|
"marketing",
|
|
38686
39429
|
"other"
|
|
38687
39430
|
];
|
|
38688
|
-
mediaAssets = pgTable(
|
|
38689
|
-
|
|
38690
|
-
|
|
38691
|
-
|
|
38692
|
-
|
|
38693
|
-
|
|
38694
|
-
|
|
38695
|
-
|
|
38696
|
-
|
|
38697
|
-
|
|
38698
|
-
|
|
38699
|
-
|
|
38700
|
-
|
|
38701
|
-
|
|
38702
|
-
|
|
38703
|
-
|
|
38704
|
-
|
|
38705
|
-
|
|
38706
|
-
|
|
38707
|
-
|
|
38708
|
-
|
|
39431
|
+
mediaAssets = pgTable(
|
|
39432
|
+
"media_assets",
|
|
39433
|
+
{
|
|
39434
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
39435
|
+
url: text("url").notNull(),
|
|
39436
|
+
r2Key: text("r2_key").notNull(),
|
|
39437
|
+
thumbnailUrl: text("thumbnail_url"),
|
|
39438
|
+
type: text("type", { enum: mediaAssetTypeEnum }).notNull(),
|
|
39439
|
+
mimeType: text("mime_type").notNull(),
|
|
39440
|
+
title: text("title"),
|
|
39441
|
+
originalFilename: text("original_filename").notNull(),
|
|
39442
|
+
size: integer("size").notNull(),
|
|
39443
|
+
width: integer("width"),
|
|
39444
|
+
height: integer("height"),
|
|
39445
|
+
tags: text("tags").array().default([]),
|
|
39446
|
+
uploadedById: uuid("uploaded_by_id").references(() => users.id, {
|
|
39447
|
+
onDelete: "set null"
|
|
39448
|
+
}),
|
|
39449
|
+
domain: text("domain", { enum: mediaAssetDomainEnum }),
|
|
39450
|
+
domainEntityId: uuid("domain_entity_id"),
|
|
39451
|
+
status: text("status", { enum: mediaAssetStatusEnum }).notNull().default("pending"),
|
|
39452
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
39453
|
+
},
|
|
39454
|
+
(table) => ({
|
|
39455
|
+
r2KeyUnique: uniqueIndex("media_assets_r2_key_unique").on(table.r2Key)
|
|
39456
|
+
})
|
|
39457
|
+
);
|
|
38709
39458
|
}
|
|
38710
39459
|
});
|
|
38711
39460
|
|
|
@@ -38896,7 +39645,6 @@ __export(schema_exports, {
|
|
|
38896
39645
|
creditSubscriptionPlans: () => creditSubscriptionPlans,
|
|
38897
39646
|
creditSubscriptionStatusEnum: () => creditSubscriptionStatusEnum,
|
|
38898
39647
|
creditSubscriptions: () => creditSubscriptions,
|
|
38899
|
-
creditTransactions: () => creditTransactions2,
|
|
38900
39648
|
creditWalletTransactionTypeEnum: () => creditWalletTransactionTypeEnum,
|
|
38901
39649
|
creditWalletTransactions: () => creditWalletTransactions,
|
|
38902
39650
|
creditWallets: () => creditWallets,
|
|
@@ -39182,7 +39930,6 @@ __export(schema_exports, {
|
|
|
39182
39930
|
testimonialStatusEnum: () => testimonialStatusEnum,
|
|
39183
39931
|
threadStatusEnum: () => threadStatusEnum,
|
|
39184
39932
|
toneEnum: () => toneEnum,
|
|
39185
|
-
transactionTypeEnum: () => transactionTypeEnum,
|
|
39186
39933
|
transactions: () => transactions2,
|
|
39187
39934
|
transactionsRelations: () => transactionsRelations2,
|
|
39188
39935
|
transferStatusEnum: () => transferStatusEnum,
|
|
@@ -39199,7 +39946,6 @@ __export(schema_exports, {
|
|
|
39199
39946
|
userBadgesRelations: () => userBadgesRelations2,
|
|
39200
39947
|
userChallengeScores: () => userChallengeScores,
|
|
39201
39948
|
userChallengeScoresRelations: () => userChallengeScoresRelations,
|
|
39202
|
-
userCredits: () => userCredits2,
|
|
39203
39949
|
userFollows: () => userFollows2,
|
|
39204
39950
|
userFollowsRelations: () => userFollowsRelations2,
|
|
39205
39951
|
userPathEnrollments: () => userPathEnrollments,
|
|
@@ -40896,71 +41642,12 @@ var init_lessons2 = __esm({
|
|
|
40896
41642
|
}
|
|
40897
41643
|
});
|
|
40898
41644
|
|
|
40899
|
-
// src/workspace/resolve.ts
|
|
40900
|
-
import fs6 from "node:fs/promises";
|
|
40901
|
-
import path5 from "node:path";
|
|
40902
|
-
import os6 from "node:os";
|
|
40903
|
-
function courseSlug(title) {
|
|
40904
|
-
return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
40905
|
-
}
|
|
40906
|
-
async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
|
|
40907
|
-
const slug = courseSlug(courseTitle);
|
|
40908
|
-
const candidate = path5.join(basePath, slug);
|
|
40909
|
-
try {
|
|
40910
|
-
await fs6.access(path5.join(candidate, ".ana-config.json"));
|
|
40911
|
-
return { found: true, workspacePath: candidate };
|
|
40912
|
-
} catch {
|
|
40913
|
-
return { found: false, workspacePath: null };
|
|
40914
|
-
}
|
|
40915
|
-
}
|
|
40916
|
-
var DEFAULT_BASE;
|
|
40917
|
-
var init_resolve = __esm({
|
|
40918
|
-
"src/workspace/resolve.ts"() {
|
|
40919
|
-
"use strict";
|
|
40920
|
-
DEFAULT_BASE = path5.join(os6.homedir(), "study");
|
|
40921
|
-
}
|
|
40922
|
-
});
|
|
40923
|
-
|
|
40924
|
-
// src/onboarding/status.ts
|
|
40925
|
-
import fs7 from "node:fs/promises";
|
|
40926
|
-
import path6 from "node:path";
|
|
40927
|
-
async function resolveStoredWorkspace(workspacePath) {
|
|
40928
|
-
if (!workspacePath) return null;
|
|
40929
|
-
try {
|
|
40930
|
-
await fs7.access(path6.join(workspacePath, ".ana-config.json"));
|
|
40931
|
-
return workspacePath;
|
|
40932
|
-
} catch {
|
|
40933
|
-
return null;
|
|
40934
|
-
}
|
|
40935
|
-
}
|
|
40936
|
-
async function getCourseOnboardingStatus(activeCourse, configDir) {
|
|
40937
|
-
const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
|
|
40938
|
-
const initReady = Boolean(onboardingState?.initCompletedAt);
|
|
40939
|
-
let workspacePath = await resolveStoredWorkspace(onboardingState?.workspacePath);
|
|
40940
|
-
if (!workspacePath) {
|
|
40941
|
-
const resolvedWorkspace = await resolveWorkspace(activeCourse.courseTitle);
|
|
40942
|
-
workspacePath = resolvedWorkspace.workspacePath;
|
|
40943
|
-
}
|
|
40944
|
-
return {
|
|
40945
|
-
initReady,
|
|
40946
|
-
workspaceReady: Boolean(workspacePath),
|
|
40947
|
-
workspacePath
|
|
40948
|
-
};
|
|
40949
|
-
}
|
|
40950
|
-
var init_status = __esm({
|
|
40951
|
-
"src/onboarding/status.ts"() {
|
|
40952
|
-
"use strict";
|
|
40953
|
-
init_session();
|
|
40954
|
-
init_resolve();
|
|
40955
|
-
}
|
|
40956
|
-
});
|
|
40957
|
-
|
|
40958
41645
|
// src/commands/start.ts
|
|
40959
41646
|
import { Command as Command8 } from "commander";
|
|
40960
|
-
async function runStart(opts, deps =
|
|
41647
|
+
async function runStart(opts, deps = defaultDeps3) {
|
|
40961
41648
|
try {
|
|
40962
41649
|
const session = await deps.requireSession();
|
|
40963
|
-
const activeCourse = await deps.requireActiveCourse();
|
|
41650
|
+
const activeCourse = await deps.requireActiveCourse(void 0, { respectWorkspace: true });
|
|
40964
41651
|
const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
|
|
40965
41652
|
if (!onboarding.initReady) {
|
|
40966
41653
|
deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
|
|
@@ -40989,7 +41676,7 @@ async function runStart(opts, deps = defaultDeps2) {
|
|
|
40989
41676
|
deps.error(msg);
|
|
40990
41677
|
}
|
|
40991
41678
|
}
|
|
40992
|
-
var
|
|
41679
|
+
var logger9, defaultDeps3, startCommand;
|
|
40993
41680
|
var init_start = __esm({
|
|
40994
41681
|
"src/commands/start.ts"() {
|
|
40995
41682
|
"use strict";
|
|
@@ -40999,8 +41686,8 @@ var init_start = __esm({
|
|
|
40999
41686
|
init_session();
|
|
41000
41687
|
init_status();
|
|
41001
41688
|
init_formatter();
|
|
41002
|
-
|
|
41003
|
-
|
|
41689
|
+
logger9 = createLogger("cli:start");
|
|
41690
|
+
defaultDeps3 = {
|
|
41004
41691
|
requireSession,
|
|
41005
41692
|
requireActiveCourse,
|
|
41006
41693
|
checkCourseDrift,
|
|
@@ -41012,7 +41699,7 @@ var init_start = __esm({
|
|
|
41012
41699
|
output,
|
|
41013
41700
|
error,
|
|
41014
41701
|
stderrWrite: (message) => process.stderr.write(message),
|
|
41015
|
-
logger:
|
|
41702
|
+
logger: logger9
|
|
41016
41703
|
};
|
|
41017
41704
|
startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41018
41705
|
await runStart(opts);
|
|
@@ -41022,7 +41709,7 @@ var init_start = __esm({
|
|
|
41022
41709
|
|
|
41023
41710
|
// src/commands/start-next.ts
|
|
41024
41711
|
import { Command as Command9 } from "commander";
|
|
41025
|
-
var
|
|
41712
|
+
var logger10, startNextCommand;
|
|
41026
41713
|
var init_start_next = __esm({
|
|
41027
41714
|
"src/commands/start-next.ts"() {
|
|
41028
41715
|
"use strict";
|
|
@@ -41031,7 +41718,7 @@ var init_start_next = __esm({
|
|
|
41031
41718
|
init_http2();
|
|
41032
41719
|
init_session();
|
|
41033
41720
|
init_formatter();
|
|
41034
|
-
|
|
41721
|
+
logger10 = createLogger("cli:start-next");
|
|
41035
41722
|
startNextCommand = new Command9("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41036
41723
|
try {
|
|
41037
41724
|
const session = await requireSession();
|
|
@@ -41039,7 +41726,7 @@ var init_start_next = __esm({
|
|
|
41039
41726
|
const driftWarning = await checkCourseDrift();
|
|
41040
41727
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41041
41728
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41042
|
-
const deps = { data, logger:
|
|
41729
|
+
const deps = { data, logger: logger10 };
|
|
41043
41730
|
const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
41044
41731
|
await setActiveCourse({ ...activeCourse, currentLessonId: moduleData.firstLesson.id });
|
|
41045
41732
|
if (opts.json) {
|
|
@@ -41062,7 +41749,7 @@ var init_start_next = __esm({
|
|
|
41062
41749
|
|
|
41063
41750
|
// src/commands/next.ts
|
|
41064
41751
|
import { Command as Command10 } from "commander";
|
|
41065
|
-
var
|
|
41752
|
+
var logger11, nextCommand;
|
|
41066
41753
|
var init_next = __esm({
|
|
41067
41754
|
"src/commands/next.ts"() {
|
|
41068
41755
|
"use strict";
|
|
@@ -41071,15 +41758,15 @@ var init_next = __esm({
|
|
|
41071
41758
|
init_http2();
|
|
41072
41759
|
init_session();
|
|
41073
41760
|
init_formatter();
|
|
41074
|
-
|
|
41761
|
+
logger11 = createLogger("cli:next");
|
|
41075
41762
|
nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41076
41763
|
try {
|
|
41077
41764
|
const session = await requireSession();
|
|
41078
|
-
const activeCourse = await requireActiveCourse();
|
|
41765
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41079
41766
|
const driftWarning = await checkCourseDrift();
|
|
41080
41767
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41081
41768
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41082
|
-
const deps = { data, logger:
|
|
41769
|
+
const deps = { data, logger: logger11 };
|
|
41083
41770
|
const lessonData = await nextLesson(
|
|
41084
41771
|
{ enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
|
|
41085
41772
|
deps
|
|
@@ -41160,7 +41847,7 @@ function formatLessonContent(data) {
|
|
|
41160
41847
|
}
|
|
41161
41848
|
return lines.join("\n");
|
|
41162
41849
|
}
|
|
41163
|
-
var
|
|
41850
|
+
var logger12, lessonCommand;
|
|
41164
41851
|
var init_lesson = __esm({
|
|
41165
41852
|
"src/commands/lesson.ts"() {
|
|
41166
41853
|
"use strict";
|
|
@@ -41170,15 +41857,15 @@ var init_lesson = __esm({
|
|
|
41170
41857
|
init_session();
|
|
41171
41858
|
init_formatter();
|
|
41172
41859
|
init_resolve();
|
|
41173
|
-
|
|
41860
|
+
logger12 = createLogger("cli:lesson");
|
|
41174
41861
|
lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41175
41862
|
try {
|
|
41176
41863
|
const session = await requireSession();
|
|
41177
|
-
const activeCourse = await requireActiveCourse();
|
|
41864
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41178
41865
|
const driftWarning = await checkCourseDrift();
|
|
41179
41866
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41180
41867
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41181
|
-
const deps = { data, logger:
|
|
41868
|
+
const deps = { data, logger: logger12 };
|
|
41182
41869
|
const lessonId = activeCourse.currentLessonId;
|
|
41183
41870
|
if (!lessonId) {
|
|
41184
41871
|
error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
|
|
@@ -41190,10 +41877,14 @@ var init_lesson = __esm({
|
|
|
41190
41877
|
output(formatLessonContent(content), { json: false });
|
|
41191
41878
|
}
|
|
41192
41879
|
if (content.type === "exercise") {
|
|
41193
|
-
const
|
|
41880
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
41881
|
+
const ws = await resolveEffectiveWorkspace(
|
|
41882
|
+
activeCourse.courseTitle,
|
|
41883
|
+
onboardingState?.workspacePath
|
|
41884
|
+
);
|
|
41194
41885
|
if (!ws.found) {
|
|
41195
41886
|
process.stderr.write(
|
|
41196
|
-
"\n\u{1F4A1} Dica: rode `tostudy
|
|
41887
|
+
"\n\u{1F4A1} Dica: rode `tostudy select` desta pasta para us\xE1-la como workspace, ou `tostudy workspace setup` para criar em ~/study/.\n"
|
|
41197
41888
|
);
|
|
41198
41889
|
}
|
|
41199
41890
|
}
|
|
@@ -41207,7 +41898,7 @@ var init_lesson = __esm({
|
|
|
41207
41898
|
|
|
41208
41899
|
// src/commands/hint.ts
|
|
41209
41900
|
import { Command as Command12 } from "commander";
|
|
41210
|
-
var
|
|
41901
|
+
var logger13, hintCommand;
|
|
41211
41902
|
var init_hint = __esm({
|
|
41212
41903
|
"src/commands/hint.ts"() {
|
|
41213
41904
|
"use strict";
|
|
@@ -41216,15 +41907,15 @@ var init_hint = __esm({
|
|
|
41216
41907
|
init_http2();
|
|
41217
41908
|
init_session();
|
|
41218
41909
|
init_formatter();
|
|
41219
|
-
|
|
41910
|
+
logger13 = createLogger("cli:hint");
|
|
41220
41911
|
hintCommand = new Command12("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41221
41912
|
try {
|
|
41222
41913
|
const session = await requireSession();
|
|
41223
|
-
const activeCourse = await requireActiveCourse();
|
|
41914
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41224
41915
|
const driftWarning = await checkCourseDrift();
|
|
41225
41916
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41226
41917
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41227
|
-
const deps = { data, logger:
|
|
41918
|
+
const deps = { data, logger: logger13 };
|
|
41228
41919
|
const hint = await getHint(
|
|
41229
41920
|
{ userId: session.userId, enrollmentId: activeCourse.enrollmentId },
|
|
41230
41921
|
deps
|
|
@@ -41270,10 +41961,10 @@ var init_exercises = __esm({
|
|
|
41270
41961
|
});
|
|
41271
41962
|
|
|
41272
41963
|
// src/commands/validate.ts
|
|
41273
|
-
import
|
|
41274
|
-
import
|
|
41964
|
+
import fs9 from "node:fs";
|
|
41965
|
+
import path9 from "node:path";
|
|
41275
41966
|
import { Command as Command13 } from "commander";
|
|
41276
|
-
var
|
|
41967
|
+
var logger14, validateCommand;
|
|
41277
41968
|
var init_validate = __esm({
|
|
41278
41969
|
"src/commands/validate.ts"() {
|
|
41279
41970
|
"use strict";
|
|
@@ -41283,11 +41974,11 @@ var init_validate = __esm({
|
|
|
41283
41974
|
init_session();
|
|
41284
41975
|
init_formatter();
|
|
41285
41976
|
init_init_template();
|
|
41286
|
-
|
|
41977
|
+
logger14 = createLogger("cli:validate");
|
|
41287
41978
|
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) => {
|
|
41288
41979
|
try {
|
|
41289
41980
|
const session = await requireSession();
|
|
41290
|
-
const activeCourse = await requireActiveCourse();
|
|
41981
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41291
41982
|
const driftWarning = await checkCourseDrift();
|
|
41292
41983
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41293
41984
|
const lessonId = activeCourse.currentLessonId;
|
|
@@ -41296,17 +41987,17 @@ var init_validate = __esm({
|
|
|
41296
41987
|
}
|
|
41297
41988
|
let solution;
|
|
41298
41989
|
if (opts.stdin) {
|
|
41299
|
-
solution =
|
|
41990
|
+
solution = fs9.readFileSync("/dev/stdin", "utf-8");
|
|
41300
41991
|
} else if (file2) {
|
|
41301
|
-
if (!
|
|
41992
|
+
if (!fs9.existsSync(file2)) {
|
|
41302
41993
|
error(`Arquivo n\xE3o encontrado: ${file2}`);
|
|
41303
41994
|
}
|
|
41304
|
-
solution =
|
|
41995
|
+
solution = fs9.readFileSync(file2, "utf-8");
|
|
41305
41996
|
} else {
|
|
41306
41997
|
error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
|
|
41307
41998
|
}
|
|
41308
41999
|
if (file2 && activeCourse.courseTags?.length) {
|
|
41309
|
-
const ext =
|
|
42000
|
+
const ext = path9.extname(file2).toLowerCase();
|
|
41310
42001
|
const LANG_EXTENSIONS = {
|
|
41311
42002
|
".html": ["html", "html5"],
|
|
41312
42003
|
".css": ["css"],
|
|
@@ -41342,7 +42033,7 @@ var init_validate = __esm({
|
|
|
41342
42033
|
}
|
|
41343
42034
|
}
|
|
41344
42035
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41345
|
-
const deps = { data, logger:
|
|
42036
|
+
const deps = { data, logger: logger14 };
|
|
41346
42037
|
const result = await validateSolution(
|
|
41347
42038
|
{
|
|
41348
42039
|
lessonId,
|
|
@@ -41431,9 +42122,9 @@ var init_menu = __esm({
|
|
|
41431
42122
|
});
|
|
41432
42123
|
|
|
41433
42124
|
// src/onboarding/learner-context.ts
|
|
41434
|
-
import
|
|
42125
|
+
import readline2 from "node:readline/promises";
|
|
41435
42126
|
import { stdin as input, stdout as output2 } from "node:process";
|
|
41436
|
-
async function
|
|
42127
|
+
async function askNonEmpty2(question, deps, defaultValue) {
|
|
41437
42128
|
while (true) {
|
|
41438
42129
|
const answer = (await deps.ask(question)).trim();
|
|
41439
42130
|
if (answer.length > 0) return answer;
|
|
@@ -41449,7 +42140,7 @@ async function askChoice(question, choices, deps, defaultChoice) {
|
|
|
41449
42140
|
}
|
|
41450
42141
|
}
|
|
41451
42142
|
function createPromptDeps() {
|
|
41452
|
-
const rl =
|
|
42143
|
+
const rl = readline2.createInterface({ input, output: output2 });
|
|
41453
42144
|
return {
|
|
41454
42145
|
ask: (question) => rl.question(question),
|
|
41455
42146
|
write: (content) => output2.write(content),
|
|
@@ -41482,32 +42173,32 @@ async function collectLearnerContextProfileWithDeps(input2, deps) {
|
|
|
41482
42173
|
"keep"
|
|
41483
42174
|
) : "edit";
|
|
41484
42175
|
const baseProfile = existingProfile;
|
|
41485
|
-
const segment = action === "keep" && baseProfile ? baseProfile.segment : await
|
|
42176
|
+
const segment = action === "keep" && baseProfile ? baseProfile.segment : await askNonEmpty2(
|
|
41486
42177
|
baseProfile ? `Segmento ou nicho do aluno [${baseProfile.segment}]: ` : "Segmento ou nicho do aluno: ",
|
|
41487
42178
|
deps,
|
|
41488
42179
|
baseProfile?.segment
|
|
41489
42180
|
);
|
|
41490
|
-
const company = action === "keep" && baseProfile ? baseProfile.company : await
|
|
42181
|
+
const company = action === "keep" && baseProfile ? baseProfile.company : await askNonEmpty2(
|
|
41491
42182
|
baseProfile ? `Empresa ou tipo de negocio [${baseProfile.company}]: ` : "Empresa ou tipo de negocio: ",
|
|
41492
42183
|
deps,
|
|
41493
42184
|
baseProfile?.company
|
|
41494
42185
|
);
|
|
41495
|
-
const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await
|
|
42186
|
+
const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await askNonEmpty2(
|
|
41496
42187
|
baseProfile ? `Produtos ou servicos principais [${baseProfile.productsOrServices}]: ` : "Produtos ou servicos principais: ",
|
|
41497
42188
|
deps,
|
|
41498
42189
|
baseProfile?.productsOrServices
|
|
41499
42190
|
);
|
|
41500
|
-
const region = action === "keep" && baseProfile ? baseProfile.region : await
|
|
42191
|
+
const region = action === "keep" && baseProfile ? baseProfile.region : await askNonEmpty2(
|
|
41501
42192
|
baseProfile ? `Regiao de atuacao [${baseProfile.region}]: ` : "Regiao de atuacao: ",
|
|
41502
42193
|
deps,
|
|
41503
42194
|
baseProfile?.region
|
|
41504
42195
|
);
|
|
41505
|
-
const team = action === "keep" && baseProfile ? baseProfile.team : await
|
|
42196
|
+
const team = action === "keep" && baseProfile ? baseProfile.team : await askNonEmpty2(
|
|
41506
42197
|
baseProfile ? `Equipe envolvida neste contexto [${baseProfile.team}]: ` : "Equipe envolvida neste contexto: ",
|
|
41507
42198
|
deps,
|
|
41508
42199
|
baseProfile?.team
|
|
41509
42200
|
);
|
|
41510
|
-
const goal = action === "keep" && baseProfile ? baseProfile.goal : await
|
|
42201
|
+
const goal = action === "keep" && baseProfile ? baseProfile.goal : await askNonEmpty2(
|
|
41511
42202
|
baseProfile ? `Objetivo principal com este curso [${baseProfile.goal}]: ` : "Objetivo principal com este curso: ",
|
|
41512
42203
|
deps,
|
|
41513
42204
|
baseProfile?.goal
|
|
@@ -41556,7 +42247,7 @@ function isCompleteProfile(flags) {
|
|
|
41556
42247
|
flags.segment && flags.company && flags.products && flags.region && flags.team && flags.goal && flags.level
|
|
41557
42248
|
);
|
|
41558
42249
|
}
|
|
41559
|
-
async function runInit(deps =
|
|
42250
|
+
async function runInit(deps = defaultDeps4, flags = {}) {
|
|
41560
42251
|
const session = await deps.getSession();
|
|
41561
42252
|
if (!session) {
|
|
41562
42253
|
deps.output("Nao autenticado. Rode `tostudy login` para comecar.", { json: false });
|
|
@@ -41671,22 +42362,20 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
|
|
|
41671
42362
|
});
|
|
41672
42363
|
await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
|
|
41673
42364
|
try {
|
|
41674
|
-
|
|
41675
|
-
{
|
|
41676
|
-
|
|
41677
|
-
|
|
41678
|
-
progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0,
|
|
41679
|
-
moduleCount: progressData?.currentModule?.totalModules ?? 0,
|
|
41680
|
-
lessonCount: progressData?.currentLesson?.totalLessons ?? 0,
|
|
41681
|
-
currentModuleTitle: progressData?.currentModule?.title,
|
|
41682
|
-
currentLessonTitle: progressData?.currentLesson?.title,
|
|
41683
|
-
courseDescription: matchedCourse.description ?? void 0
|
|
41684
|
-
},
|
|
41685
|
-
learnerProfile
|
|
42365
|
+
const pipelineResult = await deps.resolveAndGenerate(
|
|
42366
|
+
{ courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
|
|
42367
|
+
{ forceRefresh: false, collectT1Interactive: true, collectT2Interactive: false },
|
|
42368
|
+
{ cwd: process.cwd(), deps: deps.buildPipelineDeps() }
|
|
41686
42369
|
);
|
|
41687
|
-
deps.logger.info("Instruction files
|
|
42370
|
+
deps.logger.info("Instruction files regenerated via pipeline", {
|
|
42371
|
+
wroteFiles: pipelineResult.wroteFiles,
|
|
42372
|
+
usedT0: pipelineResult.usedT0,
|
|
42373
|
+
usedT1: pipelineResult.usedT1,
|
|
42374
|
+
usedT2: pipelineResult.usedT2,
|
|
42375
|
+
bootstrapped: pipelineResult.bootstrapped
|
|
42376
|
+
});
|
|
41688
42377
|
} catch (err) {
|
|
41689
|
-
deps.logger.warn("Failed to
|
|
42378
|
+
deps.logger.warn("Failed to run instruction pipeline", {
|
|
41690
42379
|
error: err instanceof Error ? err.message : String(err)
|
|
41691
42380
|
});
|
|
41692
42381
|
}
|
|
@@ -41702,7 +42391,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
|
|
|
41702
42391
|
deps.output(artifacts.learnerBrief, { json: false });
|
|
41703
42392
|
}
|
|
41704
42393
|
}
|
|
41705
|
-
var
|
|
42394
|
+
var logger15, defaultDeps4, initCommand;
|
|
41706
42395
|
var init_init = __esm({
|
|
41707
42396
|
"src/commands/init.ts"() {
|
|
41708
42397
|
"use strict";
|
|
@@ -41714,9 +42403,10 @@ var init_init = __esm({
|
|
|
41714
42403
|
init_init_template();
|
|
41715
42404
|
init_learner_context();
|
|
41716
42405
|
init_api();
|
|
41717
|
-
|
|
41718
|
-
|
|
41719
|
-
|
|
42406
|
+
init_instruction_pipeline();
|
|
42407
|
+
init_pipeline_deps();
|
|
42408
|
+
logger15 = createLogger("cli:init");
|
|
42409
|
+
defaultDeps4 = {
|
|
41720
42410
|
getSession,
|
|
41721
42411
|
getActiveCourse,
|
|
41722
42412
|
listCourses,
|
|
@@ -41729,24 +42419,26 @@ var init_init = __esm({
|
|
|
41729
42419
|
saveCourseLearnerProfile,
|
|
41730
42420
|
buildInitArtifacts,
|
|
41731
42421
|
output,
|
|
41732
|
-
logger:
|
|
41733
|
-
createHttpProvider
|
|
42422
|
+
logger: logger15,
|
|
42423
|
+
createHttpProvider,
|
|
42424
|
+
resolveAndGenerate,
|
|
42425
|
+
buildPipelineDeps
|
|
41734
42426
|
};
|
|
41735
42427
|
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) => {
|
|
41736
|
-
await runInit(
|
|
42428
|
+
await runInit(defaultDeps4, opts);
|
|
41737
42429
|
});
|
|
41738
42430
|
}
|
|
41739
42431
|
});
|
|
41740
42432
|
|
|
41741
42433
|
// ../../packages/tostudy-core/src/workspace/setup-workspace.ts
|
|
41742
|
-
import
|
|
41743
|
-
import
|
|
42434
|
+
import fs10 from "node:fs/promises";
|
|
42435
|
+
import path10 from "node:path";
|
|
41744
42436
|
async function setupWorkspace(input2) {
|
|
41745
|
-
const workspacePath =
|
|
42437
|
+
const workspacePath = path10.join(input2.basePath, input2.courseSlug);
|
|
41746
42438
|
for (const dir of WORKSPACE_DIRS) {
|
|
41747
|
-
await
|
|
42439
|
+
await fs10.mkdir(path10.join(workspacePath, dir), { recursive: true });
|
|
41748
42440
|
}
|
|
41749
|
-
const configPath =
|
|
42441
|
+
const configPath = path10.join(workspacePath, ".ana-config.json");
|
|
41750
42442
|
const config2 = {
|
|
41751
42443
|
courseId: input2.courseId,
|
|
41752
42444
|
courseSlug: input2.courseSlug,
|
|
@@ -41756,7 +42448,7 @@ async function setupWorkspace(input2) {
|
|
|
41756
42448
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41757
42449
|
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41758
42450
|
};
|
|
41759
|
-
await
|
|
42451
|
+
await fs10.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
41760
42452
|
const readme = [
|
|
41761
42453
|
`# ${input2.courseName}`,
|
|
41762
42454
|
"",
|
|
@@ -41781,7 +42473,7 @@ async function setupWorkspace(input2) {
|
|
|
41781
42473
|
"tostudy vault sync # Sincronizar progresso",
|
|
41782
42474
|
"```"
|
|
41783
42475
|
].join("\n");
|
|
41784
|
-
await
|
|
42476
|
+
await fs10.writeFile(path10.join(workspacePath, "README.md"), readme, "utf-8");
|
|
41785
42477
|
return { workspacePath, directories: WORKSPACE_DIRS, configPath };
|
|
41786
42478
|
}
|
|
41787
42479
|
var WORKSPACE_DIRS;
|
|
@@ -41882,8 +42574,8 @@ var init_templates = __esm({
|
|
|
41882
42574
|
});
|
|
41883
42575
|
|
|
41884
42576
|
// ../../packages/tostudy-core/src/workspace/extract-exercise.ts
|
|
41885
|
-
import
|
|
41886
|
-
import
|
|
42577
|
+
import fs11 from "node:fs/promises";
|
|
42578
|
+
import path11 from "node:path";
|
|
41887
42579
|
function padOrder(n) {
|
|
41888
42580
|
return String(n).padStart(2, "0");
|
|
41889
42581
|
}
|
|
@@ -41907,16 +42599,16 @@ async function extractExercise(input2) {
|
|
|
41907
42599
|
const { lessonData, exerciseTier, workspacePath } = input2;
|
|
41908
42600
|
const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
|
|
41909
42601
|
const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
|
|
41910
|
-
const exercisePath =
|
|
41911
|
-
await
|
|
42602
|
+
const exercisePath = path11.join(workspacePath, "exercises", moduleDir, lessonDir);
|
|
42603
|
+
await fs11.mkdir(exercisePath, { recursive: true });
|
|
41912
42604
|
const extractedFiles = [];
|
|
41913
42605
|
let hasStarterCode = false;
|
|
41914
42606
|
if (lessonData.sandpackConfig?.files) {
|
|
41915
42607
|
for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
|
|
41916
42608
|
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
41917
|
-
const fullPath =
|
|
41918
|
-
await
|
|
41919
|
-
await
|
|
42609
|
+
const fullPath = path11.join(exercisePath, cleanPath);
|
|
42610
|
+
await fs11.mkdir(path11.dirname(fullPath), { recursive: true });
|
|
42611
|
+
await fs11.writeFile(fullPath, fileData.code, "utf-8");
|
|
41920
42612
|
extractedFiles.push(cleanPath);
|
|
41921
42613
|
hasStarterCode = true;
|
|
41922
42614
|
}
|
|
@@ -41924,13 +42616,13 @@ async function extractExercise(input2) {
|
|
|
41924
42616
|
const tierData = getTierData(lessonData.structuredData, exerciseTier);
|
|
41925
42617
|
const tierCode = tierData?.code;
|
|
41926
42618
|
if (tierCode) {
|
|
41927
|
-
await
|
|
42619
|
+
await fs11.writeFile(path11.join(exercisePath, "exercise.js"), tierCode, "utf-8");
|
|
41928
42620
|
extractedFiles.push("exercise.js");
|
|
41929
42621
|
hasStarterCode = true;
|
|
41930
42622
|
} else {
|
|
41931
42623
|
const starter = getStarterCode(lessonData.structuredData);
|
|
41932
42624
|
if (starter) {
|
|
41933
|
-
await
|
|
42625
|
+
await fs11.writeFile(path11.join(exercisePath, "exercise.js"), starter, "utf-8");
|
|
41934
42626
|
extractedFiles.push("exercise.js");
|
|
41935
42627
|
hasStarterCode = true;
|
|
41936
42628
|
}
|
|
@@ -41948,8 +42640,8 @@ async function extractExercise(input2) {
|
|
|
41948
42640
|
...exerciseDeps
|
|
41949
42641
|
}
|
|
41950
42642
|
};
|
|
41951
|
-
await
|
|
41952
|
-
|
|
42643
|
+
await fs11.writeFile(
|
|
42644
|
+
path11.join(exercisePath, "package.json"),
|
|
41953
42645
|
JSON.stringify(pkgJson, null, 2),
|
|
41954
42646
|
"utf-8"
|
|
41955
42647
|
);
|
|
@@ -41961,20 +42653,20 @@ async function extractExercise(input2) {
|
|
|
41961
42653
|
);
|
|
41962
42654
|
for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
|
|
41963
42655
|
if (!sandpackFileNames.has(configFile)) {
|
|
41964
|
-
await
|
|
42656
|
+
await fs11.writeFile(path11.join(exercisePath, configFile), configContent, "utf-8");
|
|
41965
42657
|
extractedFiles.push(configFile);
|
|
41966
42658
|
}
|
|
41967
42659
|
}
|
|
41968
42660
|
const setupSh = `#!/bin/sh
|
|
41969
42661
|
${scaffold.setupScript}
|
|
41970
42662
|
`;
|
|
41971
|
-
await
|
|
42663
|
+
await fs11.writeFile(path11.join(exercisePath, "setup.sh"), setupSh, "utf-8");
|
|
41972
42664
|
extractedFiles.push("setup.sh");
|
|
41973
42665
|
}
|
|
41974
42666
|
}
|
|
41975
42667
|
const readme = generateReadme(lessonData, exerciseTier);
|
|
41976
|
-
const readmePath =
|
|
41977
|
-
await
|
|
42668
|
+
const readmePath = path11.join(exercisePath, "README.md");
|
|
42669
|
+
await fs11.writeFile(readmePath, readme, "utf-8");
|
|
41978
42670
|
extractedFiles.push("README.md");
|
|
41979
42671
|
return {
|
|
41980
42672
|
exercisePath,
|
|
@@ -42046,109 +42738,151 @@ var init_workspace = __esm({
|
|
|
42046
42738
|
|
|
42047
42739
|
// src/commands/workspace.ts
|
|
42048
42740
|
import { Command as Command16 } from "commander";
|
|
42049
|
-
import
|
|
42050
|
-
import
|
|
42051
|
-
import
|
|
42052
|
-
var
|
|
42741
|
+
import path12 from "node:path";
|
|
42742
|
+
import os11 from "node:os";
|
|
42743
|
+
import fs12 from "node:fs/promises";
|
|
42744
|
+
var logger16, workspaceCommand;
|
|
42053
42745
|
var init_workspace2 = __esm({
|
|
42054
42746
|
"src/commands/workspace.ts"() {
|
|
42055
42747
|
"use strict";
|
|
42056
42748
|
init_src();
|
|
42057
42749
|
init_workspace();
|
|
42058
42750
|
init_session();
|
|
42059
|
-
|
|
42751
|
+
init_resolve();
|
|
42752
|
+
logger16 = createLogger("cli:workspace");
|
|
42060
42753
|
workspaceCommand = new Command16("workspace").description(
|
|
42061
42754
|
"Gerenciar workspace de estudo local"
|
|
42062
42755
|
);
|
|
42063
|
-
workspaceCommand.command("setup").description("Criar estrutura do workspace para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace
|
|
42756
|
+
workspaceCommand.command("setup").description("Criar estrutura do workspace para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace (omita para usar a pasta atual)").option("--json", "Output structured JSON").action(async (opts) => {
|
|
42064
42757
|
try {
|
|
42065
42758
|
await requireSession();
|
|
42066
42759
|
const activeCourse = await requireActiveCourse();
|
|
42067
|
-
const
|
|
42068
|
-
|
|
42069
|
-
|
|
42070
|
-
|
|
42071
|
-
|
|
42072
|
-
|
|
42073
|
-
|
|
42074
|
-
|
|
42760
|
+
const cwdIsWorkspace = await isCwdWorkspace(process.cwd());
|
|
42761
|
+
let workspacePath;
|
|
42762
|
+
let directories;
|
|
42763
|
+
if (!opts.path && cwdIsWorkspace) {
|
|
42764
|
+
const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
|
|
42765
|
+
workspacePath = resolvedCwd ?? path12.join(process.cwd(), ".tostudy");
|
|
42766
|
+
await fs12.mkdir(workspacePath, { recursive: true });
|
|
42767
|
+
directories = ["exercises", "generated", "notes", "diagrams"];
|
|
42768
|
+
for (const dir of directories) {
|
|
42769
|
+
await fs12.mkdir(path12.join(workspacePath, dir), { recursive: true });
|
|
42770
|
+
}
|
|
42771
|
+
const configPath = path12.join(workspacePath, ".ana-config.json");
|
|
42772
|
+
await fs12.writeFile(
|
|
42773
|
+
configPath,
|
|
42774
|
+
JSON.stringify(
|
|
42775
|
+
{
|
|
42776
|
+
courseId: activeCourse.courseId,
|
|
42777
|
+
courseSlug: courseSlug(activeCourse.courseTitle),
|
|
42778
|
+
courseName: activeCourse.courseTitle,
|
|
42779
|
+
workspacePath,
|
|
42780
|
+
locale: "pt-BR",
|
|
42781
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42782
|
+
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42783
|
+
},
|
|
42784
|
+
null,
|
|
42785
|
+
2
|
|
42786
|
+
),
|
|
42787
|
+
"utf-8"
|
|
42788
|
+
);
|
|
42789
|
+
} else {
|
|
42790
|
+
const basePath = opts.path ?? path12.join(os11.homedir(), "study");
|
|
42791
|
+
const result2 = await setupWorkspace({
|
|
42792
|
+
courseId: activeCourse.courseId,
|
|
42793
|
+
courseSlug: courseSlug(activeCourse.courseTitle),
|
|
42794
|
+
courseName: activeCourse.courseTitle,
|
|
42795
|
+
basePath,
|
|
42796
|
+
locale: "pt-BR"
|
|
42797
|
+
});
|
|
42798
|
+
workspacePath = result2.workspacePath;
|
|
42799
|
+
directories = result2.directories;
|
|
42800
|
+
}
|
|
42801
|
+
await setCourseWorkspacePath(activeCourse.courseId, workspacePath);
|
|
42802
|
+
const result = { workspacePath, directories };
|
|
42075
42803
|
if (opts.json) {
|
|
42076
42804
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
42077
42805
|
} else {
|
|
42078
42806
|
process.stdout.write(
|
|
42079
42807
|
`
|
|
42080
|
-
\u2705 Workspace criado em: ${
|
|
42808
|
+
\u2705 Workspace criado em: ${workspacePath}
|
|
42081
42809
|
|
|
42082
42810
|
Diret\xF3rios:
|
|
42083
|
-
${
|
|
42811
|
+
${directories.map((d) => ` \u{1F4C1} ${d}/`).join("\n")}
|
|
42084
42812
|
|
|
42085
42813
|
Pr\xF3ximo passo: tostudy export
|
|
42086
42814
|
`
|
|
42087
42815
|
);
|
|
42088
42816
|
}
|
|
42089
42817
|
} catch (err) {
|
|
42090
|
-
|
|
42818
|
+
logger16.error("workspace setup failed", { error: err });
|
|
42091
42819
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42092
42820
|
`);
|
|
42093
42821
|
process.exit(1);
|
|
42094
42822
|
}
|
|
42095
42823
|
});
|
|
42096
|
-
workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
42824
|
+
workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path12.join(os11.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42097
42825
|
try {
|
|
42098
42826
|
const activeCourse = await requireActiveCourse();
|
|
42099
|
-
const
|
|
42100
|
-
const
|
|
42101
|
-
|
|
42102
|
-
|
|
42103
|
-
|
|
42104
|
-
|
|
42105
|
-
|
|
42827
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42828
|
+
const ws = await resolveEffectiveWorkspace(
|
|
42829
|
+
activeCourse.courseTitle,
|
|
42830
|
+
onboardingState?.workspacePath,
|
|
42831
|
+
process.cwd(),
|
|
42832
|
+
opts.path
|
|
42833
|
+
);
|
|
42834
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42106
42835
|
process.stderr.write(
|
|
42107
42836
|
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
42108
42837
|
);
|
|
42109
42838
|
process.exit(1);
|
|
42110
42839
|
}
|
|
42111
|
-
const
|
|
42840
|
+
const workspacePath = ws.workspacePath;
|
|
42841
|
+
let configData = null;
|
|
42842
|
+
try {
|
|
42843
|
+
const raw = await fs12.readFile(path12.join(workspacePath, ".ana-config.json"), "utf-8");
|
|
42844
|
+
configData = JSON.parse(raw);
|
|
42845
|
+
} catch {
|
|
42846
|
+
configData = null;
|
|
42847
|
+
}
|
|
42848
|
+
const exercisesDir = path12.join(workspacePath, "exercises");
|
|
42112
42849
|
let exerciseCount = 0;
|
|
42113
42850
|
try {
|
|
42114
|
-
const moduleDirs = await
|
|
42851
|
+
const moduleDirs = await fs12.readdir(exercisesDir);
|
|
42115
42852
|
for (const modDir of moduleDirs) {
|
|
42116
|
-
const modPath =
|
|
42117
|
-
const stat = await
|
|
42853
|
+
const modPath = path12.join(exercisesDir, modDir);
|
|
42854
|
+
const stat = await fs12.stat(modPath);
|
|
42118
42855
|
if (stat.isDirectory()) {
|
|
42119
|
-
const lessonDirs = await
|
|
42856
|
+
const lessonDirs = await fs12.readdir(modPath);
|
|
42120
42857
|
for (const lessonDir of lessonDirs) {
|
|
42121
|
-
const lessonPath =
|
|
42122
|
-
const lstat = await
|
|
42858
|
+
const lessonPath = path12.join(modPath, lessonDir);
|
|
42859
|
+
const lstat = await fs12.stat(lessonPath);
|
|
42123
42860
|
if (lstat.isDirectory()) exerciseCount++;
|
|
42124
42861
|
}
|
|
42125
42862
|
}
|
|
42126
42863
|
}
|
|
42127
42864
|
} catch {
|
|
42128
42865
|
}
|
|
42129
|
-
const generatedDir =
|
|
42866
|
+
const generatedDir = path12.join(workspacePath, "generated");
|
|
42130
42867
|
let artifactCount = 0;
|
|
42131
42868
|
try {
|
|
42132
|
-
const files = await
|
|
42869
|
+
const files = await fs12.readdir(generatedDir);
|
|
42133
42870
|
artifactCount = files.length;
|
|
42134
42871
|
} catch {
|
|
42135
42872
|
}
|
|
42136
|
-
const diagramsDir =
|
|
42873
|
+
const diagramsDir = path12.join(workspacePath, "diagrams");
|
|
42137
42874
|
let diagramCount = 0;
|
|
42138
42875
|
try {
|
|
42139
|
-
const files = await
|
|
42876
|
+
const files = await fs12.readdir(diagramsDir);
|
|
42140
42877
|
diagramCount = files.length;
|
|
42141
42878
|
} catch {
|
|
42142
42879
|
}
|
|
42143
|
-
const
|
|
42144
|
-
|
|
42145
|
-
|
|
42146
|
-
await fs11.access(path10.join(vaultDir, ".ana-vault.json"));
|
|
42147
|
-
hasVault = true;
|
|
42148
|
-
} catch {
|
|
42149
|
-
}
|
|
42880
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
42881
|
+
const foundVaultPath = await findExistingVault(workspacePath, slug);
|
|
42882
|
+
const hasVault = foundVaultPath !== null;
|
|
42150
42883
|
const status = {
|
|
42151
42884
|
workspacePath,
|
|
42885
|
+
workspaceSource: ws.source,
|
|
42152
42886
|
course: activeCourse.courseTitle,
|
|
42153
42887
|
courseId: activeCourse.courseId,
|
|
42154
42888
|
exercisesExtracted: exerciseCount,
|
|
@@ -42160,11 +42894,12 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42160
42894
|
if (opts.json) {
|
|
42161
42895
|
process.stdout.write(JSON.stringify(status, null, 2) + "\n");
|
|
42162
42896
|
} else {
|
|
42897
|
+
const sourceLabel = ws.source === "cwd" ? " (pasta atual)" : ws.source === "stored" ? " (configurado)" : ws.source === "default" ? " (~/study/)" : "";
|
|
42163
42898
|
process.stdout.write(
|
|
42164
42899
|
[
|
|
42165
42900
|
"",
|
|
42166
42901
|
`\u{1F4DA} **${activeCourse.courseTitle}**`,
|
|
42167
|
-
`\u{1F4C1} ${workspacePath}`,
|
|
42902
|
+
`\u{1F4C1} ${workspacePath}${sourceLabel}`,
|
|
42168
42903
|
"",
|
|
42169
42904
|
` Exerc\xEDcios extra\xEDdos: ${exerciseCount}`,
|
|
42170
42905
|
` Artefatos exportados: ${artifactCount}`,
|
|
@@ -42187,9 +42922,10 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42187
42922
|
|
|
42188
42923
|
// src/commands/export.ts
|
|
42189
42924
|
import { Command as Command17 } from "commander";
|
|
42190
|
-
import
|
|
42191
|
-
import
|
|
42192
|
-
|
|
42925
|
+
import path13 from "node:path";
|
|
42926
|
+
import os12 from "node:os";
|
|
42927
|
+
import fs13 from "node:fs/promises";
|
|
42928
|
+
var logger17, exportCommand;
|
|
42193
42929
|
var init_export = __esm({
|
|
42194
42930
|
"src/commands/export.ts"() {
|
|
42195
42931
|
"use strict";
|
|
@@ -42198,8 +42934,8 @@ var init_export = __esm({
|
|
|
42198
42934
|
init_http2();
|
|
42199
42935
|
init_session();
|
|
42200
42936
|
init_resolve();
|
|
42201
|
-
|
|
42202
|
-
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",
|
|
42937
|
+
logger17 = createLogger("cli:export");
|
|
42938
|
+
exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42203
42939
|
try {
|
|
42204
42940
|
const session = await requireSession();
|
|
42205
42941
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42209,10 +42945,57 @@ var init_export = __esm({
|
|
|
42209
42945
|
process.stderr.write("\u274C Nenhuma li\xE7\xE3o ativa. Execute 'tostudy start' primeiro.\n");
|
|
42210
42946
|
process.exit(1);
|
|
42211
42947
|
}
|
|
42212
|
-
const
|
|
42213
|
-
|
|
42948
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42949
|
+
const ws = await resolveEffectiveWorkspace(
|
|
42950
|
+
activeCourse.courseTitle,
|
|
42951
|
+
onboardingState?.workspacePath,
|
|
42952
|
+
process.cwd(),
|
|
42953
|
+
opts.path
|
|
42954
|
+
);
|
|
42955
|
+
if (ws.found && ws.source === "cwd" && ws.workspacePath) {
|
|
42956
|
+
const configPath = path13.join(ws.workspacePath, ".ana-config.json");
|
|
42957
|
+
let hasConfig = false;
|
|
42958
|
+
try {
|
|
42959
|
+
await fs13.access(configPath);
|
|
42960
|
+
hasConfig = true;
|
|
42961
|
+
} catch {
|
|
42962
|
+
}
|
|
42963
|
+
if (!hasConfig) {
|
|
42964
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
42965
|
+
logger17.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
|
|
42966
|
+
await fs13.mkdir(ws.workspacePath, { recursive: true });
|
|
42967
|
+
for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
|
|
42968
|
+
await fs13.mkdir(path13.join(ws.workspacePath, dir), { recursive: true });
|
|
42969
|
+
}
|
|
42970
|
+
await fs13.writeFile(
|
|
42971
|
+
configPath,
|
|
42972
|
+
JSON.stringify(
|
|
42973
|
+
{
|
|
42974
|
+
courseId: activeCourse.courseId,
|
|
42975
|
+
courseSlug: slug,
|
|
42976
|
+
courseName: activeCourse.courseTitle,
|
|
42977
|
+
workspacePath: ws.workspacePath,
|
|
42978
|
+
locale: "pt-BR",
|
|
42979
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42980
|
+
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42981
|
+
},
|
|
42982
|
+
null,
|
|
42983
|
+
2
|
|
42984
|
+
),
|
|
42985
|
+
"utf-8"
|
|
42986
|
+
);
|
|
42987
|
+
await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
|
|
42988
|
+
const isNamespaced = ws.workspacePath === path13.join(process.cwd(), ".tostudy");
|
|
42989
|
+
process.stderr.write(
|
|
42990
|
+
isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
|
|
42991
|
+
` : `\u2728 Workspace inicializado nesta pasta.
|
|
42992
|
+
`
|
|
42993
|
+
);
|
|
42994
|
+
}
|
|
42995
|
+
}
|
|
42996
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42214
42997
|
process.stderr.write(
|
|
42215
|
-
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup'
|
|
42998
|
+
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' ou rode 'tostudy select' desta pasta.\n"
|
|
42216
42999
|
);
|
|
42217
43000
|
process.exit(1);
|
|
42218
43001
|
}
|
|
@@ -42243,7 +43026,7 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
|
|
|
42243
43026
|
);
|
|
42244
43027
|
}
|
|
42245
43028
|
} catch (err) {
|
|
42246
|
-
|
|
43029
|
+
logger17.error("export failed", { error: err });
|
|
42247
43030
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42248
43031
|
`);
|
|
42249
43032
|
process.exit(1);
|
|
@@ -42255,49 +43038,45 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
|
|
|
42255
43038
|
// src/commands/open.ts
|
|
42256
43039
|
import { Command as Command18 } from "commander";
|
|
42257
43040
|
import { execFile as execFile3 } from "node:child_process";
|
|
42258
|
-
import
|
|
42259
|
-
import
|
|
42260
|
-
|
|
42261
|
-
async function findWorkspacePath(courseTitle, basePath) {
|
|
42262
|
-
const slug = courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
42263
|
-
const candidate = path12.join(basePath, slug);
|
|
42264
|
-
try {
|
|
42265
|
-
await fs12.access(path12.join(candidate, ".ana-config.json"));
|
|
42266
|
-
return candidate;
|
|
42267
|
-
} catch {
|
|
42268
|
-
return null;
|
|
42269
|
-
}
|
|
42270
|
-
}
|
|
42271
|
-
var logger16, openCommand;
|
|
43041
|
+
import path14 from "node:path";
|
|
43042
|
+
import os13 from "node:os";
|
|
43043
|
+
var logger18, openCommand;
|
|
42272
43044
|
var init_open = __esm({
|
|
42273
43045
|
"src/commands/open.ts"() {
|
|
42274
43046
|
"use strict";
|
|
42275
43047
|
init_src();
|
|
42276
43048
|
init_session();
|
|
42277
|
-
|
|
42278
|
-
|
|
43049
|
+
init_resolve();
|
|
43050
|
+
logger18 = createLogger("cli:open");
|
|
43051
|
+
openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os13.homedir(), "study")).action(async (opts) => {
|
|
42279
43052
|
try {
|
|
42280
43053
|
const activeCourse = await requireActiveCourse();
|
|
42281
|
-
const
|
|
42282
|
-
|
|
43054
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
43055
|
+
const ws = await resolveEffectiveWorkspace(
|
|
43056
|
+
activeCourse.courseTitle,
|
|
43057
|
+
onboardingState?.workspacePath,
|
|
43058
|
+
process.cwd(),
|
|
43059
|
+
opts.path
|
|
43060
|
+
);
|
|
43061
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42283
43062
|
process.stderr.write(
|
|
42284
43063
|
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
42285
43064
|
);
|
|
42286
43065
|
process.exit(1);
|
|
42287
43066
|
}
|
|
42288
43067
|
const editor = process.env["EDITOR"] ?? "code";
|
|
42289
|
-
execFile3(editor, [workspacePath], (err) => {
|
|
43068
|
+
execFile3(editor, [ws.workspacePath], (err) => {
|
|
42290
43069
|
if (err) {
|
|
42291
|
-
|
|
43070
|
+
logger18.error("open failed", { editor, workspacePath: ws.workspacePath });
|
|
42292
43071
|
process.stderr.write(`\u274C Falha ao abrir: ${err.message}
|
|
42293
43072
|
`);
|
|
42294
43073
|
process.exit(1);
|
|
42295
43074
|
}
|
|
42296
|
-
process.stdout.write(`\u2705 Aberto em ${editor}: ${workspacePath}
|
|
43075
|
+
process.stdout.write(`\u2705 Aberto em ${editor}: ${ws.workspacePath}
|
|
42297
43076
|
`);
|
|
42298
43077
|
});
|
|
42299
43078
|
} catch (err) {
|
|
42300
|
-
|
|
43079
|
+
logger18.error("open command failed", { error: err });
|
|
42301
43080
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42302
43081
|
`);
|
|
42303
43082
|
process.exit(1);
|
|
@@ -42324,24 +43103,24 @@ var init_types3 = __esm({
|
|
|
42324
43103
|
});
|
|
42325
43104
|
|
|
42326
43105
|
// ../../packages/tostudy-core/src/vault/write-vault.ts
|
|
42327
|
-
import
|
|
42328
|
-
import
|
|
43106
|
+
import fs14 from "node:fs/promises";
|
|
43107
|
+
import path15 from "node:path";
|
|
42329
43108
|
async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
|
|
42330
43109
|
for (const file2 of files) {
|
|
42331
|
-
const fullPath =
|
|
42332
|
-
await
|
|
42333
|
-
await
|
|
43110
|
+
const fullPath = path15.join(outputPath, file2.relativePath);
|
|
43111
|
+
await fs14.mkdir(path15.dirname(fullPath), { recursive: true });
|
|
43112
|
+
await fs14.writeFile(fullPath, file2.content, "utf-8");
|
|
42334
43113
|
}
|
|
42335
|
-
const vaultPath =
|
|
43114
|
+
const vaultPath = path15.join(outputPath, courseSlug2);
|
|
42336
43115
|
const marker = {
|
|
42337
43116
|
courseId,
|
|
42338
43117
|
courseSlug: courseSlug2,
|
|
42339
43118
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42340
43119
|
version: VAULT_MARKER_VERSION
|
|
42341
43120
|
};
|
|
42342
|
-
await
|
|
42343
|
-
await
|
|
42344
|
-
|
|
43121
|
+
await fs14.mkdir(vaultPath, { recursive: true });
|
|
43122
|
+
await fs14.writeFile(
|
|
43123
|
+
path15.join(vaultPath, VAULT_MARKER_FILENAME),
|
|
42345
43124
|
JSON.stringify(marker, null, 2),
|
|
42346
43125
|
"utf-8"
|
|
42347
43126
|
);
|
|
@@ -42366,10 +43145,10 @@ var init_vault = __esm({
|
|
|
42366
43145
|
|
|
42367
43146
|
// src/commands/vault.ts
|
|
42368
43147
|
import { Command as Command19 } from "commander";
|
|
42369
|
-
import
|
|
42370
|
-
import
|
|
42371
|
-
import
|
|
42372
|
-
var
|
|
43148
|
+
import path16 from "node:path";
|
|
43149
|
+
import os14 from "node:os";
|
|
43150
|
+
import fs15 from "node:fs/promises";
|
|
43151
|
+
var logger19, vaultCommand;
|
|
42373
43152
|
var init_vault2 = __esm({
|
|
42374
43153
|
"src/commands/vault.ts"() {
|
|
42375
43154
|
"use strict";
|
|
@@ -42378,17 +43157,31 @@ var init_vault2 = __esm({
|
|
|
42378
43157
|
init_courses();
|
|
42379
43158
|
init_http2();
|
|
42380
43159
|
init_session();
|
|
42381
|
-
|
|
43160
|
+
init_resolve();
|
|
43161
|
+
logger19 = createLogger("cli:vault");
|
|
42382
43162
|
vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
|
|
42383
|
-
vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
43163
|
+
vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path16.join(os14.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42384
43164
|
try {
|
|
42385
43165
|
const session = await requireSession();
|
|
42386
43166
|
const activeCourse = await requireActiveCourse();
|
|
42387
43167
|
const driftWarning = await checkCourseDrift();
|
|
42388
43168
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
42389
|
-
const
|
|
42390
|
-
const
|
|
42391
|
-
|
|
43169
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
43170
|
+
const ws = await resolveEffectiveWorkspace(
|
|
43171
|
+
activeCourse.courseTitle,
|
|
43172
|
+
onboardingState?.workspacePath,
|
|
43173
|
+
process.cwd(),
|
|
43174
|
+
opts.path
|
|
43175
|
+
);
|
|
43176
|
+
if (!ws.found || !ws.workspacePath) {
|
|
43177
|
+
process.stderr.write(
|
|
43178
|
+
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
43179
|
+
);
|
|
43180
|
+
process.exit(1);
|
|
43181
|
+
}
|
|
43182
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
43183
|
+
const workspacePath = ws.workspacePath;
|
|
43184
|
+
const vaultOutputPath = resolveVaultPath(workspacePath, slug);
|
|
42392
43185
|
const res = await fetch(`${session.apiUrl}/api/cli/vault/init`, {
|
|
42393
43186
|
method: "POST",
|
|
42394
43187
|
headers: {
|
|
@@ -42410,9 +43203,9 @@ var init_vault2 = __esm({
|
|
|
42410
43203
|
data.files,
|
|
42411
43204
|
vaultOutputPath,
|
|
42412
43205
|
activeCourse.courseId,
|
|
42413
|
-
|
|
43206
|
+
slug
|
|
42414
43207
|
);
|
|
42415
|
-
|
|
43208
|
+
logger19.info("Vault generated", {
|
|
42416
43209
|
courseId: activeCourse.courseId,
|
|
42417
43210
|
vaultPath: result.vaultPath,
|
|
42418
43211
|
filesWritten: result.filesWritten
|
|
@@ -42445,31 +43238,42 @@ Para visualizar:
|
|
|
42445
43238
|
);
|
|
42446
43239
|
}
|
|
42447
43240
|
} catch (err) {
|
|
42448
|
-
|
|
43241
|
+
logger19.error("vault init failed", { error: err });
|
|
42449
43242
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42450
43243
|
`);
|
|
42451
43244
|
process.exit(1);
|
|
42452
43245
|
}
|
|
42453
43246
|
});
|
|
42454
|
-
vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
43247
|
+
vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path16.join(os14.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42455
43248
|
try {
|
|
42456
43249
|
const session = await requireSession();
|
|
42457
43250
|
const activeCourse = await requireActiveCourse();
|
|
42458
43251
|
const driftWarning = await checkCourseDrift();
|
|
42459
43252
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
42460
|
-
const
|
|
42461
|
-
const
|
|
42462
|
-
|
|
42463
|
-
|
|
42464
|
-
|
|
43253
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
43254
|
+
const ws = await resolveEffectiveWorkspace(
|
|
43255
|
+
activeCourse.courseTitle,
|
|
43256
|
+
onboardingState?.workspacePath,
|
|
43257
|
+
process.cwd(),
|
|
43258
|
+
opts.path
|
|
43259
|
+
);
|
|
43260
|
+
if (!ws.found || !ws.workspacePath) {
|
|
43261
|
+
process.stderr.write(
|
|
43262
|
+
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
43263
|
+
);
|
|
43264
|
+
process.exit(1);
|
|
43265
|
+
}
|
|
43266
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
43267
|
+
const vaultPath = await findExistingVault(ws.workspacePath, slug);
|
|
43268
|
+
if (!vaultPath) {
|
|
42465
43269
|
process.stderr.write("\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n");
|
|
42466
43270
|
process.exit(1);
|
|
42467
43271
|
}
|
|
42468
43272
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
42469
|
-
const deps = { data, logger:
|
|
43273
|
+
const deps = { data, logger: logger19 };
|
|
42470
43274
|
const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
42471
|
-
const markerPath =
|
|
42472
|
-
const markerRaw = await
|
|
43275
|
+
const markerPath = path16.join(vaultPath, ".ana-vault.json");
|
|
43276
|
+
const markerRaw = await fs15.readFile(markerPath, "utf-8");
|
|
42473
43277
|
const marker = JSON.parse(markerRaw);
|
|
42474
43278
|
marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42475
43279
|
marker.progress = {
|
|
@@ -42477,10 +43281,10 @@ Para visualizar:
|
|
|
42477
43281
|
currentModule: progress3.currentModule.title,
|
|
42478
43282
|
currentLesson: progress3.currentLesson.title
|
|
42479
43283
|
};
|
|
42480
|
-
await
|
|
42481
|
-
const courseIndexPath =
|
|
43284
|
+
await fs15.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
43285
|
+
const courseIndexPath = path16.join(vaultPath, slug, "index.md");
|
|
42482
43286
|
try {
|
|
42483
|
-
let indexContent = await
|
|
43287
|
+
let indexContent = await fs15.readFile(courseIndexPath, "utf-8");
|
|
42484
43288
|
indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
|
|
42485
43289
|
const titleEnd = indexContent.indexOf("\n");
|
|
42486
43290
|
if (titleEnd !== -1) {
|
|
@@ -42491,7 +43295,7 @@ Para visualizar:
|
|
|
42491
43295
|
`;
|
|
42492
43296
|
indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
|
|
42493
43297
|
}
|
|
42494
|
-
await
|
|
43298
|
+
await fs15.writeFile(courseIndexPath, indexContent, "utf-8");
|
|
42495
43299
|
} catch {
|
|
42496
43300
|
}
|
|
42497
43301
|
const syncedAt = marker.lastSyncedAt;
|
|
@@ -42520,7 +43324,7 @@ Para visualizar:
|
|
|
42520
43324
|
);
|
|
42521
43325
|
}
|
|
42522
43326
|
} catch (err) {
|
|
42523
|
-
|
|
43327
|
+
logger19.error("vault sync failed", { error: err });
|
|
42524
43328
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42525
43329
|
`);
|
|
42526
43330
|
process.exit(1);
|
|
@@ -42596,15 +43400,206 @@ var init_profile = __esm({
|
|
|
42596
43400
|
}
|
|
42597
43401
|
});
|
|
42598
43402
|
|
|
43403
|
+
// src/commands/sync.ts
|
|
43404
|
+
import { Command as Command21 } from "commander";
|
|
43405
|
+
var logger20, syncCommand;
|
|
43406
|
+
var init_sync = __esm({
|
|
43407
|
+
"src/commands/sync.ts"() {
|
|
43408
|
+
"use strict";
|
|
43409
|
+
init_src();
|
|
43410
|
+
init_session();
|
|
43411
|
+
init_formatter();
|
|
43412
|
+
init_instruction_pipeline();
|
|
43413
|
+
init_pipeline_deps();
|
|
43414
|
+
logger20 = createLogger("cli:sync");
|
|
43415
|
+
syncCommand = new Command21("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").action(async (opts) => {
|
|
43416
|
+
try {
|
|
43417
|
+
await requireSession();
|
|
43418
|
+
const activeCourse = await requireActiveCourse();
|
|
43419
|
+
const pipelineResult = await resolveAndGenerate(
|
|
43420
|
+
{ courseId: activeCourse.courseId, enrollmentId: activeCourse.enrollmentId },
|
|
43421
|
+
{ forceRefresh: true, collectT1Interactive: false, collectT2Interactive: false },
|
|
43422
|
+
{ cwd: process.cwd(), deps: buildPipelineDeps() }
|
|
43423
|
+
);
|
|
43424
|
+
if (opts.json) {
|
|
43425
|
+
output(
|
|
43426
|
+
{
|
|
43427
|
+
status: "ok",
|
|
43428
|
+
courseId: activeCourse.courseId,
|
|
43429
|
+
courseTitle: activeCourse.courseTitle,
|
|
43430
|
+
wroteFiles: pipelineResult.wroteFiles,
|
|
43431
|
+
markerPath: pipelineResult.markerPath,
|
|
43432
|
+
usedT0: pipelineResult.usedT0,
|
|
43433
|
+
usedT1: pipelineResult.usedT1,
|
|
43434
|
+
usedT2: pipelineResult.usedT2
|
|
43435
|
+
},
|
|
43436
|
+
{ json: true }
|
|
43437
|
+
);
|
|
43438
|
+
} else {
|
|
43439
|
+
output(
|
|
43440
|
+
[
|
|
43441
|
+
`\u2713 Instru\xE7\xF5es sincronizadas para "${activeCourse.courseTitle}"`,
|
|
43442
|
+
` - T0 (persona): ${pipelineResult.usedT0 ? "presente" : "ausente"}`,
|
|
43443
|
+
` - T1 (brief base): ${pipelineResult.usedT1 ? "presente" : "ausente"}`,
|
|
43444
|
+
` - T2 (curso): ${pipelineResult.usedT2 ? "presente" : "ausente"}`,
|
|
43445
|
+
` - Arquivos: ${pipelineResult.wroteFiles.join(", ")}`
|
|
43446
|
+
].join("\n"),
|
|
43447
|
+
{ json: false }
|
|
43448
|
+
);
|
|
43449
|
+
}
|
|
43450
|
+
} catch (err) {
|
|
43451
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43452
|
+
if (msg.includes("process.exit")) return;
|
|
43453
|
+
logger20.warn("sync failed", { error: msg });
|
|
43454
|
+
error(msg);
|
|
43455
|
+
}
|
|
43456
|
+
});
|
|
43457
|
+
}
|
|
43458
|
+
});
|
|
43459
|
+
|
|
43460
|
+
// src/commands/brief.ts
|
|
43461
|
+
import { Command as Command22 } from "commander";
|
|
43462
|
+
var logger21, briefCommand;
|
|
43463
|
+
var init_brief = __esm({
|
|
43464
|
+
"src/commands/brief.ts"() {
|
|
43465
|
+
"use strict";
|
|
43466
|
+
init_src();
|
|
43467
|
+
init_session();
|
|
43468
|
+
init_cache();
|
|
43469
|
+
init_api2();
|
|
43470
|
+
init_formatter();
|
|
43471
|
+
logger21 = createLogger("cli:brief");
|
|
43472
|
+
briefCommand = new Command22("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
|
|
43473
|
+
try {
|
|
43474
|
+
const session = await requireSession();
|
|
43475
|
+
const cached2 = await readBriefCache();
|
|
43476
|
+
const remote = await fetchLearnerBrief({
|
|
43477
|
+
apiUrl: session.apiUrl,
|
|
43478
|
+
token: session.token
|
|
43479
|
+
});
|
|
43480
|
+
if (opts.json) {
|
|
43481
|
+
output(
|
|
43482
|
+
{
|
|
43483
|
+
cached: cached2?.brief ?? null,
|
|
43484
|
+
remote,
|
|
43485
|
+
cacheFetchedAt: cached2?.fetchedAt ?? null
|
|
43486
|
+
},
|
|
43487
|
+
{ json: true }
|
|
43488
|
+
);
|
|
43489
|
+
return;
|
|
43490
|
+
}
|
|
43491
|
+
if (!remote) {
|
|
43492
|
+
const lines2 = [
|
|
43493
|
+
"Voce ainda nao tem um brief base preenchido.",
|
|
43494
|
+
"Para criar: `tostudy brief-create` (terminal) ou `tostudy brief-open` (portal web)."
|
|
43495
|
+
];
|
|
43496
|
+
output(lines2.join("\n"), { json: false });
|
|
43497
|
+
return;
|
|
43498
|
+
}
|
|
43499
|
+
const lines = [`Brief base (atualizado em ${remote.updatedAt}):`, "", remote.text, ""];
|
|
43500
|
+
if (cached2 && cached2.brief && cached2.brief.updatedAt !== remote.updatedAt) {
|
|
43501
|
+
lines.push("Aviso: cache local esta desatualizado. Rode `tostudy sync` para atualizar.");
|
|
43502
|
+
}
|
|
43503
|
+
output(lines.join("\n"), { json: false });
|
|
43504
|
+
} catch (err) {
|
|
43505
|
+
logger21.error("Failed to show brief", { err });
|
|
43506
|
+
error(`Erro ao buscar brief: ${err instanceof Error ? err.message : String(err)}`);
|
|
43507
|
+
}
|
|
43508
|
+
});
|
|
43509
|
+
}
|
|
43510
|
+
});
|
|
43511
|
+
|
|
43512
|
+
// src/commands/brief-create.ts
|
|
43513
|
+
import { Command as Command23 } from "commander";
|
|
43514
|
+
var logger22, briefCreateCommand;
|
|
43515
|
+
var init_brief_create = __esm({
|
|
43516
|
+
"src/commands/brief-create.ts"() {
|
|
43517
|
+
"use strict";
|
|
43518
|
+
init_src();
|
|
43519
|
+
init_session();
|
|
43520
|
+
init_bootstrap();
|
|
43521
|
+
init_api2();
|
|
43522
|
+
init_cache();
|
|
43523
|
+
init_formatter();
|
|
43524
|
+
logger22 = createLogger("cli:brief-create");
|
|
43525
|
+
briefCreateCommand = new Command23("brief-create").description("Create your base learner brief via interactive prompts (T1 bootstrap)").action(async () => {
|
|
43526
|
+
try {
|
|
43527
|
+
const session = await requireSession();
|
|
43528
|
+
const answers = await collectBootstrapAnswers({ userName: session.userName });
|
|
43529
|
+
const text2 = composeBriefFromAnswers(answers);
|
|
43530
|
+
const previewLines = ["", "Brief composto:", "---", text2, "---"];
|
|
43531
|
+
output(previewLines.join("\n"), { json: false });
|
|
43532
|
+
const brief = await upsertLearnerBrief({
|
|
43533
|
+
apiUrl: session.apiUrl,
|
|
43534
|
+
token: session.token,
|
|
43535
|
+
text: text2,
|
|
43536
|
+
source: "manual"
|
|
43537
|
+
});
|
|
43538
|
+
await writeBriefCache(void 0, {
|
|
43539
|
+
userId: session.userId,
|
|
43540
|
+
brief,
|
|
43541
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
43542
|
+
});
|
|
43543
|
+
const doneLines = [
|
|
43544
|
+
"",
|
|
43545
|
+
"Brief salvo. Rode `tostudy sync` para atualizar os instruction files."
|
|
43546
|
+
];
|
|
43547
|
+
output(doneLines.join("\n"), { json: false });
|
|
43548
|
+
} catch (err) {
|
|
43549
|
+
logger22.error("Failed to create brief", { err });
|
|
43550
|
+
error(`Erro ao criar brief: ${err instanceof Error ? err.message : String(err)}`);
|
|
43551
|
+
}
|
|
43552
|
+
});
|
|
43553
|
+
}
|
|
43554
|
+
});
|
|
43555
|
+
|
|
43556
|
+
// src/commands/brief-open.ts
|
|
43557
|
+
import { Command as Command24 } from "commander";
|
|
43558
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
43559
|
+
import { platform } from "node:process";
|
|
43560
|
+
function openUrl(url2) {
|
|
43561
|
+
let cmd;
|
|
43562
|
+
let args;
|
|
43563
|
+
if (platform === "darwin") {
|
|
43564
|
+
cmd = "open";
|
|
43565
|
+
args = [url2];
|
|
43566
|
+
} else if (platform === "win32") {
|
|
43567
|
+
cmd = "cmd";
|
|
43568
|
+
args = ["/c", "start", "", url2];
|
|
43569
|
+
} else {
|
|
43570
|
+
cmd = "xdg-open";
|
|
43571
|
+
args = [url2];
|
|
43572
|
+
}
|
|
43573
|
+
execFile4(cmd, args, (err) => {
|
|
43574
|
+
if (err) {
|
|
43575
|
+
output(`Nao consegui abrir o navegador. Acesse manualmente: ${url2}`, { json: false });
|
|
43576
|
+
}
|
|
43577
|
+
});
|
|
43578
|
+
}
|
|
43579
|
+
var BRIEF_URL, briefOpenCommand;
|
|
43580
|
+
var init_brief_open = __esm({
|
|
43581
|
+
"src/commands/brief-open.ts"() {
|
|
43582
|
+
"use strict";
|
|
43583
|
+
init_session();
|
|
43584
|
+
init_formatter();
|
|
43585
|
+
BRIEF_URL = "https://tostudy.ai/student/settings/learner-brief";
|
|
43586
|
+
briefOpenCommand = new Command24("brief-open").description("Open the learner brief editor in your web browser").action(async () => {
|
|
43587
|
+
await requireSession();
|
|
43588
|
+
output(`Abrindo ${BRIEF_URL} no navegador...`, { json: false });
|
|
43589
|
+
openUrl(BRIEF_URL);
|
|
43590
|
+
});
|
|
43591
|
+
}
|
|
43592
|
+
});
|
|
43593
|
+
|
|
42599
43594
|
// src/cli.ts
|
|
42600
43595
|
var cli_exports = {};
|
|
42601
43596
|
__export(cli_exports, {
|
|
42602
43597
|
CLI_VERSION: () => CLI_VERSION,
|
|
42603
43598
|
createProgram: () => createProgram
|
|
42604
43599
|
});
|
|
42605
|
-
import { Command as
|
|
43600
|
+
import { Command as Command25 } from "commander";
|
|
42606
43601
|
function createProgram() {
|
|
42607
|
-
const program2 = new
|
|
43602
|
+
const program2 = new Command25();
|
|
42608
43603
|
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");
|
|
42609
43604
|
program2.addCommand(setupCommand);
|
|
42610
43605
|
program2.addCommand(doctorCommand);
|
|
@@ -42622,7 +43617,11 @@ function createProgram() {
|
|
|
42622
43617
|
program2.addCommand(validateCommand);
|
|
42623
43618
|
program2.addCommand(menuCommand);
|
|
42624
43619
|
program2.addCommand(profileCommand);
|
|
43620
|
+
program2.addCommand(briefCommand);
|
|
43621
|
+
program2.addCommand(briefCreateCommand);
|
|
43622
|
+
program2.addCommand(briefOpenCommand);
|
|
42625
43623
|
program2.addCommand(workspaceCommand);
|
|
43624
|
+
program2.addCommand(syncCommand);
|
|
42626
43625
|
program2.addCommand(exportCommand);
|
|
42627
43626
|
program2.addCommand(openCommand);
|
|
42628
43627
|
program2.addCommand(vaultCommand);
|
|
@@ -42652,7 +43651,11 @@ var init_cli = __esm({
|
|
|
42652
43651
|
init_open();
|
|
42653
43652
|
init_vault2();
|
|
42654
43653
|
init_profile();
|
|
42655
|
-
|
|
43654
|
+
init_sync();
|
|
43655
|
+
init_brief();
|
|
43656
|
+
init_brief_create();
|
|
43657
|
+
init_brief_open();
|
|
43658
|
+
CLI_VERSION = true ? "0.9.0" : "0.7.1";
|
|
42656
43659
|
}
|
|
42657
43660
|
});
|
|
42658
43661
|
|