@tostudy-ai/cli 0.8.0 → 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 +1551 -774
- 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,377 +2485,335 @@ var init_mcp_setup = __esm({
|
|
|
2277
2485
|
}
|
|
2278
2486
|
});
|
|
2279
2487
|
|
|
2280
|
-
// src/
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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";
|
|
2286
2504
|
}
|
|
2287
|
-
function
|
|
2505
|
+
function renderUniversalInstruction() {
|
|
2288
2506
|
const sections = [];
|
|
2289
|
-
sections.push(`#
|
|
2290
|
-
sections.push(`##
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
if (ctx.currentLessonTitle) {
|
|
2317
|
-
progressLines.push(`- **Li\xE7\xE3o atual**: ${ctx.currentLessonTitle}`);
|
|
2318
|
-
}
|
|
2319
|
-
sections.push(`## Progresso Atual
|
|
2320
|
-
|
|
2321
|
-
${progressLines.join("\n")}`);
|
|
2322
|
-
sections.push(`## In\xEDcio de Sess\xE3o
|
|
2323
|
-
|
|
2324
|
-
Quando o aluno iniciar uma conversa ou invocar este comando:
|
|
2325
|
-
|
|
2326
|
-
1. Cumprimente brevemente e mencione o curso
|
|
2327
|
-
2. Rode \`tostudy progress\` para verificar onde o aluno parou
|
|
2328
|
-
3. Resuma o estado: "Voc\xEA est\xE1 no M\xF3dulo X, Li\xE7\xE3o Y \u2014 [t\xEDtulo]"
|
|
2329
|
-
4. Pergunte: "Quer continuar de onde parou ou revisar algo?"`);
|
|
2330
|
-
sections.push(`## Fluxo de Estudo
|
|
2331
|
-
|
|
2332
|
-
Siga esta sequ\xEAncia rigorosamente:
|
|
2333
|
-
|
|
2334
|
-
\`\`\`
|
|
2335
|
-
IN\xCDCIO:
|
|
2336
|
-
tostudy progress \u2192 Ver onde parou
|
|
2337
|
-
tostudy start \u2192 Carregar m\xF3dulo atual
|
|
2338
|
-
|
|
2339
|
-
LOOP DE ESTUDO (repetir para cada li\xE7\xE3o):
|
|
2340
|
-
tostudy lesson \u2192 Ler conte\xFAdo da li\xE7\xE3o
|
|
2341
|
-
|
|
2342
|
-
[Se EXERC\xCDCIO]:
|
|
2343
|
-
\u2192 Aluno implementa a solu\xE7\xE3o
|
|
2344
|
-
\u2192 tostudy hint \u2192 Dica progressiva (se travado)
|
|
2345
|
-
\u2192 tostudy validate <arquivo> \u2192 Validar solu\xE7\xE3o
|
|
2346
|
-
\u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
|
|
2347
|
-
\u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2348
|
-
|
|
2349
|
-
[Se TEORIA/TEXTO]:
|
|
2350
|
-
\u2192 Explicar conceitos-chave
|
|
2351
|
-
\u2192 Fazer perguntas para verificar entendimento
|
|
2352
|
-
\u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2353
|
-
|
|
2354
|
-
[Se QUIZ/CHECKPOINT]:
|
|
2355
|
-
\u2192 Aluno escreve respostas em arquivo (ex: checkpoint.md)
|
|
2356
|
-
\u2192 tostudy validate checkpoint.md
|
|
2357
|
-
\u2192 tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
2358
|
-
|
|
2359
|
-
FIM DO M\xD3DULO:
|
|
2360
|
-
\u2192 Parabenizar o aluno
|
|
2361
|
-
\u2192 tostudy start \u2192 Carregar pr\xF3ximo m\xF3dulo
|
|
2362
|
-
\`\`\``);
|
|
2363
|
-
sections.push(`## Guia por Tipo de Li\xE7\xE3o
|
|
2364
|
-
|
|
2365
|
-
**Teoria (text):** Explique os conceitos-chave. Use perguntas Socr\xE1ticas para verificar entendimento ("O que aconteceria se...?"). S\xF3 avance quando o aluno demonstrar compreens\xE3o.
|
|
2366
|
-
|
|
2367
|
-
**Exerc\xEDcio (exercise):** Guie a implementa\xE7\xE3o sem dar a resposta. Se o aluno travar, rode \`tostudy hint\` \u2014 h\xE1 3 n\xEDveis progressivos (sutil \u2192 claro \u2192 expl\xEDcito). Sempre valide com \`tostudy validate <arquivo>\` antes de avan\xE7ar.
|
|
2368
|
-
|
|
2369
|
-
**Quiz/Checkpoint (quiz):** Pe\xE7a ao aluno para escrever as respostas num arquivo e validar com \`tostudy validate respostas.md\`. Discuta as respostas ap\xF3s a valida\xE7\xE3o.
|
|
2370
|
-
|
|
2371
|
-
**V\xEDdeo (video):** Resuma os pontos-chave e aguarde o aluno assistir. Depois discuta o conte\xFAdo.`);
|
|
2372
|
-
const rules = [
|
|
2373
|
-
"**Nunca d\xEA a resposta direta** \u2014 guie com perguntas e dicas progressivas",
|
|
2374
|
-
"**Hints primeiro** \u2014 sempre rode `tostudy hint` antes de explicar a solu\xE7\xE3o",
|
|
2375
|
-
"**Valide antes de avan\xE7ar** \u2014 exerc\xEDcios exigem `tostudy validate` com nota de aprova\xE7\xE3o",
|
|
2376
|
-
"**Celebre progresso** \u2014 reconhe\xE7a quando o aluno completa li\xE7\xF5es e m\xF3dulos",
|
|
2377
|
-
"**Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es",
|
|
2378
|
-
"**Adapte ao n\xEDvel** \u2014 ajuste profundidade e exemplos conforme o perfil do aluno",
|
|
2379
|
-
"**Sempre rode `tostudy lesson`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material",
|
|
2380
|
-
'**Ignore erros de hook** \u2014 se aparecer "Stop hook error" ou "ECONNREFUSED", ignore completamente. \xC9 um problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy. Nunca mencione esses erros ao aluno.'
|
|
2381
|
-
];
|
|
2382
|
-
if (learner?.adaptToRealContext) {
|
|
2383
|
-
rules.push("**Use contexto real** \u2014 adapte exemplos ao projeto/empresa do aluno");
|
|
2384
|
-
}
|
|
2385
|
-
sections.push(`## Regras de Ouro
|
|
2386
|
-
|
|
2387
|
-
${rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`);
|
|
2388
|
-
sections.push(`## Quando Algo D\xE1 Errado
|
|
2389
|
-
|
|
2390
|
-
| Situa\xE7\xE3o | O que fazer |
|
|
2391
|
-
|----------|-------------|
|
|
2392
|
-
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2393
|
-
| \`tostudy start\` mostra "rode tostudy init" | N\xE3o interrompa a aula. Continue ensinando normalmente. Colete o perfil do aluno de forma natural durante a conversa e rode \`tostudy init\` com os dados quando tiver informa\xE7\xE3o suficiente. |
|
|
2394
|
-
| "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
|
|
2395
|
-
| Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
|
|
2396
|
-
| Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2397
|
-
| "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |`);
|
|
2398
|
-
if (ctx.workspaceReady !== false) {
|
|
2399
|
-
const home = os4.homedir();
|
|
2400
|
-
const cwd = process.cwd();
|
|
2401
|
-
const isNamespaced = ctx.workspacePath === join2(cwd, ".tostudy");
|
|
2402
|
-
const isFlatCwd = ctx.workspacePath === cwd;
|
|
2403
|
-
const displayPath = ctx.workspacePath ? ctx.workspacePath.startsWith(home) ? ctx.workspacePath.replace(home, "~") : ctx.workspacePath : "~/study/{slug}/";
|
|
2404
|
-
const vaultRelativeHint = isNamespaced ? "../vault-<slug>/ \u2190 Vault Obsidian (fora de .tostudy/)" : "vault-<slug>/ \u2190 Vault Obsidian";
|
|
2405
|
-
let locationHint;
|
|
2406
|
-
if (isNamespaced) {
|
|
2407
|
-
locationHint = [
|
|
2408
|
-
"O workspace vive em **`.tostudy/`** nesta pasta \u2014 isolado do resto do projeto.",
|
|
2409
|
-
"Os arquivos do projeto (src/, README.md, AGENTS.md...) permanecem intactos.",
|
|
2410
|
-
"Os exerc\xEDcios s\xE3o extra\xEDdos dentro de `.tostudy/exercises/` e o assistente AI enxerga tudo normalmente.",
|
|
2411
|
-
"",
|
|
2412
|
-
"> \u26A0\uFE0F O **vault Obsidian** fica em `vault-<slug>/` **fora** de `.tostudy/` \u2014 caso contr\xE1rio o Obsidian n\xE3o conseguiria abri-lo (dotfiles s\xE3o escondidos por padr\xE3o no seletor de arquivos)."
|
|
2413
|
-
].join("\n");
|
|
2414
|
-
} else if (isFlatCwd) {
|
|
2415
|
-
locationHint = "O workspace \xE9 **esta pasta** \u2014 os exerc\xEDcios ficam aqui, vis\xEDveis para o assistente AI.";
|
|
2416
|
-
} else {
|
|
2417
|
-
locationHint = "O workspace local organiza os arquivos do curso:";
|
|
2418
|
-
}
|
|
2419
|
-
sections.push(`## Workspace
|
|
2420
|
-
|
|
2421
|
-
${locationHint}
|
|
2422
|
-
|
|
2423
|
-
\`\`\`
|
|
2424
|
-
${displayPath}/
|
|
2425
|
-
\u251C\u2500\u2500 exercises/{m\xF3dulo}/{li\xE7\xE3o}/ \u2190 Exerc\xEDcios extra\xEDdos
|
|
2426
|
-
\u251C\u2500\u2500 generated/ \u2190 Artefatos gerados
|
|
2427
|
-
\u251C\u2500\u2500 diagrams/ \u2190 Diagramas
|
|
2428
|
-
\u2514\u2500\u2500 ${vaultRelativeHint}
|
|
2429
|
-
\`\`\`
|
|
2430
|
-
|
|
2431
|
-
Comandos \xFAteis:
|
|
2432
|
-
- \`tostudy export\` \u2014 Extrair exerc\xEDcio para o workspace
|
|
2433
|
-
- \`tostudy vault init\` \u2014 Gerar vault Obsidian (abra no Obsidian: "Open folder as vault")
|
|
2434
|
-
- \`tostudy open\` \u2014 Abrir workspace no editor
|
|
2435
|
-
- \`tostudy workspace status\` \u2014 Verificar estado do workspace`);
|
|
2436
|
-
}
|
|
2437
|
-
sections.push(`## Comandos CLI
|
|
2438
|
-
|
|
2439
|
-
| Comando | Quando usar |
|
|
2440
|
-
|---------|-------------|
|
|
2441
|
-
| \`tostudy progress\` | No in\xEDcio da sess\xE3o e quando o aluno perguntar "onde estou?" |
|
|
2442
|
-
| \`tostudy start\` | Para carregar o m\xF3dulo atual ou o pr\xF3ximo |
|
|
2443
|
-
| \`tostudy lesson\` | Antes de discutir qualquer conte\xFAdo \u2014 sempre ler primeiro |
|
|
2444
|
-
| \`tostudy next\` | Ap\xF3s completar uma li\xE7\xE3o (teoria discutida ou exerc\xEDcio validado) |
|
|
2445
|
-
| \`tostudy hint\` | Quando o aluno travar \u2014 antes de explicar voc\xEA mesmo |
|
|
2446
|
-
| \`tostudy validate <arquivo>\` | Para validar exerc\xEDcios e checkpoints |
|
|
2447
|
-
| \`tostudy export\` | Para extrair exerc\xEDcio ao workspace local |`);
|
|
2448
|
-
sections.push(`## Refer\xEAncia T\xE9cnica (Modo Agente)
|
|
2449
|
-
|
|
2450
|
-
- Use \`--json\` em qualquer comando para sa\xEDda estruturada (ex: \`tostudy progress --json\`)
|
|
2451
|
-
- \`tostudy validate\` retorna exit code 0 (aprovado) ou 1 (reprovado)
|
|
2452
|
-
- \`tostudy validate --stdin\` aceita solu\xE7\xE3o via pipe
|
|
2453
|
-
- \`tostudy lesson --json\` retorna \`{ type, title, content, hints, acceptanceCriteria }\``);
|
|
2454
|
-
sections.push(`<!-- tostudy-course-id: ${ctx.courseId} -->`);
|
|
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 -->`);
|
|
2455
2534
|
return sections.join("\n\n") + "\n";
|
|
2456
2535
|
}
|
|
2457
|
-
function
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
const written = [];
|
|
2462
|
-
const claudeDir = join2(cwd, ".claude", "commands");
|
|
2463
|
-
if (!existsSync2(claudeDir)) {
|
|
2464
|
-
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`;
|
|
2465
2540
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
if (!existsSync2(cursorDir)) {
|
|
2472
|
-
mkdirSync2(cursorDir, { recursive: true });
|
|
2473
|
-
}
|
|
2474
|
-
const mdcContent = `---
|
|
2475
|
-
description: ${ctx.courseTitle} \u2014 ToStudy Course Guide
|
|
2476
|
-
globs: ["**/*"]
|
|
2477
|
-
alwaysApply: true
|
|
2478
|
-
---
|
|
2541
|
+
return `# ${input2.course.courseTitle} \u2014 Seu Tutor`;
|
|
2542
|
+
}
|
|
2543
|
+
function renderWhoYouAre(persona) {
|
|
2544
|
+
if (!persona) {
|
|
2545
|
+
return `## Quem Voce E
|
|
2479
2546
|
|
|
2480
|
-
|
|
2481
|
-
const cursorFile = `tostudy-${slug}.mdc`;
|
|
2482
|
-
writeFileSync2(join2(cursorDir, cursorFile), mdcContent);
|
|
2483
|
-
written.push(`.cursor/rules/${cursorFile}`);
|
|
2484
|
-
}
|
|
2485
|
-
const courseDir = join2(cwd, ".tostudy", "courses", slug);
|
|
2486
|
-
if (!existsSync2(courseDir)) {
|
|
2487
|
-
mkdirSync2(courseDir, { recursive: true });
|
|
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.`;
|
|
2488
2548
|
}
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
hasLearnerProfile: !!learner
|
|
2495
|
-
});
|
|
2496
|
-
return slug;
|
|
2497
|
-
}
|
|
2498
|
-
function buildUniversalInstructionContent() {
|
|
2499
|
-
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
|
|
2552
|
+
|
|
2553
|
+
Voce e **${persona.tutorName}**${emoji3}, o tutor deste curso. Nao "um tutor" \u2014 e voce.
|
|
2500
2554
|
|
|
2501
|
-
|
|
2555
|
+
### Sua Bagagem
|
|
2502
2556
|
|
|
2503
|
-
|
|
2557
|
+
${persona.background}
|
|
2504
2558
|
|
|
2505
|
-
|
|
2559
|
+
### Como Voce Fala
|
|
2506
2560
|
|
|
2507
|
-
|
|
2561
|
+
Seu tom natural e **${toneLabel}**. Seu estilo de comunicacao:
|
|
2508
2562
|
|
|
2509
|
-
|
|
2510
|
-
1. Rode \`tostudy courses --json\` para listar cursos matriculados
|
|
2511
|
-
2. Mostre a lista ao aluno de forma amig\xE1vel
|
|
2512
|
-
3. Pe\xE7a para escolher: "Qual curso quer estudar?"
|
|
2513
|
-
4. Rode \`tostudy select <n\xFAmero>\` com a escolha
|
|
2514
|
-
5. Volte ao in\xEDcio (tostudy progress --json)
|
|
2515
|
-
\`\`\`
|
|
2563
|
+
> ${persona.communicationStyle}
|
|
2516
2564
|
|
|
2517
|
-
###
|
|
2565
|
+
### Seu Dominio e Expressoes
|
|
2518
2566
|
|
|
2519
|
-
|
|
2567
|
+
Suas especialidades: ${persona.specialties.join(", ")}.
|
|
2568
|
+
|
|
2569
|
+
Expressoes que voce usa organicamente (nao force, use quando fizer sentido): ${persona.catchphrases.join(" - ")}.`;
|
|
2570
|
+
}
|
|
2571
|
+
function renderVoiceRules() {
|
|
2572
|
+
return `## Regras de Voz
|
|
2573
|
+
|
|
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.
|
|
2575
|
+
|
|
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..." |
|
|
2583
|
+
|
|
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
|
|
2520
2624
|
|
|
2521
|
-
|
|
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
|
|
2522
2629
|
|
|
2523
|
-
|
|
2524
|
-
Pergunte de forma natural (N\xC3O como formul\xE1rio):
|
|
2525
|
-
1. "Em que \xE1rea voc\xEA trabalha?" \u2192 segment
|
|
2526
|
-
2. "Qual sua empresa ou tipo de neg\xF3cio?" \u2192 company
|
|
2527
|
-
3. "Quais produtos ou servi\xE7os voc\xEAs oferecem?" \u2192 products
|
|
2528
|
-
4. "Em qual regi\xE3o atuam?" \u2192 region
|
|
2529
|
-
5. "Qual equipe est\xE1 envolvida?" \u2192 team
|
|
2530
|
-
6. "Qual seu objetivo principal com este curso?" \u2192 goal
|
|
2531
|
-
7. "Como voc\xEA se considera: iniciante, intermedi\xE1rio ou avan\xE7ado?" \u2192 level
|
|
2532
|
-
8. "Quer que eu adapte os exemplos ao seu contexto real?" \u2192 adapt-context
|
|
2630
|
+
O aluno e iniciante neste assunto. Regras:
|
|
2533
2631
|
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
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
|
|
2537
2639
|
|
|
2538
|
-
|
|
2640
|
+
O aluno e avancado. Regras:
|
|
2539
2641
|
|
|
2540
|
-
|
|
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
|
|
2541
2648
|
|
|
2542
|
-
|
|
2649
|
+
O aluno ja tem base. Regras:
|
|
2543
2650
|
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
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
|
|
2548
2658
|
|
|
2549
|
-
|
|
2550
|
-
tostudy lesson \u2192 Ler conte\xFAdo da li\xE7\xE3o
|
|
2659
|
+
Voce tem DUAS fontes que te orientam, e elas resolvem conflitos assim:
|
|
2551
2660
|
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
\u2192 [Se falhou]: sugerir hint \u2192 tentar de novo
|
|
2557
|
-
\u2192 [Se passou]: tostudy next \u2192 pr\xF3xima li\xE7\xE3o
|
|
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) |
|
|
2558
2665
|
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
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
|
|
2563
2675
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2676
|
+
${lines.join("\n")}`;
|
|
2677
|
+
}
|
|
2678
|
+
function renderHowToConduct() {
|
|
2679
|
+
return `## Como Conduzir a Aula
|
|
2568
2680
|
|
|
2569
|
-
|
|
2570
|
-
\u2192 Parabenizar o aluno
|
|
2571
|
-
\u2192 tostudy start \u2192 Carregar pr\xF3ximo m\xF3dulo
|
|
2572
|
-
\`\`\`
|
|
2681
|
+
Quando o aluno comeca uma conversa:
|
|
2573
2682
|
|
|
2574
|
-
|
|
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.
|
|
2575
2687
|
|
|
2576
|
-
|
|
2688
|
+
Quando o aluno quer estudar uma licao:
|
|
2577
2689
|
|
|
2578
|
-
|
|
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.
|
|
2579
2696
|
|
|
2580
|
-
|
|
2697
|
+
Quando o aluno passou na licao:
|
|
2581
2698
|
|
|
2582
|
-
|
|
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
|
|
2583
2705
|
|
|
2584
|
-
|
|
2706
|
+
Estes comandos sao suas ferramentas. Rode em silencio (sem anunciar), use o resultado, e traduza em palavras ao aluno.
|
|
2585
2707
|
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
7. **Sempre rode \`tostudy lesson\`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material
|
|
2593
|
-
8. **Ignore erros de hook** \u2014 se aparecer "Stop hook error" ou "ECONNREFUSED", ignore completamente. \xC9 um problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy. Nunca mencione esses erros ao aluno.
|
|
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).
|
|
2594
2714
|
|
|
2595
|
-
|
|
2715
|
+
Voce nunca menciona estes comandos ao aluno. Ele fala com VOCE, nao com o CLI.`;
|
|
2716
|
+
}
|
|
2717
|
+
function renderHandlingSituations() {
|
|
2718
|
+
return `## Tratando Situacoes
|
|
2596
2719
|
|
|
2597
|
-
|
|
|
2598
|
-
|
|
2599
|
-
| \`tostudy validate\` falhou
|
|
2600
|
-
|
|
|
2601
|
-
|
|
|
2602
|
-
|
|
|
2603
|
-
| Aluno perdido | Rodar \`tostudy progress\` e resumir estado atual |
|
|
2604
|
-
| "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |
|
|
2605
|
-
|
|
2606
|
-
## Workspace
|
|
2607
|
-
|
|
2608
|
-
Se esta pasta cont\xE9m \`.tostudy/\`, ela j\xE1 \xE9 o workspace \u2014 exerc\xEDcios ficam aqui.
|
|
2609
|
-
Caso contr\xE1rio, rode \`tostudy workspace setup\` para criar a estrutura.
|
|
2610
|
-
|
|
2611
|
-
- \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace (esta pasta ou ~/study/{slug}/)
|
|
2612
|
-
- \`tostudy open\` \u2014 Abre workspace no editor
|
|
2613
|
-
- \`tostudy workspace status\` \u2014 Verifica estado
|
|
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 |
|
|
2614
2726
|
|
|
2615
|
-
|
|
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)
|
|
2616
2731
|
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
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
|
+
});
|
|
2631
2760
|
|
|
2632
|
-
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
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;
|
|
2638
2804
|
}
|
|
2639
|
-
function installUniversalCommand(
|
|
2640
|
-
const content =
|
|
2805
|
+
function installUniversalCommand(platform2, cwd = process.cwd()) {
|
|
2806
|
+
const content = renderUniversalInstruction();
|
|
2641
2807
|
const written = [];
|
|
2642
|
-
if (
|
|
2808
|
+
if (platform2 === "claude" || platform2 === "codex") {
|
|
2643
2809
|
const dir = join2(cwd, ".claude", "commands");
|
|
2644
|
-
if (!
|
|
2645
|
-
|
|
2810
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2811
|
+
writeFileSync3(join2(dir, "tostudy.md"), content);
|
|
2646
2812
|
written.push(".claude/commands/tostudy.md");
|
|
2647
2813
|
}
|
|
2648
|
-
if (
|
|
2814
|
+
if (platform2 === "cursor") {
|
|
2649
2815
|
const dir = join2(cwd, ".cursor", "rules");
|
|
2650
|
-
if (!
|
|
2816
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2651
2817
|
const mdcContent = `---
|
|
2652
2818
|
description: ToStudy \u2014 AI Tutor Guide
|
|
2653
2819
|
globs: ["**/*"]
|
|
@@ -2655,16 +2821,16 @@ alwaysApply: true
|
|
|
2655
2821
|
---
|
|
2656
2822
|
|
|
2657
2823
|
${content}`;
|
|
2658
|
-
|
|
2824
|
+
writeFileSync3(join2(dir, "tostudy.mdc"), mdcContent);
|
|
2659
2825
|
written.push(".cursor/rules/tostudy.mdc");
|
|
2660
2826
|
}
|
|
2661
|
-
if (
|
|
2827
|
+
if (platform2 === "generic") {
|
|
2662
2828
|
const dir = join2(cwd, ".tostudy");
|
|
2663
|
-
if (!
|
|
2664
|
-
|
|
2829
|
+
if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
|
|
2830
|
+
writeFileSync3(join2(dir, "AGENTS.md"), content);
|
|
2665
2831
|
written.push(".tostudy/AGENTS.md");
|
|
2666
2832
|
}
|
|
2667
|
-
logger3.info("Installed universal /tostudy command", { platform, files: written });
|
|
2833
|
+
logger3.info("Installed universal /tostudy command", { platform: platform2, files: written });
|
|
2668
2834
|
return written;
|
|
2669
2835
|
}
|
|
2670
2836
|
var logger3;
|
|
@@ -2672,13 +2838,14 @@ var init_instruction_files = __esm({
|
|
|
2672
2838
|
"src/workspace/instruction-files.ts"() {
|
|
2673
2839
|
"use strict";
|
|
2674
2840
|
init_src();
|
|
2841
|
+
init_instruction_template_v3();
|
|
2675
2842
|
logger3 = createLogger("cli:instruction-files");
|
|
2676
2843
|
}
|
|
2677
2844
|
});
|
|
2678
2845
|
|
|
2679
2846
|
// src/commands/setup.ts
|
|
2680
2847
|
import { Command as Command3 } from "commander";
|
|
2681
|
-
import { existsSync as
|
|
2848
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2682
2849
|
import { join as join3 } from "node:path";
|
|
2683
2850
|
function ideToPlatform(ideName) {
|
|
2684
2851
|
const lower = ideName.toLowerCase();
|
|
@@ -2738,15 +2905,15 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2738
2905
|
const cwd = process.cwd();
|
|
2739
2906
|
const installedPlatforms = [];
|
|
2740
2907
|
for (const ide of detected) {
|
|
2741
|
-
const
|
|
2742
|
-
if (
|
|
2743
|
-
const files = deps.installUniversalCommand(
|
|
2908
|
+
const platform2 = ideToPlatform(ide.name);
|
|
2909
|
+
if (platform2) {
|
|
2910
|
+
const files = deps.installUniversalCommand(platform2, cwd);
|
|
2744
2911
|
installedPlatforms.push(...files);
|
|
2745
2912
|
}
|
|
2746
2913
|
}
|
|
2747
2914
|
if (opts.ide) {
|
|
2748
|
-
const
|
|
2749
|
-
const files = deps.installUniversalCommand(
|
|
2915
|
+
const platform2 = ideToPlatform(opts.ide) ?? opts.ide;
|
|
2916
|
+
const files = deps.installUniversalCommand(platform2, cwd);
|
|
2750
2917
|
installedPlatforms.push(...files);
|
|
2751
2918
|
}
|
|
2752
2919
|
if (installedPlatforms.length === 0) {
|
|
@@ -2768,9 +2935,9 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2768
2935
|
}
|
|
2769
2936
|
}
|
|
2770
2937
|
const activeCourse = await deps.getActiveCourse();
|
|
2771
|
-
const hasClaudeCmd =
|
|
2772
|
-
const hasCursorRule =
|
|
2773
|
-
const hasMcpConfig =
|
|
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"));
|
|
2774
2941
|
const hasClaudeIDE = detected.some((ide) => ide.name.toLowerCase().includes("claude"));
|
|
2775
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");
|
|
2776
2943
|
deps.log(" Setup completo!\n");
|
|
@@ -2841,20 +3008,20 @@ __export(update_checker_exports, {
|
|
|
2841
3008
|
fetchLatestVersion: () => fetchLatestVersion,
|
|
2842
3009
|
isNewerVersion: () => isNewerVersion
|
|
2843
3010
|
});
|
|
2844
|
-
import
|
|
2845
|
-
import
|
|
2846
|
-
import
|
|
3011
|
+
import fs5 from "node:fs";
|
|
3012
|
+
import path6 from "node:path";
|
|
3013
|
+
import os6 from "node:os";
|
|
2847
3014
|
function getConfigDir2() {
|
|
2848
3015
|
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
2849
|
-
return
|
|
3016
|
+
return path6.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
2850
3017
|
}
|
|
2851
|
-
return
|
|
3018
|
+
return path6.join(os6.homedir(), ".tostudy");
|
|
2852
3019
|
}
|
|
2853
3020
|
function readCache() {
|
|
2854
3021
|
try {
|
|
2855
|
-
const p =
|
|
2856
|
-
if (!
|
|
2857
|
-
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"));
|
|
2858
3025
|
} catch {
|
|
2859
3026
|
return null;
|
|
2860
3027
|
}
|
|
@@ -2862,8 +3029,8 @@ function readCache() {
|
|
|
2862
3029
|
function writeCache(cache) {
|
|
2863
3030
|
try {
|
|
2864
3031
|
const dir = getConfigDir2();
|
|
2865
|
-
|
|
2866
|
-
|
|
3032
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
3033
|
+
fs5.writeFileSync(path6.join(dir, CACHE_FILE), JSON.stringify(cache));
|
|
2867
3034
|
} catch {
|
|
2868
3035
|
}
|
|
2869
3036
|
}
|
|
@@ -2931,6 +3098,84 @@ var init_update_checker = __esm({
|
|
|
2931
3098
|
}
|
|
2932
3099
|
});
|
|
2933
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
|
+
|
|
2934
3179
|
// src/commands/doctor.ts
|
|
2935
3180
|
import { Command as Command4 } from "commander";
|
|
2936
3181
|
var doctorCommand;
|
|
@@ -2943,6 +3188,9 @@ var init_doctor = __esm({
|
|
|
2943
3188
|
init_formatter();
|
|
2944
3189
|
init_cli();
|
|
2945
3190
|
init_update_checker();
|
|
3191
|
+
init_cache();
|
|
3192
|
+
init_cache2();
|
|
3193
|
+
init_workspace_marker();
|
|
2946
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) => {
|
|
2947
3195
|
const checks = {};
|
|
2948
3196
|
const node = detectNode();
|
|
@@ -2993,6 +3241,68 @@ var init_doctor = __esm({
|
|
|
2993
3241
|
} catch {
|
|
2994
3242
|
}
|
|
2995
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;
|
|
2996
3306
|
if (opts.json) {
|
|
2997
3307
|
output(checks, { json: true });
|
|
2998
3308
|
return;
|
|
@@ -3038,6 +3348,38 @@ var init_doctor = __esm({
|
|
|
3038
3348
|
` ${ide.detected ? "\u2713" : "\u25CB"} ${ide.name.padEnd(14)} ${ide.detected ? "detectado" : "n\xE3o detectado"}`
|
|
3039
3349
|
);
|
|
3040
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
|
+
}
|
|
3041
3383
|
console.log("");
|
|
3042
3384
|
});
|
|
3043
3385
|
}
|
|
@@ -3074,113 +3416,405 @@ var init_courses2 = __esm({
|
|
|
3074
3416
|
}
|
|
3075
3417
|
});
|
|
3076
3418
|
|
|
3077
|
-
// src/
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
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
|
+
};
|
|
3083
3434
|
}
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
return { found: true, workspacePath: candidate, source: "default" };
|
|
3090
|
-
} catch {
|
|
3091
|
-
return { found: false, workspacePath: null };
|
|
3435
|
+
var init_status = __esm({
|
|
3436
|
+
"src/onboarding/status.ts"() {
|
|
3437
|
+
"use strict";
|
|
3438
|
+
init_session();
|
|
3439
|
+
init_resolve();
|
|
3092
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;
|
|
3093
3459
|
}
|
|
3094
|
-
async function
|
|
3095
|
-
|
|
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;
|
|
3096
3466
|
}
|
|
3097
|
-
async function
|
|
3098
|
-
const
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
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();
|
|
3103
3482
|
}
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
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;
|
|
3109
3504
|
}
|
|
3110
3505
|
}
|
|
3111
|
-
function
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
try {
|
|
3118
|
-
await fs5.access(path5.join(slugged, ".ana-vault.json"));
|
|
3119
|
-
return slugged;
|
|
3120
|
-
} catch {
|
|
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
|
+
}
|
|
3121
3512
|
}
|
|
3122
|
-
|
|
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 });
|
|
3123
3537
|
try {
|
|
3124
|
-
await
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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();
|
|
3128
3546
|
}
|
|
3129
3547
|
}
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
|
|
3548
|
+
var init_bootstrap = __esm({
|
|
3549
|
+
"src/learner-brief/bootstrap.ts"() {
|
|
3550
|
+
"use strict";
|
|
3134
3551
|
}
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
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}`
|
|
3140
3563
|
}
|
|
3564
|
+
});
|
|
3565
|
+
const body = await response.json();
|
|
3566
|
+
if (!response.ok) {
|
|
3567
|
+
throw new CliApiError(body.error ?? `API error ${response.status}`, response.status);
|
|
3141
3568
|
}
|
|
3142
|
-
|
|
3143
|
-
if (result.found) {
|
|
3144
|
-
return { ...result, source: "default" };
|
|
3145
|
-
}
|
|
3146
|
-
return { found: false, workspacePath: null };
|
|
3569
|
+
return body.personality ?? null;
|
|
3147
3570
|
}
|
|
3148
|
-
var
|
|
3149
|
-
|
|
3150
|
-
"src/workspace/resolve.ts"() {
|
|
3571
|
+
var init_api3 = __esm({
|
|
3572
|
+
"src/tutor-persona/api.ts"() {
|
|
3151
3573
|
"use strict";
|
|
3152
|
-
|
|
3574
|
+
init_http2();
|
|
3153
3575
|
}
|
|
3154
3576
|
});
|
|
3155
3577
|
|
|
3156
|
-
// src/
|
|
3157
|
-
async function
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
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)"
|
|
3164
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
|
+
}
|
|
3165
3705
|
return {
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3706
|
+
wroteFiles,
|
|
3707
|
+
markerPath,
|
|
3708
|
+
usedT0: tutorPersona !== null,
|
|
3709
|
+
usedT1: studentBrief !== null,
|
|
3710
|
+
usedT2: enrollmentProfile !== null,
|
|
3711
|
+
bootstrapped
|
|
3170
3712
|
};
|
|
3171
3713
|
}
|
|
3172
|
-
var
|
|
3173
|
-
|
|
3714
|
+
var logger5, defaultDeps2;
|
|
3715
|
+
var init_instruction_pipeline = __esm({
|
|
3716
|
+
"src/instruction-pipeline.ts"() {
|
|
3174
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();
|
|
3175
3726
|
init_session();
|
|
3176
|
-
|
|
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");
|
|
3177
3811
|
}
|
|
3178
3812
|
});
|
|
3179
3813
|
|
|
3180
3814
|
// src/commands/select.ts
|
|
3181
3815
|
import { Command as Command6 } from "commander";
|
|
3182
|
-
import
|
|
3183
|
-
var
|
|
3816
|
+
import os9 from "node:os";
|
|
3817
|
+
var logger7, selectCommand;
|
|
3184
3818
|
var init_select = __esm({
|
|
3185
3819
|
"src/commands/select.ts"() {
|
|
3186
3820
|
"use strict";
|
|
@@ -3189,14 +3823,15 @@ var init_select = __esm({
|
|
|
3189
3823
|
init_http2();
|
|
3190
3824
|
init_session();
|
|
3191
3825
|
init_formatter();
|
|
3192
|
-
init_instruction_files();
|
|
3193
3826
|
init_status();
|
|
3194
|
-
|
|
3827
|
+
init_instruction_pipeline();
|
|
3828
|
+
init_pipeline_deps();
|
|
3829
|
+
logger7 = createLogger("cli:select");
|
|
3195
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) => {
|
|
3196
3831
|
try {
|
|
3197
3832
|
const session = await requireSession();
|
|
3198
3833
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3199
|
-
const deps = { data, logger:
|
|
3834
|
+
const deps = { data, logger: logger7 };
|
|
3200
3835
|
const courses3 = await listCourses({ userId: session.userId }, deps);
|
|
3201
3836
|
let courseId = course;
|
|
3202
3837
|
let enrollmentId = "";
|
|
@@ -3229,23 +3864,20 @@ var init_select = __esm({
|
|
|
3229
3864
|
enrollmentId
|
|
3230
3865
|
};
|
|
3231
3866
|
const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
|
|
3232
|
-
let
|
|
3867
|
+
let pipelineResult = null;
|
|
3233
3868
|
try {
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
lessonCount: detail.lessonCount,
|
|
3240
|
-
courseDescription: detail.courseDescription,
|
|
3241
|
-
workspaceReady: onboarding.workspaceReady,
|
|
3242
|
-
workspacePath: onboarding.workspacePath ?? void 0
|
|
3243
|
-
});
|
|
3869
|
+
pipelineResult = await resolveAndGenerate(
|
|
3870
|
+
{ courseId: detail.courseId, enrollmentId },
|
|
3871
|
+
{ forceRefresh: false, collectT1Interactive: false, collectT2Interactive: false },
|
|
3872
|
+
{ cwd: process.cwd(), deps: buildPipelineDeps() }
|
|
3873
|
+
);
|
|
3244
3874
|
} catch (err) {
|
|
3245
|
-
|
|
3875
|
+
logger7.warn("Failed to run instruction pipeline", {
|
|
3246
3876
|
error: err instanceof Error ? err.message : String(err)
|
|
3247
3877
|
});
|
|
3248
3878
|
}
|
|
3879
|
+
const slugMatch = pipelineResult?.wroteFiles.find((f) => f.startsWith(".claude/commands/"))?.match(/tostudy-(.+)\.md$/);
|
|
3880
|
+
const courseSlug2 = slugMatch?.[1] ?? "";
|
|
3249
3881
|
if (opts.json) {
|
|
3250
3882
|
output(
|
|
3251
3883
|
{
|
|
@@ -3253,13 +3885,18 @@ var init_select = __esm({
|
|
|
3253
3885
|
enrollmentId,
|
|
3254
3886
|
courseSlug: courseSlug2,
|
|
3255
3887
|
workspacePath: onboarding.workspacePath,
|
|
3256
|
-
workspaceSource: onboarding.workspaceSource
|
|
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
|
|
3257
3894
|
},
|
|
3258
3895
|
{ json: true }
|
|
3259
3896
|
);
|
|
3260
3897
|
} else {
|
|
3261
3898
|
const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
|
|
3262
|
-
const home =
|
|
3899
|
+
const home = os9.homedir();
|
|
3263
3900
|
const cwd = process.cwd();
|
|
3264
3901
|
const namespacedPath = `${cwd}/.tostudy`;
|
|
3265
3902
|
let wsLine;
|
|
@@ -3272,20 +3909,26 @@ var init_select = __esm({
|
|
|
3272
3909
|
} else {
|
|
3273
3910
|
wsLine = " Workspace: rode `tostudy workspace setup` para configurar";
|
|
3274
3911
|
}
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
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"
|
|
3288
3930
|
);
|
|
3931
|
+
output(lines.join("\n"), { json: false });
|
|
3289
3932
|
}
|
|
3290
3933
|
} catch (err) {
|
|
3291
3934
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3297,7 +3940,7 @@ var init_select = __esm({
|
|
|
3297
3940
|
|
|
3298
3941
|
// src/commands/progress.ts
|
|
3299
3942
|
import { Command as Command7 } from "commander";
|
|
3300
|
-
var
|
|
3943
|
+
var logger8, progressCommand;
|
|
3301
3944
|
var init_progress = __esm({
|
|
3302
3945
|
"src/commands/progress.ts"() {
|
|
3303
3946
|
"use strict";
|
|
@@ -3306,15 +3949,15 @@ var init_progress = __esm({
|
|
|
3306
3949
|
init_http2();
|
|
3307
3950
|
init_session();
|
|
3308
3951
|
init_formatter();
|
|
3309
|
-
|
|
3952
|
+
logger8 = createLogger("cli:progress");
|
|
3310
3953
|
progressCommand = new Command7("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
3311
3954
|
try {
|
|
3312
3955
|
const session = await requireSession();
|
|
3313
|
-
const activeCourse = await requireActiveCourse();
|
|
3956
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
3314
3957
|
const driftWarning = await checkCourseDrift();
|
|
3315
3958
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
3316
3959
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
3317
|
-
const deps = { data, logger:
|
|
3960
|
+
const deps = { data, logger: logger8 };
|
|
3318
3961
|
const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
3319
3962
|
if (opts.json) {
|
|
3320
3963
|
output(progress3, { json: true });
|
|
@@ -4875,7 +5518,7 @@ var init_query_promise = __esm({
|
|
|
4875
5518
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
4876
5519
|
const nullifyMap = {};
|
|
4877
5520
|
const result = columns.reduce(
|
|
4878
|
-
(result2, { path:
|
|
5521
|
+
(result2, { path: path17, field }, columnIndex) => {
|
|
4879
5522
|
let decoder;
|
|
4880
5523
|
if (is(field, Column)) {
|
|
4881
5524
|
decoder = field;
|
|
@@ -4887,8 +5530,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4887
5530
|
decoder = field.sql.decoder;
|
|
4888
5531
|
}
|
|
4889
5532
|
let node = result2;
|
|
4890
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
4891
|
-
if (pathChunkIndex <
|
|
5533
|
+
for (const [pathChunkIndex, pathChunk] of path17.entries()) {
|
|
5534
|
+
if (pathChunkIndex < path17.length - 1) {
|
|
4892
5535
|
if (!(pathChunk in node)) {
|
|
4893
5536
|
node[pathChunk] = {};
|
|
4894
5537
|
}
|
|
@@ -4896,8 +5539,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4896
5539
|
} else {
|
|
4897
5540
|
const rawValue = row[columnIndex];
|
|
4898
5541
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
4899
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
4900
|
-
const objectName =
|
|
5542
|
+
if (joinsNotNullableMap && is(field, Column) && path17.length === 2) {
|
|
5543
|
+
const objectName = path17[0];
|
|
4901
5544
|
if (!(objectName in nullifyMap)) {
|
|
4902
5545
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
4903
5546
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -8844,13 +9487,13 @@ function Subscribe(postgres2, options) {
|
|
|
8844
9487
|
}
|
|
8845
9488
|
}
|
|
8846
9489
|
function handle(a, b2) {
|
|
8847
|
-
const
|
|
9490
|
+
const path17 = b2.relation.schema + "." + b2.relation.table;
|
|
8848
9491
|
call("*", a, b2);
|
|
8849
|
-
call("*:" +
|
|
8850
|
-
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);
|
|
8851
9494
|
call(b2.command, a, b2);
|
|
8852
|
-
call(b2.command + ":" +
|
|
8853
|
-
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);
|
|
8854
9497
|
}
|
|
8855
9498
|
function pong() {
|
|
8856
9499
|
const x2 = Buffer.alloc(34);
|
|
@@ -8963,8 +9606,8 @@ function parseEvent(x) {
|
|
|
8963
9606
|
const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
|
|
8964
9607
|
if (!xs)
|
|
8965
9608
|
throw new Error("Malformed subscribe pattern: " + x);
|
|
8966
|
-
const [, command,
|
|
8967
|
-
return (command || "*") + (
|
|
9609
|
+
const [, command, path17, key] = xs;
|
|
9610
|
+
return (command || "*") + (path17 ? ":" + (path17.indexOf(".") === -1 ? "public." + path17 : path17) : "") + (key ? "=" + key : "");
|
|
8968
9611
|
}
|
|
8969
9612
|
var noop2;
|
|
8970
9613
|
var init_subscribe = __esm({
|
|
@@ -9045,8 +9688,8 @@ var init_large = __esm({
|
|
|
9045
9688
|
});
|
|
9046
9689
|
|
|
9047
9690
|
// ../../node_modules/postgres/src/index.js
|
|
9048
|
-
import
|
|
9049
|
-
import
|
|
9691
|
+
import os10 from "os";
|
|
9692
|
+
import fs8 from "fs";
|
|
9050
9693
|
function Postgres(a, b2) {
|
|
9051
9694
|
const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
|
|
9052
9695
|
let ending = false;
|
|
@@ -9102,10 +9745,10 @@ function Postgres(a, b2) {
|
|
|
9102
9745
|
});
|
|
9103
9746
|
return query;
|
|
9104
9747
|
}
|
|
9105
|
-
function file2(
|
|
9748
|
+
function file2(path17, args = [], options2 = {}) {
|
|
9106
9749
|
arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
|
|
9107
9750
|
const query = new Query([], args, (query2) => {
|
|
9108
|
-
|
|
9751
|
+
fs8.readFile(path17, "utf8", (err, string4) => {
|
|
9109
9752
|
if (err)
|
|
9110
9753
|
return query2.reject(err);
|
|
9111
9754
|
query2.strings = [string4];
|
|
@@ -9423,7 +10066,7 @@ function parseUrl(url2) {
|
|
|
9423
10066
|
}
|
|
9424
10067
|
function osUsername() {
|
|
9425
10068
|
try {
|
|
9426
|
-
return
|
|
10069
|
+
return os10.userInfo().username;
|
|
9427
10070
|
} catch (_) {
|
|
9428
10071
|
return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
|
|
9429
10072
|
}
|
|
@@ -13331,7 +13974,7 @@ async function hashQuery(sql2, params) {
|
|
|
13331
13974
|
return hashHex;
|
|
13332
13975
|
}
|
|
13333
13976
|
var Cache, NoopCache;
|
|
13334
|
-
var
|
|
13977
|
+
var init_cache3 = __esm({
|
|
13335
13978
|
"../../node_modules/drizzle-orm/cache/core/cache.js"() {
|
|
13336
13979
|
init_entity();
|
|
13337
13980
|
Cache = class {
|
|
@@ -13356,7 +13999,7 @@ var init_cache = __esm({
|
|
|
13356
13999
|
// ../../node_modules/drizzle-orm/cache/core/index.js
|
|
13357
14000
|
var init_core = __esm({
|
|
13358
14001
|
"../../node_modules/drizzle-orm/cache/core/index.js"() {
|
|
13359
|
-
|
|
14002
|
+
init_cache3();
|
|
13360
14003
|
}
|
|
13361
14004
|
});
|
|
13362
14005
|
|
|
@@ -13464,7 +14107,7 @@ var init_schema = __esm({
|
|
|
13464
14107
|
var PgPreparedQuery, PgSession, PgTransaction;
|
|
13465
14108
|
var init_session2 = __esm({
|
|
13466
14109
|
"../../node_modules/drizzle-orm/pg-core/session.js"() {
|
|
13467
|
-
|
|
14110
|
+
init_cache3();
|
|
13468
14111
|
init_entity();
|
|
13469
14112
|
init_errors();
|
|
13470
14113
|
init_sql2();
|
|
@@ -13684,12 +14327,12 @@ var init_session3 = __esm({
|
|
|
13684
14327
|
init_tracing();
|
|
13685
14328
|
init_utils();
|
|
13686
14329
|
PostgresJsPreparedQuery = class extends PgPreparedQuery {
|
|
13687
|
-
constructor(client, queryString, params,
|
|
14330
|
+
constructor(client, queryString, params, logger23, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
|
|
13688
14331
|
super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
|
|
13689
14332
|
this.client = client;
|
|
13690
14333
|
this.queryString = queryString;
|
|
13691
14334
|
this.params = params;
|
|
13692
|
-
this.logger =
|
|
14335
|
+
this.logger = logger23;
|
|
13693
14336
|
this.fields = fields;
|
|
13694
14337
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
13695
14338
|
this.customResultMapper = customResultMapper;
|
|
@@ -13830,11 +14473,11 @@ function construct(client, config2 = {}) {
|
|
|
13830
14473
|
client.options.serializers["114"] = transparentParser;
|
|
13831
14474
|
client.options.serializers["3802"] = transparentParser;
|
|
13832
14475
|
const dialect = new PgDialect({ casing: config2.casing });
|
|
13833
|
-
let
|
|
14476
|
+
let logger23;
|
|
13834
14477
|
if (config2.logger === true) {
|
|
13835
|
-
|
|
14478
|
+
logger23 = new DefaultLogger();
|
|
13836
14479
|
} else if (config2.logger !== false) {
|
|
13837
|
-
|
|
14480
|
+
logger23 = config2.logger;
|
|
13838
14481
|
}
|
|
13839
14482
|
let schema;
|
|
13840
14483
|
if (config2.schema) {
|
|
@@ -13848,7 +14491,7 @@ function construct(client, config2 = {}) {
|
|
|
13848
14491
|
tableNamesMap: tablesConfig.tableNamesMap
|
|
13849
14492
|
};
|
|
13850
14493
|
}
|
|
13851
|
-
const session = new PostgresJsSession(client, dialect, schema, { logger:
|
|
14494
|
+
const session = new PostgresJsSession(client, dialect, schema, { logger: logger23, cache: config2.cache });
|
|
13852
14495
|
const db2 = new PostgresJsDatabase(dialect, session, schema);
|
|
13853
14496
|
db2.$client = client;
|
|
13854
14497
|
db2.$cache = config2.cache;
|
|
@@ -14163,10 +14806,10 @@ function mergeDefs(...defs) {
|
|
|
14163
14806
|
function cloneDef(schema) {
|
|
14164
14807
|
return mergeDefs(schema._zod.def);
|
|
14165
14808
|
}
|
|
14166
|
-
function getElementAtPath(obj,
|
|
14167
|
-
if (!
|
|
14809
|
+
function getElementAtPath(obj, path17) {
|
|
14810
|
+
if (!path17)
|
|
14168
14811
|
return obj;
|
|
14169
|
-
return
|
|
14812
|
+
return path17.reduce((acc, key) => acc?.[key], obj);
|
|
14170
14813
|
}
|
|
14171
14814
|
function promiseAllObject(promisesObj) {
|
|
14172
14815
|
const keys = Object.keys(promisesObj);
|
|
@@ -14478,11 +15121,11 @@ function aborted(x, startIndex = 0) {
|
|
|
14478
15121
|
}
|
|
14479
15122
|
return false;
|
|
14480
15123
|
}
|
|
14481
|
-
function prefixIssues(
|
|
15124
|
+
function prefixIssues(path17, issues) {
|
|
14482
15125
|
return issues.map((iss) => {
|
|
14483
15126
|
var _a2;
|
|
14484
15127
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
14485
|
-
iss.path.unshift(
|
|
15128
|
+
iss.path.unshift(path17);
|
|
14486
15129
|
return iss;
|
|
14487
15130
|
});
|
|
14488
15131
|
}
|
|
@@ -14724,7 +15367,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14724
15367
|
}
|
|
14725
15368
|
function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
14726
15369
|
const result = { errors: [] };
|
|
14727
|
-
const processError = (error50,
|
|
15370
|
+
const processError = (error50, path17 = []) => {
|
|
14728
15371
|
var _a2, _b;
|
|
14729
15372
|
for (const issue2 of error50.issues) {
|
|
14730
15373
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -14734,7 +15377,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14734
15377
|
} else if (issue2.code === "invalid_element") {
|
|
14735
15378
|
processError({ issues: issue2.issues }, issue2.path);
|
|
14736
15379
|
} else {
|
|
14737
|
-
const fullpath = [...
|
|
15380
|
+
const fullpath = [...path17, ...issue2.path];
|
|
14738
15381
|
if (fullpath.length === 0) {
|
|
14739
15382
|
result.errors.push(mapper(issue2));
|
|
14740
15383
|
continue;
|
|
@@ -14766,8 +15409,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14766
15409
|
}
|
|
14767
15410
|
function toDotPath(_path) {
|
|
14768
15411
|
const segs = [];
|
|
14769
|
-
const
|
|
14770
|
-
for (const seg of
|
|
15412
|
+
const path17 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
15413
|
+
for (const seg of path17) {
|
|
14771
15414
|
if (typeof seg === "number")
|
|
14772
15415
|
segs.push(`[${seg}]`);
|
|
14773
15416
|
else if (typeof seg === "symbol")
|
|
@@ -24678,7 +25321,7 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
|
|
|
24678
25321
|
return inst;
|
|
24679
25322
|
}
|
|
24680
25323
|
var TimePrecision;
|
|
24681
|
-
var
|
|
25324
|
+
var init_api4 = __esm({
|
|
24682
25325
|
"../../node_modules/zod/v4/core/api.js"() {
|
|
24683
25326
|
init_checks2();
|
|
24684
25327
|
init_registries();
|
|
@@ -25989,7 +26632,7 @@ var init_core3 = __esm({
|
|
|
25989
26632
|
init_locales();
|
|
25990
26633
|
init_registries();
|
|
25991
26634
|
init_doc();
|
|
25992
|
-
|
|
26635
|
+
init_api4();
|
|
25993
26636
|
init_to_json_schema();
|
|
25994
26637
|
init_json_schema_processors();
|
|
25995
26638
|
init_json_schema_generator();
|
|
@@ -27461,13 +28104,13 @@ function resolveRef(ref, ctx) {
|
|
|
27461
28104
|
if (!ref.startsWith("#")) {
|
|
27462
28105
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
27463
28106
|
}
|
|
27464
|
-
const
|
|
27465
|
-
if (
|
|
28107
|
+
const path17 = ref.slice(1).split("/").filter(Boolean);
|
|
28108
|
+
if (path17.length === 0) {
|
|
27466
28109
|
return ctx.rootSchema;
|
|
27467
28110
|
}
|
|
27468
28111
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
27469
|
-
if (
|
|
27470
|
-
const key =
|
|
28112
|
+
if (path17[0] === defsKey) {
|
|
28113
|
+
const key = path17[1];
|
|
27471
28114
|
if (!key || !ctx.defs[key]) {
|
|
27472
28115
|
throw new Error(`Reference not found: ${ref}`);
|
|
27473
28116
|
}
|
|
@@ -38785,27 +39428,33 @@ var init_media_assets = __esm({
|
|
|
38785
39428
|
"marketing",
|
|
38786
39429
|
"other"
|
|
38787
39430
|
];
|
|
38788
|
-
mediaAssets = pgTable(
|
|
38789
|
-
|
|
38790
|
-
|
|
38791
|
-
|
|
38792
|
-
|
|
38793
|
-
|
|
38794
|
-
|
|
38795
|
-
|
|
38796
|
-
|
|
38797
|
-
|
|
38798
|
-
|
|
38799
|
-
|
|
38800
|
-
|
|
38801
|
-
|
|
38802
|
-
|
|
38803
|
-
|
|
38804
|
-
|
|
38805
|
-
|
|
38806
|
-
|
|
38807
|
-
|
|
38808
|
-
|
|
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
|
+
);
|
|
38809
39458
|
}
|
|
38810
39459
|
});
|
|
38811
39460
|
|
|
@@ -40995,10 +41644,10 @@ var init_lessons2 = __esm({
|
|
|
40995
41644
|
|
|
40996
41645
|
// src/commands/start.ts
|
|
40997
41646
|
import { Command as Command8 } from "commander";
|
|
40998
|
-
async function runStart(opts, deps =
|
|
41647
|
+
async function runStart(opts, deps = defaultDeps3) {
|
|
40999
41648
|
try {
|
|
41000
41649
|
const session = await deps.requireSession();
|
|
41001
|
-
const activeCourse = await deps.requireActiveCourse();
|
|
41650
|
+
const activeCourse = await deps.requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41002
41651
|
const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
|
|
41003
41652
|
if (!onboarding.initReady) {
|
|
41004
41653
|
deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
|
|
@@ -41027,7 +41676,7 @@ async function runStart(opts, deps = defaultDeps2) {
|
|
|
41027
41676
|
deps.error(msg);
|
|
41028
41677
|
}
|
|
41029
41678
|
}
|
|
41030
|
-
var
|
|
41679
|
+
var logger9, defaultDeps3, startCommand;
|
|
41031
41680
|
var init_start = __esm({
|
|
41032
41681
|
"src/commands/start.ts"() {
|
|
41033
41682
|
"use strict";
|
|
@@ -41037,8 +41686,8 @@ var init_start = __esm({
|
|
|
41037
41686
|
init_session();
|
|
41038
41687
|
init_status();
|
|
41039
41688
|
init_formatter();
|
|
41040
|
-
|
|
41041
|
-
|
|
41689
|
+
logger9 = createLogger("cli:start");
|
|
41690
|
+
defaultDeps3 = {
|
|
41042
41691
|
requireSession,
|
|
41043
41692
|
requireActiveCourse,
|
|
41044
41693
|
checkCourseDrift,
|
|
@@ -41050,7 +41699,7 @@ var init_start = __esm({
|
|
|
41050
41699
|
output,
|
|
41051
41700
|
error,
|
|
41052
41701
|
stderrWrite: (message) => process.stderr.write(message),
|
|
41053
|
-
logger:
|
|
41702
|
+
logger: logger9
|
|
41054
41703
|
};
|
|
41055
41704
|
startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41056
41705
|
await runStart(opts);
|
|
@@ -41060,7 +41709,7 @@ var init_start = __esm({
|
|
|
41060
41709
|
|
|
41061
41710
|
// src/commands/start-next.ts
|
|
41062
41711
|
import { Command as Command9 } from "commander";
|
|
41063
|
-
var
|
|
41712
|
+
var logger10, startNextCommand;
|
|
41064
41713
|
var init_start_next = __esm({
|
|
41065
41714
|
"src/commands/start-next.ts"() {
|
|
41066
41715
|
"use strict";
|
|
@@ -41069,7 +41718,7 @@ var init_start_next = __esm({
|
|
|
41069
41718
|
init_http2();
|
|
41070
41719
|
init_session();
|
|
41071
41720
|
init_formatter();
|
|
41072
|
-
|
|
41721
|
+
logger10 = createLogger("cli:start-next");
|
|
41073
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) => {
|
|
41074
41723
|
try {
|
|
41075
41724
|
const session = await requireSession();
|
|
@@ -41077,7 +41726,7 @@ var init_start_next = __esm({
|
|
|
41077
41726
|
const driftWarning = await checkCourseDrift();
|
|
41078
41727
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41079
41728
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41080
|
-
const deps = { data, logger:
|
|
41729
|
+
const deps = { data, logger: logger10 };
|
|
41081
41730
|
const moduleData = await startNextModule({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
41082
41731
|
await setActiveCourse({ ...activeCourse, currentLessonId: moduleData.firstLesson.id });
|
|
41083
41732
|
if (opts.json) {
|
|
@@ -41100,7 +41749,7 @@ var init_start_next = __esm({
|
|
|
41100
41749
|
|
|
41101
41750
|
// src/commands/next.ts
|
|
41102
41751
|
import { Command as Command10 } from "commander";
|
|
41103
|
-
var
|
|
41752
|
+
var logger11, nextCommand;
|
|
41104
41753
|
var init_next = __esm({
|
|
41105
41754
|
"src/commands/next.ts"() {
|
|
41106
41755
|
"use strict";
|
|
@@ -41109,15 +41758,15 @@ var init_next = __esm({
|
|
|
41109
41758
|
init_http2();
|
|
41110
41759
|
init_session();
|
|
41111
41760
|
init_formatter();
|
|
41112
|
-
|
|
41761
|
+
logger11 = createLogger("cli:next");
|
|
41113
41762
|
nextCommand = new Command10("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41114
41763
|
try {
|
|
41115
41764
|
const session = await requireSession();
|
|
41116
|
-
const activeCourse = await requireActiveCourse();
|
|
41765
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41117
41766
|
const driftWarning = await checkCourseDrift();
|
|
41118
41767
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41119
41768
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41120
|
-
const deps = { data, logger:
|
|
41769
|
+
const deps = { data, logger: logger11 };
|
|
41121
41770
|
const lessonData = await nextLesson(
|
|
41122
41771
|
{ enrollmentId: activeCourse.enrollmentId, userConfirmation: "cli-next" },
|
|
41123
41772
|
deps
|
|
@@ -41198,7 +41847,7 @@ function formatLessonContent(data) {
|
|
|
41198
41847
|
}
|
|
41199
41848
|
return lines.join("\n");
|
|
41200
41849
|
}
|
|
41201
|
-
var
|
|
41850
|
+
var logger12, lessonCommand;
|
|
41202
41851
|
var init_lesson = __esm({
|
|
41203
41852
|
"src/commands/lesson.ts"() {
|
|
41204
41853
|
"use strict";
|
|
@@ -41208,15 +41857,15 @@ var init_lesson = __esm({
|
|
|
41208
41857
|
init_session();
|
|
41209
41858
|
init_formatter();
|
|
41210
41859
|
init_resolve();
|
|
41211
|
-
|
|
41860
|
+
logger12 = createLogger("cli:lesson");
|
|
41212
41861
|
lessonCommand = new Command11("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41213
41862
|
try {
|
|
41214
41863
|
const session = await requireSession();
|
|
41215
|
-
const activeCourse = await requireActiveCourse();
|
|
41864
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41216
41865
|
const driftWarning = await checkCourseDrift();
|
|
41217
41866
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41218
41867
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41219
|
-
const deps = { data, logger:
|
|
41868
|
+
const deps = { data, logger: logger12 };
|
|
41220
41869
|
const lessonId = activeCourse.currentLessonId;
|
|
41221
41870
|
if (!lessonId) {
|
|
41222
41871
|
error("Nenhuma li\xE7\xE3o ativa encontrada. Rode: tostudy start ou tostudy next");
|
|
@@ -41249,7 +41898,7 @@ var init_lesson = __esm({
|
|
|
41249
41898
|
|
|
41250
41899
|
// src/commands/hint.ts
|
|
41251
41900
|
import { Command as Command12 } from "commander";
|
|
41252
|
-
var
|
|
41901
|
+
var logger13, hintCommand;
|
|
41253
41902
|
var init_hint = __esm({
|
|
41254
41903
|
"src/commands/hint.ts"() {
|
|
41255
41904
|
"use strict";
|
|
@@ -41258,15 +41907,15 @@ var init_hint = __esm({
|
|
|
41258
41907
|
init_http2();
|
|
41259
41908
|
init_session();
|
|
41260
41909
|
init_formatter();
|
|
41261
|
-
|
|
41910
|
+
logger13 = createLogger("cli:hint");
|
|
41262
41911
|
hintCommand = new Command12("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41263
41912
|
try {
|
|
41264
41913
|
const session = await requireSession();
|
|
41265
|
-
const activeCourse = await requireActiveCourse();
|
|
41914
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41266
41915
|
const driftWarning = await checkCourseDrift();
|
|
41267
41916
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41268
41917
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41269
|
-
const deps = { data, logger:
|
|
41918
|
+
const deps = { data, logger: logger13 };
|
|
41270
41919
|
const hint = await getHint(
|
|
41271
41920
|
{ userId: session.userId, enrollmentId: activeCourse.enrollmentId },
|
|
41272
41921
|
deps
|
|
@@ -41312,10 +41961,10 @@ var init_exercises = __esm({
|
|
|
41312
41961
|
});
|
|
41313
41962
|
|
|
41314
41963
|
// src/commands/validate.ts
|
|
41315
|
-
import
|
|
41316
|
-
import
|
|
41964
|
+
import fs9 from "node:fs";
|
|
41965
|
+
import path9 from "node:path";
|
|
41317
41966
|
import { Command as Command13 } from "commander";
|
|
41318
|
-
var
|
|
41967
|
+
var logger14, validateCommand;
|
|
41319
41968
|
var init_validate = __esm({
|
|
41320
41969
|
"src/commands/validate.ts"() {
|
|
41321
41970
|
"use strict";
|
|
@@ -41325,11 +41974,11 @@ var init_validate = __esm({
|
|
|
41325
41974
|
init_session();
|
|
41326
41975
|
init_formatter();
|
|
41327
41976
|
init_init_template();
|
|
41328
|
-
|
|
41977
|
+
logger14 = createLogger("cli:validate");
|
|
41329
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) => {
|
|
41330
41979
|
try {
|
|
41331
41980
|
const session = await requireSession();
|
|
41332
|
-
const activeCourse = await requireActiveCourse();
|
|
41981
|
+
const activeCourse = await requireActiveCourse(void 0, { respectWorkspace: true });
|
|
41333
41982
|
const driftWarning = await checkCourseDrift();
|
|
41334
41983
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
41335
41984
|
const lessonId = activeCourse.currentLessonId;
|
|
@@ -41338,17 +41987,17 @@ var init_validate = __esm({
|
|
|
41338
41987
|
}
|
|
41339
41988
|
let solution;
|
|
41340
41989
|
if (opts.stdin) {
|
|
41341
|
-
solution =
|
|
41990
|
+
solution = fs9.readFileSync("/dev/stdin", "utf-8");
|
|
41342
41991
|
} else if (file2) {
|
|
41343
|
-
if (!
|
|
41992
|
+
if (!fs9.existsSync(file2)) {
|
|
41344
41993
|
error(`Arquivo n\xE3o encontrado: ${file2}`);
|
|
41345
41994
|
}
|
|
41346
|
-
solution =
|
|
41995
|
+
solution = fs9.readFileSync(file2, "utf-8");
|
|
41347
41996
|
} else {
|
|
41348
41997
|
error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
|
|
41349
41998
|
}
|
|
41350
41999
|
if (file2 && activeCourse.courseTags?.length) {
|
|
41351
|
-
const ext =
|
|
42000
|
+
const ext = path9.extname(file2).toLowerCase();
|
|
41352
42001
|
const LANG_EXTENSIONS = {
|
|
41353
42002
|
".html": ["html", "html5"],
|
|
41354
42003
|
".css": ["css"],
|
|
@@ -41384,7 +42033,7 @@ var init_validate = __esm({
|
|
|
41384
42033
|
}
|
|
41385
42034
|
}
|
|
41386
42035
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
41387
|
-
const deps = { data, logger:
|
|
42036
|
+
const deps = { data, logger: logger14 };
|
|
41388
42037
|
const result = await validateSolution(
|
|
41389
42038
|
{
|
|
41390
42039
|
lessonId,
|
|
@@ -41473,9 +42122,9 @@ var init_menu = __esm({
|
|
|
41473
42122
|
});
|
|
41474
42123
|
|
|
41475
42124
|
// src/onboarding/learner-context.ts
|
|
41476
|
-
import
|
|
42125
|
+
import readline2 from "node:readline/promises";
|
|
41477
42126
|
import { stdin as input, stdout as output2 } from "node:process";
|
|
41478
|
-
async function
|
|
42127
|
+
async function askNonEmpty2(question, deps, defaultValue) {
|
|
41479
42128
|
while (true) {
|
|
41480
42129
|
const answer = (await deps.ask(question)).trim();
|
|
41481
42130
|
if (answer.length > 0) return answer;
|
|
@@ -41491,7 +42140,7 @@ async function askChoice(question, choices, deps, defaultChoice) {
|
|
|
41491
42140
|
}
|
|
41492
42141
|
}
|
|
41493
42142
|
function createPromptDeps() {
|
|
41494
|
-
const rl =
|
|
42143
|
+
const rl = readline2.createInterface({ input, output: output2 });
|
|
41495
42144
|
return {
|
|
41496
42145
|
ask: (question) => rl.question(question),
|
|
41497
42146
|
write: (content) => output2.write(content),
|
|
@@ -41524,32 +42173,32 @@ async function collectLearnerContextProfileWithDeps(input2, deps) {
|
|
|
41524
42173
|
"keep"
|
|
41525
42174
|
) : "edit";
|
|
41526
42175
|
const baseProfile = existingProfile;
|
|
41527
|
-
const segment = action === "keep" && baseProfile ? baseProfile.segment : await
|
|
42176
|
+
const segment = action === "keep" && baseProfile ? baseProfile.segment : await askNonEmpty2(
|
|
41528
42177
|
baseProfile ? `Segmento ou nicho do aluno [${baseProfile.segment}]: ` : "Segmento ou nicho do aluno: ",
|
|
41529
42178
|
deps,
|
|
41530
42179
|
baseProfile?.segment
|
|
41531
42180
|
);
|
|
41532
|
-
const company = action === "keep" && baseProfile ? baseProfile.company : await
|
|
42181
|
+
const company = action === "keep" && baseProfile ? baseProfile.company : await askNonEmpty2(
|
|
41533
42182
|
baseProfile ? `Empresa ou tipo de negocio [${baseProfile.company}]: ` : "Empresa ou tipo de negocio: ",
|
|
41534
42183
|
deps,
|
|
41535
42184
|
baseProfile?.company
|
|
41536
42185
|
);
|
|
41537
|
-
const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await
|
|
42186
|
+
const productsOrServices = action === "keep" && baseProfile ? baseProfile.productsOrServices : await askNonEmpty2(
|
|
41538
42187
|
baseProfile ? `Produtos ou servicos principais [${baseProfile.productsOrServices}]: ` : "Produtos ou servicos principais: ",
|
|
41539
42188
|
deps,
|
|
41540
42189
|
baseProfile?.productsOrServices
|
|
41541
42190
|
);
|
|
41542
|
-
const region = action === "keep" && baseProfile ? baseProfile.region : await
|
|
42191
|
+
const region = action === "keep" && baseProfile ? baseProfile.region : await askNonEmpty2(
|
|
41543
42192
|
baseProfile ? `Regiao de atuacao [${baseProfile.region}]: ` : "Regiao de atuacao: ",
|
|
41544
42193
|
deps,
|
|
41545
42194
|
baseProfile?.region
|
|
41546
42195
|
);
|
|
41547
|
-
const team = action === "keep" && baseProfile ? baseProfile.team : await
|
|
42196
|
+
const team = action === "keep" && baseProfile ? baseProfile.team : await askNonEmpty2(
|
|
41548
42197
|
baseProfile ? `Equipe envolvida neste contexto [${baseProfile.team}]: ` : "Equipe envolvida neste contexto: ",
|
|
41549
42198
|
deps,
|
|
41550
42199
|
baseProfile?.team
|
|
41551
42200
|
);
|
|
41552
|
-
const goal = action === "keep" && baseProfile ? baseProfile.goal : await
|
|
42201
|
+
const goal = action === "keep" && baseProfile ? baseProfile.goal : await askNonEmpty2(
|
|
41553
42202
|
baseProfile ? `Objetivo principal com este curso [${baseProfile.goal}]: ` : "Objetivo principal com este curso: ",
|
|
41554
42203
|
deps,
|
|
41555
42204
|
baseProfile?.goal
|
|
@@ -41598,7 +42247,7 @@ function isCompleteProfile(flags) {
|
|
|
41598
42247
|
flags.segment && flags.company && flags.products && flags.region && flags.team && flags.goal && flags.level
|
|
41599
42248
|
);
|
|
41600
42249
|
}
|
|
41601
|
-
async function runInit(deps =
|
|
42250
|
+
async function runInit(deps = defaultDeps4, flags = {}) {
|
|
41602
42251
|
const session = await deps.getSession();
|
|
41603
42252
|
if (!session) {
|
|
41604
42253
|
deps.output("Nao autenticado. Rode `tostudy login` para comecar.", { json: false });
|
|
@@ -41713,22 +42362,20 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
|
|
|
41713
42362
|
});
|
|
41714
42363
|
await deps.saveCourseLearnerProfile(activeCourse, learnerProfile, artifacts);
|
|
41715
42364
|
try {
|
|
41716
|
-
|
|
41717
|
-
{
|
|
41718
|
-
|
|
41719
|
-
|
|
41720
|
-
progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0,
|
|
41721
|
-
moduleCount: progressData?.currentModule?.totalModules ?? 0,
|
|
41722
|
-
lessonCount: progressData?.currentLesson?.totalLessons ?? 0,
|
|
41723
|
-
currentModuleTitle: progressData?.currentModule?.title,
|
|
41724
|
-
currentLessonTitle: progressData?.currentLesson?.title,
|
|
41725
|
-
courseDescription: matchedCourse.description ?? void 0
|
|
41726
|
-
},
|
|
41727
|
-
learnerProfile
|
|
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() }
|
|
41728
42369
|
);
|
|
41729
|
-
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
|
+
});
|
|
41730
42377
|
} catch (err) {
|
|
41731
|
-
deps.logger.warn("Failed to
|
|
42378
|
+
deps.logger.warn("Failed to run instruction pipeline", {
|
|
41732
42379
|
error: err instanceof Error ? err.message : String(err)
|
|
41733
42380
|
});
|
|
41734
42381
|
}
|
|
@@ -41744,7 +42391,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
|
|
|
41744
42391
|
deps.output(artifacts.learnerBrief, { json: false });
|
|
41745
42392
|
}
|
|
41746
42393
|
}
|
|
41747
|
-
var
|
|
42394
|
+
var logger15, defaultDeps4, initCommand;
|
|
41748
42395
|
var init_init = __esm({
|
|
41749
42396
|
"src/commands/init.ts"() {
|
|
41750
42397
|
"use strict";
|
|
@@ -41756,9 +42403,10 @@ var init_init = __esm({
|
|
|
41756
42403
|
init_init_template();
|
|
41757
42404
|
init_learner_context();
|
|
41758
42405
|
init_api();
|
|
41759
|
-
|
|
41760
|
-
|
|
41761
|
-
|
|
42406
|
+
init_instruction_pipeline();
|
|
42407
|
+
init_pipeline_deps();
|
|
42408
|
+
logger15 = createLogger("cli:init");
|
|
42409
|
+
defaultDeps4 = {
|
|
41762
42410
|
getSession,
|
|
41763
42411
|
getActiveCourse,
|
|
41764
42412
|
listCourses,
|
|
@@ -41771,24 +42419,26 @@ var init_init = __esm({
|
|
|
41771
42419
|
saveCourseLearnerProfile,
|
|
41772
42420
|
buildInitArtifacts,
|
|
41773
42421
|
output,
|
|
41774
|
-
logger:
|
|
41775
|
-
createHttpProvider
|
|
42422
|
+
logger: logger15,
|
|
42423
|
+
createHttpProvider,
|
|
42424
|
+
resolveAndGenerate,
|
|
42425
|
+
buildPipelineDeps
|
|
41776
42426
|
};
|
|
41777
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) => {
|
|
41778
|
-
await runInit(
|
|
42428
|
+
await runInit(defaultDeps4, opts);
|
|
41779
42429
|
});
|
|
41780
42430
|
}
|
|
41781
42431
|
});
|
|
41782
42432
|
|
|
41783
42433
|
// ../../packages/tostudy-core/src/workspace/setup-workspace.ts
|
|
41784
|
-
import
|
|
41785
|
-
import
|
|
42434
|
+
import fs10 from "node:fs/promises";
|
|
42435
|
+
import path10 from "node:path";
|
|
41786
42436
|
async function setupWorkspace(input2) {
|
|
41787
|
-
const workspacePath =
|
|
42437
|
+
const workspacePath = path10.join(input2.basePath, input2.courseSlug);
|
|
41788
42438
|
for (const dir of WORKSPACE_DIRS) {
|
|
41789
|
-
await
|
|
42439
|
+
await fs10.mkdir(path10.join(workspacePath, dir), { recursive: true });
|
|
41790
42440
|
}
|
|
41791
|
-
const configPath =
|
|
42441
|
+
const configPath = path10.join(workspacePath, ".ana-config.json");
|
|
41792
42442
|
const config2 = {
|
|
41793
42443
|
courseId: input2.courseId,
|
|
41794
42444
|
courseSlug: input2.courseSlug,
|
|
@@ -41798,7 +42448,7 @@ async function setupWorkspace(input2) {
|
|
|
41798
42448
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41799
42449
|
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41800
42450
|
};
|
|
41801
|
-
await
|
|
42451
|
+
await fs10.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
41802
42452
|
const readme = [
|
|
41803
42453
|
`# ${input2.courseName}`,
|
|
41804
42454
|
"",
|
|
@@ -41823,7 +42473,7 @@ async function setupWorkspace(input2) {
|
|
|
41823
42473
|
"tostudy vault sync # Sincronizar progresso",
|
|
41824
42474
|
"```"
|
|
41825
42475
|
].join("\n");
|
|
41826
|
-
await
|
|
42476
|
+
await fs10.writeFile(path10.join(workspacePath, "README.md"), readme, "utf-8");
|
|
41827
42477
|
return { workspacePath, directories: WORKSPACE_DIRS, configPath };
|
|
41828
42478
|
}
|
|
41829
42479
|
var WORKSPACE_DIRS;
|
|
@@ -41924,8 +42574,8 @@ var init_templates = __esm({
|
|
|
41924
42574
|
});
|
|
41925
42575
|
|
|
41926
42576
|
// ../../packages/tostudy-core/src/workspace/extract-exercise.ts
|
|
41927
|
-
import
|
|
41928
|
-
import
|
|
42577
|
+
import fs11 from "node:fs/promises";
|
|
42578
|
+
import path11 from "node:path";
|
|
41929
42579
|
function padOrder(n) {
|
|
41930
42580
|
return String(n).padStart(2, "0");
|
|
41931
42581
|
}
|
|
@@ -41949,16 +42599,16 @@ async function extractExercise(input2) {
|
|
|
41949
42599
|
const { lessonData, exerciseTier, workspacePath } = input2;
|
|
41950
42600
|
const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
|
|
41951
42601
|
const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
|
|
41952
|
-
const exercisePath =
|
|
41953
|
-
await
|
|
42602
|
+
const exercisePath = path11.join(workspacePath, "exercises", moduleDir, lessonDir);
|
|
42603
|
+
await fs11.mkdir(exercisePath, { recursive: true });
|
|
41954
42604
|
const extractedFiles = [];
|
|
41955
42605
|
let hasStarterCode = false;
|
|
41956
42606
|
if (lessonData.sandpackConfig?.files) {
|
|
41957
42607
|
for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
|
|
41958
42608
|
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
41959
|
-
const fullPath =
|
|
41960
|
-
await
|
|
41961
|
-
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");
|
|
41962
42612
|
extractedFiles.push(cleanPath);
|
|
41963
42613
|
hasStarterCode = true;
|
|
41964
42614
|
}
|
|
@@ -41966,13 +42616,13 @@ async function extractExercise(input2) {
|
|
|
41966
42616
|
const tierData = getTierData(lessonData.structuredData, exerciseTier);
|
|
41967
42617
|
const tierCode = tierData?.code;
|
|
41968
42618
|
if (tierCode) {
|
|
41969
|
-
await
|
|
42619
|
+
await fs11.writeFile(path11.join(exercisePath, "exercise.js"), tierCode, "utf-8");
|
|
41970
42620
|
extractedFiles.push("exercise.js");
|
|
41971
42621
|
hasStarterCode = true;
|
|
41972
42622
|
} else {
|
|
41973
42623
|
const starter = getStarterCode(lessonData.structuredData);
|
|
41974
42624
|
if (starter) {
|
|
41975
|
-
await
|
|
42625
|
+
await fs11.writeFile(path11.join(exercisePath, "exercise.js"), starter, "utf-8");
|
|
41976
42626
|
extractedFiles.push("exercise.js");
|
|
41977
42627
|
hasStarterCode = true;
|
|
41978
42628
|
}
|
|
@@ -41990,8 +42640,8 @@ async function extractExercise(input2) {
|
|
|
41990
42640
|
...exerciseDeps
|
|
41991
42641
|
}
|
|
41992
42642
|
};
|
|
41993
|
-
await
|
|
41994
|
-
|
|
42643
|
+
await fs11.writeFile(
|
|
42644
|
+
path11.join(exercisePath, "package.json"),
|
|
41995
42645
|
JSON.stringify(pkgJson, null, 2),
|
|
41996
42646
|
"utf-8"
|
|
41997
42647
|
);
|
|
@@ -42003,20 +42653,20 @@ async function extractExercise(input2) {
|
|
|
42003
42653
|
);
|
|
42004
42654
|
for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
|
|
42005
42655
|
if (!sandpackFileNames.has(configFile)) {
|
|
42006
|
-
await
|
|
42656
|
+
await fs11.writeFile(path11.join(exercisePath, configFile), configContent, "utf-8");
|
|
42007
42657
|
extractedFiles.push(configFile);
|
|
42008
42658
|
}
|
|
42009
42659
|
}
|
|
42010
42660
|
const setupSh = `#!/bin/sh
|
|
42011
42661
|
${scaffold.setupScript}
|
|
42012
42662
|
`;
|
|
42013
|
-
await
|
|
42663
|
+
await fs11.writeFile(path11.join(exercisePath, "setup.sh"), setupSh, "utf-8");
|
|
42014
42664
|
extractedFiles.push("setup.sh");
|
|
42015
42665
|
}
|
|
42016
42666
|
}
|
|
42017
42667
|
const readme = generateReadme(lessonData, exerciseTier);
|
|
42018
|
-
const readmePath =
|
|
42019
|
-
await
|
|
42668
|
+
const readmePath = path11.join(exercisePath, "README.md");
|
|
42669
|
+
await fs11.writeFile(readmePath, readme, "utf-8");
|
|
42020
42670
|
extractedFiles.push("README.md");
|
|
42021
42671
|
return {
|
|
42022
42672
|
exercisePath,
|
|
@@ -42088,10 +42738,10 @@ var init_workspace = __esm({
|
|
|
42088
42738
|
|
|
42089
42739
|
// src/commands/workspace.ts
|
|
42090
42740
|
import { Command as Command16 } from "commander";
|
|
42091
|
-
import
|
|
42092
|
-
import
|
|
42093
|
-
import
|
|
42094
|
-
var
|
|
42741
|
+
import path12 from "node:path";
|
|
42742
|
+
import os11 from "node:os";
|
|
42743
|
+
import fs12 from "node:fs/promises";
|
|
42744
|
+
var logger16, workspaceCommand;
|
|
42095
42745
|
var init_workspace2 = __esm({
|
|
42096
42746
|
"src/commands/workspace.ts"() {
|
|
42097
42747
|
"use strict";
|
|
@@ -42099,7 +42749,7 @@ var init_workspace2 = __esm({
|
|
|
42099
42749
|
init_workspace();
|
|
42100
42750
|
init_session();
|
|
42101
42751
|
init_resolve();
|
|
42102
|
-
|
|
42752
|
+
logger16 = createLogger("cli:workspace");
|
|
42103
42753
|
workspaceCommand = new Command16("workspace").description(
|
|
42104
42754
|
"Gerenciar workspace de estudo local"
|
|
42105
42755
|
);
|
|
@@ -42112,14 +42762,14 @@ var init_workspace2 = __esm({
|
|
|
42112
42762
|
let directories;
|
|
42113
42763
|
if (!opts.path && cwdIsWorkspace) {
|
|
42114
42764
|
const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
|
|
42115
|
-
workspacePath = resolvedCwd ??
|
|
42116
|
-
await
|
|
42765
|
+
workspacePath = resolvedCwd ?? path12.join(process.cwd(), ".tostudy");
|
|
42766
|
+
await fs12.mkdir(workspacePath, { recursive: true });
|
|
42117
42767
|
directories = ["exercises", "generated", "notes", "diagrams"];
|
|
42118
42768
|
for (const dir of directories) {
|
|
42119
|
-
await
|
|
42769
|
+
await fs12.mkdir(path12.join(workspacePath, dir), { recursive: true });
|
|
42120
42770
|
}
|
|
42121
|
-
const configPath =
|
|
42122
|
-
await
|
|
42771
|
+
const configPath = path12.join(workspacePath, ".ana-config.json");
|
|
42772
|
+
await fs12.writeFile(
|
|
42123
42773
|
configPath,
|
|
42124
42774
|
JSON.stringify(
|
|
42125
42775
|
{
|
|
@@ -42137,7 +42787,7 @@ var init_workspace2 = __esm({
|
|
|
42137
42787
|
"utf-8"
|
|
42138
42788
|
);
|
|
42139
42789
|
} else {
|
|
42140
|
-
const basePath = opts.path ??
|
|
42790
|
+
const basePath = opts.path ?? path12.join(os11.homedir(), "study");
|
|
42141
42791
|
const result2 = await setupWorkspace({
|
|
42142
42792
|
courseId: activeCourse.courseId,
|
|
42143
42793
|
courseSlug: courseSlug(activeCourse.courseTitle),
|
|
@@ -42165,13 +42815,13 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42165
42815
|
);
|
|
42166
42816
|
}
|
|
42167
42817
|
} catch (err) {
|
|
42168
|
-
|
|
42818
|
+
logger16.error("workspace setup failed", { error: err });
|
|
42169
42819
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42170
42820
|
`);
|
|
42171
42821
|
process.exit(1);
|
|
42172
42822
|
}
|
|
42173
42823
|
});
|
|
42174
|
-
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) => {
|
|
42175
42825
|
try {
|
|
42176
42826
|
const activeCourse = await requireActiveCourse();
|
|
42177
42827
|
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
@@ -42190,40 +42840,40 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42190
42840
|
const workspacePath = ws.workspacePath;
|
|
42191
42841
|
let configData = null;
|
|
42192
42842
|
try {
|
|
42193
|
-
const raw = await
|
|
42843
|
+
const raw = await fs12.readFile(path12.join(workspacePath, ".ana-config.json"), "utf-8");
|
|
42194
42844
|
configData = JSON.parse(raw);
|
|
42195
42845
|
} catch {
|
|
42196
42846
|
configData = null;
|
|
42197
42847
|
}
|
|
42198
|
-
const exercisesDir =
|
|
42848
|
+
const exercisesDir = path12.join(workspacePath, "exercises");
|
|
42199
42849
|
let exerciseCount = 0;
|
|
42200
42850
|
try {
|
|
42201
|
-
const moduleDirs = await
|
|
42851
|
+
const moduleDirs = await fs12.readdir(exercisesDir);
|
|
42202
42852
|
for (const modDir of moduleDirs) {
|
|
42203
|
-
const modPath =
|
|
42204
|
-
const stat = await
|
|
42853
|
+
const modPath = path12.join(exercisesDir, modDir);
|
|
42854
|
+
const stat = await fs12.stat(modPath);
|
|
42205
42855
|
if (stat.isDirectory()) {
|
|
42206
|
-
const lessonDirs = await
|
|
42856
|
+
const lessonDirs = await fs12.readdir(modPath);
|
|
42207
42857
|
for (const lessonDir of lessonDirs) {
|
|
42208
|
-
const lessonPath =
|
|
42209
|
-
const lstat = await
|
|
42858
|
+
const lessonPath = path12.join(modPath, lessonDir);
|
|
42859
|
+
const lstat = await fs12.stat(lessonPath);
|
|
42210
42860
|
if (lstat.isDirectory()) exerciseCount++;
|
|
42211
42861
|
}
|
|
42212
42862
|
}
|
|
42213
42863
|
}
|
|
42214
42864
|
} catch {
|
|
42215
42865
|
}
|
|
42216
|
-
const generatedDir =
|
|
42866
|
+
const generatedDir = path12.join(workspacePath, "generated");
|
|
42217
42867
|
let artifactCount = 0;
|
|
42218
42868
|
try {
|
|
42219
|
-
const files = await
|
|
42869
|
+
const files = await fs12.readdir(generatedDir);
|
|
42220
42870
|
artifactCount = files.length;
|
|
42221
42871
|
} catch {
|
|
42222
42872
|
}
|
|
42223
|
-
const diagramsDir =
|
|
42873
|
+
const diagramsDir = path12.join(workspacePath, "diagrams");
|
|
42224
42874
|
let diagramCount = 0;
|
|
42225
42875
|
try {
|
|
42226
|
-
const files = await
|
|
42876
|
+
const files = await fs12.readdir(diagramsDir);
|
|
42227
42877
|
diagramCount = files.length;
|
|
42228
42878
|
} catch {
|
|
42229
42879
|
}
|
|
@@ -42272,10 +42922,10 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42272
42922
|
|
|
42273
42923
|
// src/commands/export.ts
|
|
42274
42924
|
import { Command as Command17 } from "commander";
|
|
42275
|
-
import
|
|
42276
|
-
import
|
|
42277
|
-
import
|
|
42278
|
-
var
|
|
42925
|
+
import path13 from "node:path";
|
|
42926
|
+
import os12 from "node:os";
|
|
42927
|
+
import fs13 from "node:fs/promises";
|
|
42928
|
+
var logger17, exportCommand;
|
|
42279
42929
|
var init_export = __esm({
|
|
42280
42930
|
"src/commands/export.ts"() {
|
|
42281
42931
|
"use strict";
|
|
@@ -42284,8 +42934,8 @@ var init_export = __esm({
|
|
|
42284
42934
|
init_http2();
|
|
42285
42935
|
init_session();
|
|
42286
42936
|
init_resolve();
|
|
42287
|
-
|
|
42288
|
-
exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
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) => {
|
|
42289
42939
|
try {
|
|
42290
42940
|
const session = await requireSession();
|
|
42291
42941
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42303,21 +42953,21 @@ var init_export = __esm({
|
|
|
42303
42953
|
opts.path
|
|
42304
42954
|
);
|
|
42305
42955
|
if (ws.found && ws.source === "cwd" && ws.workspacePath) {
|
|
42306
|
-
const configPath =
|
|
42956
|
+
const configPath = path13.join(ws.workspacePath, ".ana-config.json");
|
|
42307
42957
|
let hasConfig = false;
|
|
42308
42958
|
try {
|
|
42309
|
-
await
|
|
42959
|
+
await fs13.access(configPath);
|
|
42310
42960
|
hasConfig = true;
|
|
42311
42961
|
} catch {
|
|
42312
42962
|
}
|
|
42313
42963
|
if (!hasConfig) {
|
|
42314
42964
|
const slug = courseSlug(activeCourse.courseTitle);
|
|
42315
|
-
|
|
42316
|
-
await
|
|
42965
|
+
logger17.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
|
|
42966
|
+
await fs13.mkdir(ws.workspacePath, { recursive: true });
|
|
42317
42967
|
for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
|
|
42318
|
-
await
|
|
42968
|
+
await fs13.mkdir(path13.join(ws.workspacePath, dir), { recursive: true });
|
|
42319
42969
|
}
|
|
42320
|
-
await
|
|
42970
|
+
await fs13.writeFile(
|
|
42321
42971
|
configPath,
|
|
42322
42972
|
JSON.stringify(
|
|
42323
42973
|
{
|
|
@@ -42335,7 +42985,7 @@ var init_export = __esm({
|
|
|
42335
42985
|
"utf-8"
|
|
42336
42986
|
);
|
|
42337
42987
|
await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
|
|
42338
|
-
const isNamespaced = ws.workspacePath ===
|
|
42988
|
+
const isNamespaced = ws.workspacePath === path13.join(process.cwd(), ".tostudy");
|
|
42339
42989
|
process.stderr.write(
|
|
42340
42990
|
isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
|
|
42341
42991
|
` : `\u2728 Workspace inicializado nesta pasta.
|
|
@@ -42376,7 +43026,7 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
|
|
|
42376
43026
|
);
|
|
42377
43027
|
}
|
|
42378
43028
|
} catch (err) {
|
|
42379
|
-
|
|
43029
|
+
logger17.error("export failed", { error: err });
|
|
42380
43030
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42381
43031
|
`);
|
|
42382
43032
|
process.exit(1);
|
|
@@ -42388,17 +43038,17 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
|
|
|
42388
43038
|
// src/commands/open.ts
|
|
42389
43039
|
import { Command as Command18 } from "commander";
|
|
42390
43040
|
import { execFile as execFile3 } from "node:child_process";
|
|
42391
|
-
import
|
|
42392
|
-
import
|
|
42393
|
-
var
|
|
43041
|
+
import path14 from "node:path";
|
|
43042
|
+
import os13 from "node:os";
|
|
43043
|
+
var logger18, openCommand;
|
|
42394
43044
|
var init_open = __esm({
|
|
42395
43045
|
"src/commands/open.ts"() {
|
|
42396
43046
|
"use strict";
|
|
42397
43047
|
init_src();
|
|
42398
43048
|
init_session();
|
|
42399
43049
|
init_resolve();
|
|
42400
|
-
|
|
42401
|
-
openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
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) => {
|
|
42402
43052
|
try {
|
|
42403
43053
|
const activeCourse = await requireActiveCourse();
|
|
42404
43054
|
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
@@ -42417,7 +43067,7 @@ var init_open = __esm({
|
|
|
42417
43067
|
const editor = process.env["EDITOR"] ?? "code";
|
|
42418
43068
|
execFile3(editor, [ws.workspacePath], (err) => {
|
|
42419
43069
|
if (err) {
|
|
42420
|
-
|
|
43070
|
+
logger18.error("open failed", { editor, workspacePath: ws.workspacePath });
|
|
42421
43071
|
process.stderr.write(`\u274C Falha ao abrir: ${err.message}
|
|
42422
43072
|
`);
|
|
42423
43073
|
process.exit(1);
|
|
@@ -42426,7 +43076,7 @@ var init_open = __esm({
|
|
|
42426
43076
|
`);
|
|
42427
43077
|
});
|
|
42428
43078
|
} catch (err) {
|
|
42429
|
-
|
|
43079
|
+
logger18.error("open command failed", { error: err });
|
|
42430
43080
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42431
43081
|
`);
|
|
42432
43082
|
process.exit(1);
|
|
@@ -42453,24 +43103,24 @@ var init_types3 = __esm({
|
|
|
42453
43103
|
});
|
|
42454
43104
|
|
|
42455
43105
|
// ../../packages/tostudy-core/src/vault/write-vault.ts
|
|
42456
|
-
import
|
|
42457
|
-
import
|
|
43106
|
+
import fs14 from "node:fs/promises";
|
|
43107
|
+
import path15 from "node:path";
|
|
42458
43108
|
async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
|
|
42459
43109
|
for (const file2 of files) {
|
|
42460
|
-
const fullPath =
|
|
42461
|
-
await
|
|
42462
|
-
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");
|
|
42463
43113
|
}
|
|
42464
|
-
const vaultPath =
|
|
43114
|
+
const vaultPath = path15.join(outputPath, courseSlug2);
|
|
42465
43115
|
const marker = {
|
|
42466
43116
|
courseId,
|
|
42467
43117
|
courseSlug: courseSlug2,
|
|
42468
43118
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42469
43119
|
version: VAULT_MARKER_VERSION
|
|
42470
43120
|
};
|
|
42471
|
-
await
|
|
42472
|
-
await
|
|
42473
|
-
|
|
43121
|
+
await fs14.mkdir(vaultPath, { recursive: true });
|
|
43122
|
+
await fs14.writeFile(
|
|
43123
|
+
path15.join(vaultPath, VAULT_MARKER_FILENAME),
|
|
42474
43124
|
JSON.stringify(marker, null, 2),
|
|
42475
43125
|
"utf-8"
|
|
42476
43126
|
);
|
|
@@ -42495,10 +43145,10 @@ var init_vault = __esm({
|
|
|
42495
43145
|
|
|
42496
43146
|
// src/commands/vault.ts
|
|
42497
43147
|
import { Command as Command19 } from "commander";
|
|
42498
|
-
import
|
|
42499
|
-
import
|
|
42500
|
-
import
|
|
42501
|
-
var
|
|
43148
|
+
import path16 from "node:path";
|
|
43149
|
+
import os14 from "node:os";
|
|
43150
|
+
import fs15 from "node:fs/promises";
|
|
43151
|
+
var logger19, vaultCommand;
|
|
42502
43152
|
var init_vault2 = __esm({
|
|
42503
43153
|
"src/commands/vault.ts"() {
|
|
42504
43154
|
"use strict";
|
|
@@ -42508,9 +43158,9 @@ var init_vault2 = __esm({
|
|
|
42508
43158
|
init_http2();
|
|
42509
43159
|
init_session();
|
|
42510
43160
|
init_resolve();
|
|
42511
|
-
|
|
43161
|
+
logger19 = createLogger("cli:vault");
|
|
42512
43162
|
vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
|
|
42513
|
-
vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
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) => {
|
|
42514
43164
|
try {
|
|
42515
43165
|
const session = await requireSession();
|
|
42516
43166
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42555,7 +43205,7 @@ var init_vault2 = __esm({
|
|
|
42555
43205
|
activeCourse.courseId,
|
|
42556
43206
|
slug
|
|
42557
43207
|
);
|
|
42558
|
-
|
|
43208
|
+
logger19.info("Vault generated", {
|
|
42559
43209
|
courseId: activeCourse.courseId,
|
|
42560
43210
|
vaultPath: result.vaultPath,
|
|
42561
43211
|
filesWritten: result.filesWritten
|
|
@@ -42588,13 +43238,13 @@ Para visualizar:
|
|
|
42588
43238
|
);
|
|
42589
43239
|
}
|
|
42590
43240
|
} catch (err) {
|
|
42591
|
-
|
|
43241
|
+
logger19.error("vault init failed", { error: err });
|
|
42592
43242
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42593
43243
|
`);
|
|
42594
43244
|
process.exit(1);
|
|
42595
43245
|
}
|
|
42596
43246
|
});
|
|
42597
|
-
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) => {
|
|
42598
43248
|
try {
|
|
42599
43249
|
const session = await requireSession();
|
|
42600
43250
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42620,10 +43270,10 @@ Para visualizar:
|
|
|
42620
43270
|
process.exit(1);
|
|
42621
43271
|
}
|
|
42622
43272
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
42623
|
-
const deps = { data, logger:
|
|
43273
|
+
const deps = { data, logger: logger19 };
|
|
42624
43274
|
const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
42625
|
-
const markerPath =
|
|
42626
|
-
const markerRaw = await
|
|
43275
|
+
const markerPath = path16.join(vaultPath, ".ana-vault.json");
|
|
43276
|
+
const markerRaw = await fs15.readFile(markerPath, "utf-8");
|
|
42627
43277
|
const marker = JSON.parse(markerRaw);
|
|
42628
43278
|
marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42629
43279
|
marker.progress = {
|
|
@@ -42631,10 +43281,10 @@ Para visualizar:
|
|
|
42631
43281
|
currentModule: progress3.currentModule.title,
|
|
42632
43282
|
currentLesson: progress3.currentLesson.title
|
|
42633
43283
|
};
|
|
42634
|
-
await
|
|
42635
|
-
const courseIndexPath =
|
|
43284
|
+
await fs15.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
43285
|
+
const courseIndexPath = path16.join(vaultPath, slug, "index.md");
|
|
42636
43286
|
try {
|
|
42637
|
-
let indexContent = await
|
|
43287
|
+
let indexContent = await fs15.readFile(courseIndexPath, "utf-8");
|
|
42638
43288
|
indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
|
|
42639
43289
|
const titleEnd = indexContent.indexOf("\n");
|
|
42640
43290
|
if (titleEnd !== -1) {
|
|
@@ -42645,7 +43295,7 @@ Para visualizar:
|
|
|
42645
43295
|
`;
|
|
42646
43296
|
indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
|
|
42647
43297
|
}
|
|
42648
|
-
await
|
|
43298
|
+
await fs15.writeFile(courseIndexPath, indexContent, "utf-8");
|
|
42649
43299
|
} catch {
|
|
42650
43300
|
}
|
|
42651
43301
|
const syncedAt = marker.lastSyncedAt;
|
|
@@ -42674,7 +43324,7 @@ Para visualizar:
|
|
|
42674
43324
|
);
|
|
42675
43325
|
}
|
|
42676
43326
|
} catch (err) {
|
|
42677
|
-
|
|
43327
|
+
logger19.error("vault sync failed", { error: err });
|
|
42678
43328
|
process.stderr.write(`\u274C ${err instanceof Error ? err.message : String(err)}
|
|
42679
43329
|
`);
|
|
42680
43330
|
process.exit(1);
|
|
@@ -42752,83 +43402,204 @@ var init_profile = __esm({
|
|
|
42752
43402
|
|
|
42753
43403
|
// src/commands/sync.ts
|
|
42754
43404
|
import { Command as Command21 } from "commander";
|
|
42755
|
-
var
|
|
43405
|
+
var logger20, syncCommand;
|
|
42756
43406
|
var init_sync = __esm({
|
|
42757
43407
|
"src/commands/sync.ts"() {
|
|
42758
43408
|
"use strict";
|
|
42759
43409
|
init_src();
|
|
42760
|
-
init_courses();
|
|
42761
|
-
init_http2();
|
|
42762
43410
|
init_session();
|
|
42763
43411
|
init_formatter();
|
|
42764
|
-
|
|
42765
|
-
|
|
42766
|
-
|
|
43412
|
+
init_instruction_pipeline();
|
|
43413
|
+
init_pipeline_deps();
|
|
43414
|
+
logger20 = createLogger("cli:sync");
|
|
42767
43415
|
syncCommand = new Command21("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").action(async (opts) => {
|
|
42768
43416
|
try {
|
|
42769
|
-
|
|
43417
|
+
await requireSession();
|
|
42770
43418
|
const activeCourse = await requireActiveCourse();
|
|
42771
|
-
const
|
|
42772
|
-
|
|
42773
|
-
|
|
42774
|
-
|
|
42775
|
-
getProgress({ enrollmentId: activeCourse.enrollmentId }, deps).catch(() => null)
|
|
42776
|
-
]);
|
|
42777
|
-
const matchedCourse = courses3.find((c) => c.courseId === activeCourse.courseId);
|
|
42778
|
-
if (!matchedCourse) {
|
|
42779
|
-
error("Curso ativo n\xE3o encontrado. Rode `tostudy courses` para verificar.");
|
|
42780
|
-
}
|
|
42781
|
-
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42782
|
-
const onboarding = await getCourseOnboardingStatus(activeCourse);
|
|
42783
|
-
const slug = generateInstructionFiles(
|
|
42784
|
-
{
|
|
42785
|
-
courseTitle: matchedCourse.title,
|
|
42786
|
-
courseId: activeCourse.courseId,
|
|
42787
|
-
progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0,
|
|
42788
|
-
moduleCount: progressData?.currentModule?.totalModules ?? 0,
|
|
42789
|
-
lessonCount: progressData?.currentLesson?.totalLessons ?? 0,
|
|
42790
|
-
currentModuleTitle: progressData?.currentModule?.title,
|
|
42791
|
-
currentLessonTitle: progressData?.currentLesson?.title,
|
|
42792
|
-
courseDescription: matchedCourse.description ?? void 0,
|
|
42793
|
-
workspaceReady: onboarding.workspaceReady,
|
|
42794
|
-
workspacePath: onboarding.workspacePath ?? void 0
|
|
42795
|
-
},
|
|
42796
|
-
onboardingState?.learnerProfile
|
|
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() }
|
|
42797
43423
|
);
|
|
42798
43424
|
if (opts.json) {
|
|
42799
43425
|
output(
|
|
42800
43426
|
{
|
|
42801
43427
|
status: "ok",
|
|
42802
43428
|
courseId: activeCourse.courseId,
|
|
42803
|
-
|
|
42804
|
-
|
|
43429
|
+
courseTitle: activeCourse.courseTitle,
|
|
43430
|
+
wroteFiles: pipelineResult.wroteFiles,
|
|
43431
|
+
markerPath: pipelineResult.markerPath,
|
|
43432
|
+
usedT0: pipelineResult.usedT0,
|
|
43433
|
+
usedT1: pipelineResult.usedT1,
|
|
43434
|
+
usedT2: pipelineResult.usedT2
|
|
42805
43435
|
},
|
|
42806
43436
|
{ json: true }
|
|
42807
43437
|
);
|
|
42808
43438
|
} else {
|
|
42809
|
-
|
|
42810
|
-
|
|
42811
|
-
|
|
42812
|
-
|
|
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
|
+
);
|
|
42813
43449
|
}
|
|
42814
43450
|
} catch (err) {
|
|
42815
43451
|
const msg = err instanceof Error ? err.message : String(err);
|
|
42816
43452
|
if (msg.includes("process.exit")) return;
|
|
43453
|
+
logger20.warn("sync failed", { error: msg });
|
|
42817
43454
|
error(msg);
|
|
42818
43455
|
}
|
|
42819
43456
|
});
|
|
42820
43457
|
}
|
|
42821
43458
|
});
|
|
42822
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
|
+
|
|
42823
43594
|
// src/cli.ts
|
|
42824
43595
|
var cli_exports = {};
|
|
42825
43596
|
__export(cli_exports, {
|
|
42826
43597
|
CLI_VERSION: () => CLI_VERSION,
|
|
42827
43598
|
createProgram: () => createProgram
|
|
42828
43599
|
});
|
|
42829
|
-
import { Command as
|
|
43600
|
+
import { Command as Command25 } from "commander";
|
|
42830
43601
|
function createProgram() {
|
|
42831
|
-
const program2 = new
|
|
43602
|
+
const program2 = new Command25();
|
|
42832
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");
|
|
42833
43604
|
program2.addCommand(setupCommand);
|
|
42834
43605
|
program2.addCommand(doctorCommand);
|
|
@@ -42846,6 +43617,9 @@ function createProgram() {
|
|
|
42846
43617
|
program2.addCommand(validateCommand);
|
|
42847
43618
|
program2.addCommand(menuCommand);
|
|
42848
43619
|
program2.addCommand(profileCommand);
|
|
43620
|
+
program2.addCommand(briefCommand);
|
|
43621
|
+
program2.addCommand(briefCreateCommand);
|
|
43622
|
+
program2.addCommand(briefOpenCommand);
|
|
42849
43623
|
program2.addCommand(workspaceCommand);
|
|
42850
43624
|
program2.addCommand(syncCommand);
|
|
42851
43625
|
program2.addCommand(exportCommand);
|
|
@@ -42878,7 +43652,10 @@ var init_cli = __esm({
|
|
|
42878
43652
|
init_vault2();
|
|
42879
43653
|
init_profile();
|
|
42880
43654
|
init_sync();
|
|
42881
|
-
|
|
43655
|
+
init_brief();
|
|
43656
|
+
init_brief_create();
|
|
43657
|
+
init_brief_open();
|
|
43658
|
+
CLI_VERSION = true ? "0.9.0" : "0.7.1";
|
|
42882
43659
|
}
|
|
42883
43660
|
});
|
|
42884
43661
|
|