@tostudy-ai/cli 0.18.3 → 0.18.5
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/main.js +577 -242
- package/dist/main.js.map +4 -4
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1024,6 +1024,18 @@ var init_errors_pt_br = __esm({
|
|
|
1024
1024
|
setupCommandDescription: "Atalho antigo. A pasta de estudo \xE9 workspace setup; o MCP \xE9 setup mcp",
|
|
1025
1025
|
setupRetiredNotice: "\n \u26A0\uFE0F `tostudy setup` n\xE3o cria a pasta de estudo.",
|
|
1026
1026
|
setupRetiredNext: " Pasta de estudo: `tostudy workspace setup`. MCP: `tostudy setup mcp`. Para entrar: `tostudy login`, depois `tostudy courses` e `tostudy select <n>`.\n",
|
|
1027
|
+
enrollCommandDescription: "Matricula em um curso gratuito. Curso pago abre no navegador.",
|
|
1028
|
+
enrollInvalidId: "Informe o ID do curso (UUID). Exemplo: tostudy enroll 11111111-1111-4111-8111-111111111111",
|
|
1029
|
+
enrollDone: "Matriculado. Rode `tostudy courses` e depois `tostudy select <n>`.",
|
|
1030
|
+
enrollDoneWebOnly: "Matriculado. Este curso n\xE3o abre no terminal. Continue no navegador:",
|
|
1031
|
+
enrollPaid: "Este curso \xE9 pago. A matr\xEDcula fica no navegador e nada \xE9 debitado por aqui:",
|
|
1032
|
+
suggestCommandDescription: "Sugere at\xE9 3 cursos gratuitos para matricular no terminal.",
|
|
1033
|
+
suggestEmpty: "Nenhum curso gratuito dispon\xEDvel para o terminal.",
|
|
1034
|
+
suggestNoWorkspace: "Sem contexto nesta pasta. Usei s\xF3 o perfil.",
|
|
1035
|
+
suggestNoOverlap: "Sem coincid\xEAncia com o perfil.",
|
|
1036
|
+
suggestEmptyProfile: "Perfil vazio. Mostrando cursos gratuitos em aberto.",
|
|
1037
|
+
suggestReason: "Motivo:",
|
|
1038
|
+
suggestPaidHeader: "Pagos. A matr\xEDcula fica no navegador e nada \xE9 debitado por aqui:",
|
|
1027
1039
|
enrollmentNotEntitled: "\u{1F6AB} Sua matr\xEDcula neste curso n\xE3o est\xE1 mais ativa (reembolso, cancelamento ou preview expirado).\nPara voltar a estudar, adquira o curso novamente em https://tostudy.ai.\n",
|
|
1028
1040
|
creator: {
|
|
1029
1041
|
loginBrowserOnly: "`--creator` s\xF3 funciona no login pelo browser. Rode `tostudy login --creator` sem --code, --magic-link ou --manual.",
|
|
@@ -1121,6 +1133,18 @@ var init_errors_en_us = __esm({
|
|
|
1121
1133
|
setupCommandDescription: "Old shortcut. The study folder is workspace setup; MCP is setup mcp",
|
|
1122
1134
|
setupRetiredNotice: "\n \u26A0\uFE0F `tostudy setup` does not create the study folder.",
|
|
1123
1135
|
setupRetiredNext: " Study folder: `tostudy workspace setup`. MCP: `tostudy setup mcp`. To sign in: `tostudy login`, then `tostudy courses` and `tostudy select <n>`.\n",
|
|
1136
|
+
enrollCommandDescription: "Enroll in a free course. A priced course opens in the browser.",
|
|
1137
|
+
enrollInvalidId: "Pass the course ID (UUID). Example: tostudy enroll 11111111-1111-4111-8111-111111111111",
|
|
1138
|
+
enrollDone: "Enrolled. Run `tostudy courses`, then `tostudy select <n>`.",
|
|
1139
|
+
enrollDoneWebOnly: "Enrolled. This course does not open in the terminal. Continue in the browser:",
|
|
1140
|
+
enrollPaid: "This course is paid. Enrollment stays in the browser and nothing is charged here:",
|
|
1141
|
+
suggestCommandDescription: "Suggest up to 3 free courses to enroll in from the terminal.",
|
|
1142
|
+
suggestEmpty: "No free course is available for the terminal.",
|
|
1143
|
+
suggestNoWorkspace: "No context in this folder. Only the profile was used.",
|
|
1144
|
+
suggestNoOverlap: "No word in the profile matched.",
|
|
1145
|
+
suggestEmptyProfile: "Profile is empty. Showing open free courses.",
|
|
1146
|
+
suggestReason: "Why:",
|
|
1147
|
+
suggestPaidHeader: "Paid. Enrollment stays in the browser and nothing is charged here:",
|
|
1124
1148
|
enrollmentNotEntitled: "\u{1F6AB} Your enrollment in this course is no longer active (refund, cancellation, or expired preview).\nTo study again, purchase the course at https://tostudy.ai.\n",
|
|
1125
1149
|
creator: {
|
|
1126
1150
|
loginBrowserOnly: "`--creator` only works with the browser login. Run `tostudy login --creator` without --code, --magic-link or --manual.",
|
|
@@ -3140,7 +3164,7 @@ var init_guards = __esm({
|
|
|
3140
3164
|
});
|
|
3141
3165
|
|
|
3142
3166
|
// src/cli.ts
|
|
3143
|
-
import { Command as
|
|
3167
|
+
import { Command as Command39 } from "commander";
|
|
3144
3168
|
|
|
3145
3169
|
// src/commands/login.ts
|
|
3146
3170
|
init_dist();
|
|
@@ -3786,7 +3810,7 @@ init_workspace_state();
|
|
|
3786
3810
|
init_formatter();
|
|
3787
3811
|
|
|
3788
3812
|
// src/version.ts
|
|
3789
|
-
var CLI_VERSION = true ? "0.18.
|
|
3813
|
+
var CLI_VERSION = true ? "0.18.5" : "0.7.1";
|
|
3790
3814
|
|
|
3791
3815
|
// src/update-checker.ts
|
|
3792
3816
|
init_config_dir();
|
|
@@ -4282,9 +4306,338 @@ var coursesCommand = new Command5("courses").description("List your enrolled cou
|
|
|
4282
4306
|
}
|
|
4283
4307
|
});
|
|
4284
4308
|
|
|
4309
|
+
// src/commands/suggest.ts
|
|
4310
|
+
import { Command as Command6 } from "commander";
|
|
4311
|
+
init_guards();
|
|
4312
|
+
init_errors();
|
|
4313
|
+
|
|
4314
|
+
// src/learner-brief/api.ts
|
|
4315
|
+
async function fetchLearnerBrief(input2) {
|
|
4316
|
+
const response = await cliApiFetch(
|
|
4317
|
+
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
4318
|
+
input2.token
|
|
4319
|
+
);
|
|
4320
|
+
return response.brief;
|
|
4321
|
+
}
|
|
4322
|
+
async function upsertLearnerBrief(input2) {
|
|
4323
|
+
const response = await cliApiFetch(
|
|
4324
|
+
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
4325
|
+
input2.token,
|
|
4326
|
+
{
|
|
4327
|
+
method: "POST",
|
|
4328
|
+
body: JSON.stringify({ text: input2.text, source: input2.source })
|
|
4329
|
+
}
|
|
4330
|
+
);
|
|
4331
|
+
return response.brief;
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// src/commands/suggest.ts
|
|
4335
|
+
init_user_profile();
|
|
4336
|
+
init_formatter();
|
|
4337
|
+
|
|
4338
|
+
// src/commands/suggest-score.ts
|
|
4339
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
4340
|
+
"de",
|
|
4341
|
+
"da",
|
|
4342
|
+
"do",
|
|
4343
|
+
"das",
|
|
4344
|
+
"dos",
|
|
4345
|
+
"para",
|
|
4346
|
+
"com",
|
|
4347
|
+
"uma",
|
|
4348
|
+
"um",
|
|
4349
|
+
"the",
|
|
4350
|
+
"and",
|
|
4351
|
+
"for",
|
|
4352
|
+
"you",
|
|
4353
|
+
"seu",
|
|
4354
|
+
"sua",
|
|
4355
|
+
"curso",
|
|
4356
|
+
"course"
|
|
4357
|
+
]);
|
|
4358
|
+
var TOKEN_SPLIT = /[^\p{L}\p{N}]+/u;
|
|
4359
|
+
var MAX_RESULTS = 3;
|
|
4360
|
+
var MAX_REASON_TOKENS = 3;
|
|
4361
|
+
var LEVEL_BONUS = 2;
|
|
4362
|
+
function tokensOf(value, dropStops) {
|
|
4363
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4364
|
+
const tokens = [];
|
|
4365
|
+
for (const raw2 of value.toLowerCase().split(TOKEN_SPLIT)) {
|
|
4366
|
+
if (raw2.length < 3 || seen.has(raw2)) continue;
|
|
4367
|
+
if (dropStops && STOP_WORDS.has(raw2)) continue;
|
|
4368
|
+
seen.add(raw2);
|
|
4369
|
+
tokens.push(raw2);
|
|
4370
|
+
}
|
|
4371
|
+
return tokens;
|
|
4372
|
+
}
|
|
4373
|
+
function tokenSet(parts) {
|
|
4374
|
+
return new Set(tokensOf(parts.join(" "), false));
|
|
4375
|
+
}
|
|
4376
|
+
function haystack(course) {
|
|
4377
|
+
return tokenSet([
|
|
4378
|
+
course.title,
|
|
4379
|
+
course.tags.join(" "),
|
|
4380
|
+
course.description,
|
|
4381
|
+
course.level,
|
|
4382
|
+
course.category
|
|
4383
|
+
]);
|
|
4384
|
+
}
|
|
4385
|
+
function haystackWithoutLevel(course) {
|
|
4386
|
+
return tokenSet([course.title, course.tags.join(" "), course.description, course.category]);
|
|
4387
|
+
}
|
|
4388
|
+
function rankSuggestions(courses, query) {
|
|
4389
|
+
const text2 = query.text.trim();
|
|
4390
|
+
const level = query.level;
|
|
4391
|
+
if (text2 === "" && level === null) {
|
|
4392
|
+
return courses.slice(0, MAX_RESULTS).map((course) => ({
|
|
4393
|
+
course,
|
|
4394
|
+
reason: "empty-profile"
|
|
4395
|
+
}));
|
|
4396
|
+
}
|
|
4397
|
+
const textTokens = tokensOf(text2, true);
|
|
4398
|
+
const levelTokens = level === null ? [] : tokensOf(level, false);
|
|
4399
|
+
const scored = courses.map((course) => {
|
|
4400
|
+
const stack = haystack(course);
|
|
4401
|
+
const outsideLevel = haystackWithoutLevel(course);
|
|
4402
|
+
const hits = [];
|
|
4403
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4404
|
+
for (const token of textTokens) {
|
|
4405
|
+
if (seen.has(token) || !stack.has(token)) continue;
|
|
4406
|
+
seen.add(token);
|
|
4407
|
+
hits.push(token);
|
|
4408
|
+
}
|
|
4409
|
+
for (const token of levelTokens) {
|
|
4410
|
+
if (seen.has(token) || !outsideLevel.has(token)) continue;
|
|
4411
|
+
seen.add(token);
|
|
4412
|
+
hits.push(token);
|
|
4413
|
+
}
|
|
4414
|
+
const levelBonus = level !== null && course.level.toLowerCase() === level.trim().toLowerCase() ? LEVEL_BONUS : 0;
|
|
4415
|
+
return {
|
|
4416
|
+
course,
|
|
4417
|
+
score: hits.length + levelBonus,
|
|
4418
|
+
reason: hits.slice(0, MAX_REASON_TOKENS).join(",") || "no-overlap"
|
|
4419
|
+
};
|
|
4420
|
+
});
|
|
4421
|
+
scored.sort((left, right) => {
|
|
4422
|
+
if (right.score !== left.score) return right.score - left.score;
|
|
4423
|
+
return left.course.title.localeCompare(right.course.title);
|
|
4424
|
+
});
|
|
4425
|
+
return scored.slice(0, MAX_RESULTS).map(({ course, reason }) => ({ course, reason }));
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
// src/commands/suggest-workspace.ts
|
|
4429
|
+
import fs11 from "node:fs";
|
|
4430
|
+
import path13 from "node:path";
|
|
4431
|
+
var DOC_LINE_LIMIT = 40;
|
|
4432
|
+
var MIN_TOKEN_LENGTH = 3;
|
|
4433
|
+
var FENCE_LINE = /^[ \t]{0,3}```/;
|
|
4434
|
+
function readWorkspaceSignals(cwd) {
|
|
4435
|
+
const packagePath = path13.join(cwd, "package.json");
|
|
4436
|
+
const readmePath = path13.join(cwd, "README.md");
|
|
4437
|
+
const agentsPath = path13.join(cwd, "AGENTS.md");
|
|
4438
|
+
const hasPackage = fs11.existsSync(packagePath);
|
|
4439
|
+
const hasReadme = fs11.existsSync(readmePath);
|
|
4440
|
+
const hasAgents = fs11.existsSync(agentsPath);
|
|
4441
|
+
if (!hasPackage && !hasReadme && !hasAgents) {
|
|
4442
|
+
return { tokens: [], found: false };
|
|
4443
|
+
}
|
|
4444
|
+
const tokens = [];
|
|
4445
|
+
if (hasPackage) tokens.push(...readPackageTokens(packagePath));
|
|
4446
|
+
const docPath = hasReadme ? readmePath : hasAgents ? agentsPath : null;
|
|
4447
|
+
if (docPath) tokens.push(...readDocTokens(docPath));
|
|
4448
|
+
return { tokens: dedupe(tokens), found: true };
|
|
4449
|
+
}
|
|
4450
|
+
function readPackageTokens(filePath) {
|
|
4451
|
+
const raw2 = readUtf8(filePath);
|
|
4452
|
+
if (raw2 === null) return [];
|
|
4453
|
+
let parsed;
|
|
4454
|
+
try {
|
|
4455
|
+
parsed = JSON.parse(raw2);
|
|
4456
|
+
} catch {
|
|
4457
|
+
return [];
|
|
4458
|
+
}
|
|
4459
|
+
if (!isRecord(parsed)) return [];
|
|
4460
|
+
const names = [
|
|
4461
|
+
...dependencyNames(parsed.dependencies),
|
|
4462
|
+
...dependencyNames(parsed.devDependencies)
|
|
4463
|
+
];
|
|
4464
|
+
return tokenize(names.join("\n"));
|
|
4465
|
+
}
|
|
4466
|
+
function readDocTokens(filePath) {
|
|
4467
|
+
const raw2 = readUtf8(filePath);
|
|
4468
|
+
if (raw2 === null) return [];
|
|
4469
|
+
const lines = raw2.split(/\r?\n/).slice(0, DOC_LINE_LIMIT);
|
|
4470
|
+
return tokenize(stripFencedBlocks(lines));
|
|
4471
|
+
}
|
|
4472
|
+
function stripFencedBlocks(lines) {
|
|
4473
|
+
const kept = [];
|
|
4474
|
+
let inFence = false;
|
|
4475
|
+
for (const line of lines) {
|
|
4476
|
+
if (FENCE_LINE.test(line)) {
|
|
4477
|
+
inFence = !inFence;
|
|
4478
|
+
continue;
|
|
4479
|
+
}
|
|
4480
|
+
if (!inFence) kept.push(line);
|
|
4481
|
+
}
|
|
4482
|
+
return kept.join("\n");
|
|
4483
|
+
}
|
|
4484
|
+
function dependencyNames(value) {
|
|
4485
|
+
if (!isRecord(value)) return [];
|
|
4486
|
+
return Object.keys(value);
|
|
4487
|
+
}
|
|
4488
|
+
function isRecord(value) {
|
|
4489
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4490
|
+
}
|
|
4491
|
+
function tokenize(text2) {
|
|
4492
|
+
return text2.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= MIN_TOKEN_LENGTH);
|
|
4493
|
+
}
|
|
4494
|
+
function dedupe(tokens) {
|
|
4495
|
+
return [...new Set(tokens)];
|
|
4496
|
+
}
|
|
4497
|
+
function readUtf8(filePath) {
|
|
4498
|
+
try {
|
|
4499
|
+
return fs11.readFileSync(filePath, "utf8");
|
|
4500
|
+
} catch {
|
|
4501
|
+
return null;
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
// src/commands/suggest.ts
|
|
4506
|
+
var defaultSuggestDeps = {
|
|
4507
|
+
requireSession,
|
|
4508
|
+
fetchCandidates: async () => {
|
|
4509
|
+
const session = await requireSession();
|
|
4510
|
+
return cliApiFetch(`${session.apiUrl}/api/cli/courses/suggest`, session.token, {
|
|
4511
|
+
method: "GET"
|
|
4512
|
+
});
|
|
4513
|
+
},
|
|
4514
|
+
readBrief: async () => {
|
|
4515
|
+
const session = await requireSession();
|
|
4516
|
+
return fetchLearnerBrief({ apiUrl: session.apiUrl, token: session.token });
|
|
4517
|
+
},
|
|
4518
|
+
readProfile: () => getUserProfile(),
|
|
4519
|
+
readWorkspace: () => readWorkspaceSignals(process.cwd())
|
|
4520
|
+
};
|
|
4521
|
+
function suggestionQuery(brief, profile, workspaceTokens) {
|
|
4522
|
+
const parts = [];
|
|
4523
|
+
const briefText = brief?.text.trim() ?? "";
|
|
4524
|
+
if (briefText !== "") parts.push(briefText);
|
|
4525
|
+
if (profile) {
|
|
4526
|
+
parts.push(
|
|
4527
|
+
profile.goal,
|
|
4528
|
+
profile.segment,
|
|
4529
|
+
profile.productsOrServices,
|
|
4530
|
+
profile.team,
|
|
4531
|
+
profile.learnerLevel
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
4534
|
+
const workspaceText = workspaceTokens.join(" ");
|
|
4535
|
+
if (workspaceText !== "") parts.push(workspaceText);
|
|
4536
|
+
return {
|
|
4537
|
+
text: parts.map((part) => part.trim()).filter((part) => part !== "").join(" "),
|
|
4538
|
+
level: profile?.learnerLevel ?? null
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
function reasonText(reason, copy) {
|
|
4542
|
+
if (reason === "empty-profile") return copy.suggestEmptyProfile;
|
|
4543
|
+
if (reason === "no-overlap") return copy.suggestNoOverlap;
|
|
4544
|
+
return reason;
|
|
4545
|
+
}
|
|
4546
|
+
function formatBlock(ranked, lineFor) {
|
|
4547
|
+
const copy = getErrors();
|
|
4548
|
+
const lines = [];
|
|
4549
|
+
for (const [index, row] of ranked.entries()) {
|
|
4550
|
+
lines.push(`${index + 1}. ${row.course.title}`);
|
|
4551
|
+
lines.push(`${copy.suggestReason} ${reasonText(row.reason, copy)}`);
|
|
4552
|
+
lines.push(lineFor(row));
|
|
4553
|
+
}
|
|
4554
|
+
return lines;
|
|
4555
|
+
}
|
|
4556
|
+
function formatSuggestResult(free, paid, workspaceFound, enrollUrlFor) {
|
|
4557
|
+
const copy = getErrors();
|
|
4558
|
+
if (free.length === 0 && paid.length === 0) return copy.suggestEmpty;
|
|
4559
|
+
const lines = [];
|
|
4560
|
+
lines.push(...formatBlock(free, (row) => `tostudy enroll ${row.course.id}`));
|
|
4561
|
+
if (paid.length > 0) {
|
|
4562
|
+
if (lines.length > 0) lines.push("");
|
|
4563
|
+
lines.push(copy.suggestPaidHeader);
|
|
4564
|
+
lines.push(...formatBlock(paid, (row) => enrollUrlFor(row.course.id)));
|
|
4565
|
+
}
|
|
4566
|
+
if (!workspaceFound) lines.push(copy.suggestNoWorkspace);
|
|
4567
|
+
return lines.join("\n");
|
|
4568
|
+
}
|
|
4569
|
+
async function runSuggest(deps = defaultSuggestDeps) {
|
|
4570
|
+
const session = await deps.requireSession();
|
|
4571
|
+
const [candidates, brief, profile] = await Promise.all([
|
|
4572
|
+
deps.fetchCandidates(),
|
|
4573
|
+
deps.readBrief(),
|
|
4574
|
+
deps.readProfile()
|
|
4575
|
+
]);
|
|
4576
|
+
const workspace = deps.readWorkspace();
|
|
4577
|
+
const query = suggestionQuery(brief, profile, workspace.tokens);
|
|
4578
|
+
const origin = session.apiUrl.replace(/\/$/, "");
|
|
4579
|
+
return formatSuggestResult(
|
|
4580
|
+
rankSuggestions(candidates.free, query),
|
|
4581
|
+
rankSuggestions(candidates.paid, query),
|
|
4582
|
+
workspace.found,
|
|
4583
|
+
(courseId) => `${origin}/enroll/${courseId}`
|
|
4584
|
+
);
|
|
4585
|
+
}
|
|
4586
|
+
var suggestCommand = new Command6("suggest").description(getErrors().suggestCommandDescription).action(async () => {
|
|
4587
|
+
try {
|
|
4588
|
+
output(await runSuggest(), { json: false });
|
|
4589
|
+
} catch (err) {
|
|
4590
|
+
error(err instanceof Error ? err.message : String(err));
|
|
4591
|
+
}
|
|
4592
|
+
});
|
|
4593
|
+
|
|
4594
|
+
// src/commands/enroll.ts
|
|
4595
|
+
import { Command as Command7 } from "commander";
|
|
4596
|
+
init_guards();
|
|
4597
|
+
init_errors();
|
|
4598
|
+
init_formatter();
|
|
4599
|
+
var COURSE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4600
|
+
function formatEnrollResult(result) {
|
|
4601
|
+
const copy = getErrors();
|
|
4602
|
+
if (!result.enrolled) {
|
|
4603
|
+
return `${copy.enrollPaid}
|
|
4604
|
+
${result.enrollUrl}`;
|
|
4605
|
+
}
|
|
4606
|
+
if (!result.cliChannel) {
|
|
4607
|
+
return `${copy.enrollDoneWebOnly}
|
|
4608
|
+
${result.courseUrl}`;
|
|
4609
|
+
}
|
|
4610
|
+
return copy.enrollDone;
|
|
4611
|
+
}
|
|
4612
|
+
async function runEnroll(courseId, deps = {
|
|
4613
|
+
requireSession,
|
|
4614
|
+
request: ({ apiUrl, token, courseId: id }) => cliApiFetch(`${apiUrl}/api/cli/enrollments`, token, {
|
|
4615
|
+
method: "POST",
|
|
4616
|
+
body: JSON.stringify({ courseId: id })
|
|
4617
|
+
})
|
|
4618
|
+
}) {
|
|
4619
|
+
if (!COURSE_ID.test(courseId)) {
|
|
4620
|
+
throw new Error(getErrors().enrollInvalidId);
|
|
4621
|
+
}
|
|
4622
|
+
const session = await deps.requireSession();
|
|
4623
|
+
const result = await deps.request({
|
|
4624
|
+
apiUrl: session.apiUrl,
|
|
4625
|
+
token: session.token,
|
|
4626
|
+
courseId
|
|
4627
|
+
});
|
|
4628
|
+
return formatEnrollResult(result);
|
|
4629
|
+
}
|
|
4630
|
+
var enrollCommand = new Command7("enroll").description(getErrors().enrollCommandDescription).argument("<courseId>", "Published course ID (UUID)").action(async (courseId) => {
|
|
4631
|
+
try {
|
|
4632
|
+
output(await runEnroll(courseId), { json: false });
|
|
4633
|
+
} catch (err) {
|
|
4634
|
+
error(err instanceof Error ? err.message : String(err));
|
|
4635
|
+
}
|
|
4636
|
+
});
|
|
4637
|
+
|
|
4285
4638
|
// src/commands/select.ts
|
|
4286
4639
|
init_dist();
|
|
4287
|
-
import { Command as
|
|
4640
|
+
import { Command as Command8 } from "commander";
|
|
4288
4641
|
import os7 from "node:os";
|
|
4289
4642
|
init_guards();
|
|
4290
4643
|
init_workspace_marker();
|
|
@@ -4317,26 +4670,6 @@ init_dist();
|
|
|
4317
4670
|
init_workspace_marker();
|
|
4318
4671
|
init_workspace_state();
|
|
4319
4672
|
|
|
4320
|
-
// src/learner-brief/api.ts
|
|
4321
|
-
async function fetchLearnerBrief(input2) {
|
|
4322
|
-
const response = await cliApiFetch(
|
|
4323
|
-
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
4324
|
-
input2.token
|
|
4325
|
-
);
|
|
4326
|
-
return response.brief;
|
|
4327
|
-
}
|
|
4328
|
-
async function upsertLearnerBrief(input2) {
|
|
4329
|
-
const response = await cliApiFetch(
|
|
4330
|
-
`${input2.apiUrl}/api/cli/student/learner-brief`,
|
|
4331
|
-
input2.token,
|
|
4332
|
-
{
|
|
4333
|
-
method: "POST",
|
|
4334
|
-
body: JSON.stringify({ text: input2.text, source: input2.source })
|
|
4335
|
-
}
|
|
4336
|
-
);
|
|
4337
|
-
return response.brief;
|
|
4338
|
-
}
|
|
4339
|
-
|
|
4340
4673
|
// src/learner-brief/bootstrap.ts
|
|
4341
4674
|
import readline from "node:readline/promises";
|
|
4342
4675
|
import { stdin as processStdin, stdout as processStdout } from "node:process";
|
|
@@ -4728,7 +5061,7 @@ function isInteractiveTerminal(json2) {
|
|
|
4728
5061
|
|
|
4729
5062
|
// src/commands/select.ts
|
|
4730
5063
|
var logger8 = createLogger("cli:select");
|
|
4731
|
-
var selectCommand = new
|
|
5064
|
+
var selectCommand = new Command8("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").option(
|
|
4732
5065
|
"--root-agents",
|
|
4733
5066
|
"Add the ToStudy block to an existing AGENTS.md in this folder without asking"
|
|
4734
5067
|
).action(async (course, opts) => {
|
|
@@ -4856,13 +5189,13 @@ var selectCommand = new Command6("select").description("Activate a course by ID
|
|
|
4856
5189
|
|
|
4857
5190
|
// src/commands/progress.ts
|
|
4858
5191
|
init_dist();
|
|
4859
|
-
import { Command as
|
|
5192
|
+
import { Command as Command9 } from "commander";
|
|
4860
5193
|
init_guards();
|
|
4861
5194
|
init_course_state();
|
|
4862
5195
|
init_formatter();
|
|
4863
5196
|
init_errors();
|
|
4864
5197
|
var logger9 = createLogger("cli:progress");
|
|
4865
|
-
var progressCommand = new
|
|
5198
|
+
var progressCommand = new Command9("progress").description("Show your progress in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
4866
5199
|
try {
|
|
4867
5200
|
const session = await requireSession();
|
|
4868
5201
|
const activeCourse = await requireActiveCourse();
|
|
@@ -4888,7 +5221,7 @@ var progressCommand = new Command7("progress").description("Show your progress i
|
|
|
4888
5221
|
|
|
4889
5222
|
// src/commands/start.ts
|
|
4890
5223
|
init_dist();
|
|
4891
|
-
import { Command as
|
|
5224
|
+
import { Command as Command10 } from "commander";
|
|
4892
5225
|
|
|
4893
5226
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/lessons/next-lesson.ts
|
|
4894
5227
|
async function nextLesson(input2, deps) {
|
|
@@ -4997,20 +5330,20 @@ async function runStart(opts, deps = defaultDeps3) {
|
|
|
4997
5330
|
deps.error(msg);
|
|
4998
5331
|
}
|
|
4999
5332
|
}
|
|
5000
|
-
var startCommand = new
|
|
5333
|
+
var startCommand = new Command10("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
5001
5334
|
await runStart(opts);
|
|
5002
5335
|
});
|
|
5003
5336
|
|
|
5004
5337
|
// src/commands/start-next.ts
|
|
5005
5338
|
init_dist();
|
|
5006
|
-
import { Command as
|
|
5339
|
+
import { Command as Command11 } from "commander";
|
|
5007
5340
|
init_guards();
|
|
5008
5341
|
init_course_state();
|
|
5009
5342
|
init_workspace_state();
|
|
5010
5343
|
init_formatter();
|
|
5011
5344
|
init_errors();
|
|
5012
5345
|
var logger11 = createLogger("cli:start-next");
|
|
5013
|
-
var startNextCommand = new
|
|
5346
|
+
var startNextCommand = new Command11("start-next").description("Transition to the next module after completing the current one").option("--json", "Output structured JSON").action(async (opts) => {
|
|
5014
5347
|
try {
|
|
5015
5348
|
const session = await requireSession();
|
|
5016
5349
|
const activeCourse = await requireActiveCourse();
|
|
@@ -5053,14 +5386,14 @@ var startNextCommand = new Command9("start-next").description("Transition to the
|
|
|
5053
5386
|
|
|
5054
5387
|
// src/commands/next.ts
|
|
5055
5388
|
init_dist();
|
|
5056
|
-
import { Command as
|
|
5389
|
+
import { Command as Command12 } from "commander";
|
|
5057
5390
|
init_guards();
|
|
5058
5391
|
init_course_state();
|
|
5059
5392
|
init_workspace_state();
|
|
5060
5393
|
init_formatter();
|
|
5061
5394
|
init_errors();
|
|
5062
5395
|
var logger12 = createLogger("cli:next");
|
|
5063
|
-
var nextCommand = new
|
|
5396
|
+
var nextCommand = new Command12("next").description("Advance to the next lesson in the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
5064
5397
|
try {
|
|
5065
5398
|
const session = await requireSession();
|
|
5066
5399
|
const activeCourse = await requireActiveCourse();
|
|
@@ -5138,14 +5471,14 @@ var nextCommand = new Command10("next").description("Advance to the next lesson
|
|
|
5138
5471
|
|
|
5139
5472
|
// src/commands/lesson.ts
|
|
5140
5473
|
init_dist();
|
|
5141
|
-
import { Command as
|
|
5474
|
+
import { Command as Command13 } from "commander";
|
|
5142
5475
|
init_guards();
|
|
5143
5476
|
init_course_state();
|
|
5144
5477
|
init_formatter();
|
|
5145
5478
|
init_resolve();
|
|
5146
5479
|
init_workspace_tokens();
|
|
5147
5480
|
init_errors();
|
|
5148
|
-
import
|
|
5481
|
+
import path14 from "node:path";
|
|
5149
5482
|
var logger13 = createLogger("cli:lesson");
|
|
5150
5483
|
function adjustTimeEstimate(type, baseMinutes) {
|
|
5151
5484
|
if (type === "exercise") return Math.max(baseMinutes, 30);
|
|
@@ -5162,7 +5495,7 @@ function formatLessonContent(data, workspacePath, courseTitle) {
|
|
|
5162
5495
|
const isPractical = isPracticalType(data.type) || isCheckpoint(data.type, data.title);
|
|
5163
5496
|
const lines = [`\u2501\u2501\u2501 Li\xE7\xE3o: ${data.title} \u2501\u2501\u2501`];
|
|
5164
5497
|
if (isPractical && workspacePath) {
|
|
5165
|
-
const base =
|
|
5498
|
+
const base = path14.basename(workspacePath) === ".tostudy" ? path14.dirname(workspacePath) : workspacePath;
|
|
5166
5499
|
const slug = courseSlug(courseTitle);
|
|
5167
5500
|
const vaultPath = resolveVaultPath(workspacePath, slug);
|
|
5168
5501
|
lines.push(
|
|
@@ -5196,7 +5529,7 @@ function formatLessonContent(data, workspacePath, courseTitle) {
|
|
|
5196
5529
|
}
|
|
5197
5530
|
return lines.join("\n");
|
|
5198
5531
|
}
|
|
5199
|
-
var lessonCommand = new
|
|
5532
|
+
var lessonCommand = new Command13("lesson").description("Show the content of the current lesson").option("--json", "Output structured JSON").option(
|
|
5200
5533
|
"--read <lessonId>",
|
|
5201
5534
|
"Read a specific lesson by id (works on archived / read-only courses)"
|
|
5202
5535
|
).action(async (opts) => {
|
|
@@ -5267,7 +5600,7 @@ var lessonCommand = new Command11("lesson").description("Show the content of the
|
|
|
5267
5600
|
|
|
5268
5601
|
// src/commands/knowledge.ts
|
|
5269
5602
|
init_dist();
|
|
5270
|
-
import { Command as
|
|
5603
|
+
import { Command as Command14 } from "commander";
|
|
5271
5604
|
init_guards();
|
|
5272
5605
|
init_formatter();
|
|
5273
5606
|
var logger14 = createLogger("cli:knowledge");
|
|
@@ -5287,7 +5620,7 @@ function formatKnowledge(data) {
|
|
|
5287
5620
|
});
|
|
5288
5621
|
return lines.join("\n").trimEnd();
|
|
5289
5622
|
}
|
|
5290
|
-
var knowledgeCommand = new
|
|
5623
|
+
var knowledgeCommand = new Command14("knowledge").description("Show the active course's knowledge base (base de conhecimento)").option("--json", "Output structured JSON").action(async (opts) => {
|
|
5291
5624
|
try {
|
|
5292
5625
|
const activeCourse = await requireActiveCourse();
|
|
5293
5626
|
if (!activeCourse.courseId) {
|
|
@@ -5318,13 +5651,13 @@ var knowledgeCommand = new Command12("knowledge").description("Show the active c
|
|
|
5318
5651
|
|
|
5319
5652
|
// src/commands/hint.ts
|
|
5320
5653
|
init_dist();
|
|
5321
|
-
import { Command as
|
|
5654
|
+
import { Command as Command15 } from "commander";
|
|
5322
5655
|
init_guards();
|
|
5323
5656
|
init_course_state();
|
|
5324
5657
|
init_formatter();
|
|
5325
5658
|
init_errors();
|
|
5326
5659
|
var logger15 = createLogger("cli:hint");
|
|
5327
|
-
var hintCommand = new
|
|
5660
|
+
var hintCommand = new Command15("hint").description("Get a progressive hint for the current exercise").option("--json", "Output structured JSON").action(async (opts) => {
|
|
5328
5661
|
try {
|
|
5329
5662
|
const session = await requireSession();
|
|
5330
5663
|
const activeCourse = await requireActiveCourse();
|
|
@@ -5373,9 +5706,9 @@ var hintCommand = new Command13("hint").description("Get a progressive hint for
|
|
|
5373
5706
|
|
|
5374
5707
|
// src/commands/validate.ts
|
|
5375
5708
|
init_dist();
|
|
5376
|
-
import
|
|
5377
|
-
import
|
|
5378
|
-
import { Command as
|
|
5709
|
+
import fs12 from "node:fs";
|
|
5710
|
+
import path15 from "node:path";
|
|
5711
|
+
import { Command as Command16 } from "commander";
|
|
5379
5712
|
|
|
5380
5713
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/exercises/validate-solution.ts
|
|
5381
5714
|
async function validateSolution(input2, deps) {
|
|
@@ -6164,10 +6497,10 @@ function mergeDefs(...defs) {
|
|
|
6164
6497
|
function cloneDef(schema) {
|
|
6165
6498
|
return mergeDefs(schema._zod.def);
|
|
6166
6499
|
}
|
|
6167
|
-
function getElementAtPath(obj,
|
|
6168
|
-
if (!
|
|
6500
|
+
function getElementAtPath(obj, path29) {
|
|
6501
|
+
if (!path29)
|
|
6169
6502
|
return obj;
|
|
6170
|
-
return
|
|
6503
|
+
return path29.reduce((acc, key) => acc?.[key], obj);
|
|
6171
6504
|
}
|
|
6172
6505
|
function promiseAllObject(promisesObj) {
|
|
6173
6506
|
const keys = Object.keys(promisesObj);
|
|
@@ -6550,11 +6883,11 @@ function aborted(x, startIndex = 0) {
|
|
|
6550
6883
|
}
|
|
6551
6884
|
return false;
|
|
6552
6885
|
}
|
|
6553
|
-
function prefixIssues(
|
|
6886
|
+
function prefixIssues(path29, issues) {
|
|
6554
6887
|
return issues.map((iss) => {
|
|
6555
6888
|
var _a2;
|
|
6556
6889
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
6557
|
-
iss.path.unshift(
|
|
6890
|
+
iss.path.unshift(path29);
|
|
6558
6891
|
return iss;
|
|
6559
6892
|
});
|
|
6560
6893
|
}
|
|
@@ -6737,7 +7070,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
|
|
|
6737
7070
|
}
|
|
6738
7071
|
function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
6739
7072
|
const result = { errors: [] };
|
|
6740
|
-
const processError = (error50,
|
|
7073
|
+
const processError = (error50, path29 = []) => {
|
|
6741
7074
|
var _a2, _b;
|
|
6742
7075
|
for (const issue2 of error50.issues) {
|
|
6743
7076
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -6747,7 +7080,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
6747
7080
|
} else if (issue2.code === "invalid_element") {
|
|
6748
7081
|
processError({ issues: issue2.issues }, issue2.path);
|
|
6749
7082
|
} else {
|
|
6750
|
-
const fullpath = [...
|
|
7083
|
+
const fullpath = [...path29, ...issue2.path];
|
|
6751
7084
|
if (fullpath.length === 0) {
|
|
6752
7085
|
result.errors.push(mapper(issue2));
|
|
6753
7086
|
continue;
|
|
@@ -6779,8 +7112,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
6779
7112
|
}
|
|
6780
7113
|
function toDotPath(_path) {
|
|
6781
7114
|
const segs = [];
|
|
6782
|
-
const
|
|
6783
|
-
for (const seg of
|
|
7115
|
+
const path29 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
7116
|
+
for (const seg of path29) {
|
|
6784
7117
|
if (typeof seg === "number")
|
|
6785
7118
|
segs.push(`[${seg}]`);
|
|
6786
7119
|
else if (typeof seg === "symbol")
|
|
@@ -18757,13 +19090,13 @@ function resolveRef(ref, ctx) {
|
|
|
18757
19090
|
if (!ref.startsWith("#")) {
|
|
18758
19091
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
18759
19092
|
}
|
|
18760
|
-
const
|
|
18761
|
-
if (
|
|
19093
|
+
const path29 = ref.slice(1).split("/").filter(Boolean);
|
|
19094
|
+
if (path29.length === 0) {
|
|
18762
19095
|
return ctx.rootSchema;
|
|
18763
19096
|
}
|
|
18764
19097
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
18765
|
-
if (
|
|
18766
|
-
const key =
|
|
19098
|
+
if (path29[0] === defsKey) {
|
|
19099
|
+
const key = path29[1];
|
|
18767
19100
|
if (!key || !ctx.defs[key]) {
|
|
18768
19101
|
throw new Error(`Reference not found: ${ref}`);
|
|
18769
19102
|
}
|
|
@@ -19561,7 +19894,7 @@ async function clearRetryPin() {
|
|
|
19561
19894
|
await updateWorkspaceState(ws.workspacePath, { retryLessonId: void 0 });
|
|
19562
19895
|
}
|
|
19563
19896
|
}
|
|
19564
|
-
var validateCommand = new
|
|
19897
|
+
var validateCommand = new Command16("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) => {
|
|
19565
19898
|
try {
|
|
19566
19899
|
const session = await requireSession();
|
|
19567
19900
|
const activeCourse = await requireActiveCourse();
|
|
@@ -19590,21 +19923,21 @@ var validateCommand = new Command14("validate").description("Validate your solut
|
|
|
19590
19923
|
}
|
|
19591
19924
|
let solution;
|
|
19592
19925
|
if (opts.stdin) {
|
|
19593
|
-
solution =
|
|
19926
|
+
solution = fs12.readFileSync("/dev/stdin", "utf-8");
|
|
19594
19927
|
} else if (file2) {
|
|
19595
|
-
if (!
|
|
19928
|
+
if (!fs12.existsSync(file2)) {
|
|
19596
19929
|
if (opts.json)
|
|
19597
19930
|
jsonError("file_not_found", { message: `Arquivo n\xE3o encontrado: ${file2}` });
|
|
19598
19931
|
error(`Arquivo n\xE3o encontrado: ${file2}`);
|
|
19599
19932
|
}
|
|
19600
|
-
solution =
|
|
19933
|
+
solution = fs12.readFileSync(file2, "utf-8");
|
|
19601
19934
|
} else {
|
|
19602
19935
|
if (opts.json)
|
|
19603
19936
|
jsonError("no_solution_provided", { message: "Forne\xE7a um arquivo ou use --stdin" });
|
|
19604
19937
|
error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
|
|
19605
19938
|
}
|
|
19606
19939
|
if (file2 && activeCourse.courseTags?.length) {
|
|
19607
|
-
const ext =
|
|
19940
|
+
const ext = path15.extname(file2).toLowerCase();
|
|
19608
19941
|
const LANG_EXTENSIONS = {
|
|
19609
19942
|
".html": ["html", "html5"],
|
|
19610
19943
|
".css": ["css"],
|
|
@@ -19699,13 +20032,13 @@ var validateCommand = new Command14("validate").description("Validate your solut
|
|
|
19699
20032
|
|
|
19700
20033
|
// src/commands/retry.ts
|
|
19701
20034
|
init_dist();
|
|
19702
|
-
import { Command as
|
|
20035
|
+
import { Command as Command17 } from "commander";
|
|
19703
20036
|
init_guards();
|
|
19704
20037
|
init_workspace_state();
|
|
19705
20038
|
init_formatter();
|
|
19706
20039
|
var logger17 = createLogger("cli:retry");
|
|
19707
20040
|
var LESSON_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19708
|
-
var retryCommand = new
|
|
20041
|
+
var retryCommand = new Command17("retry").description("Revalidate a lesson you already completed (your grade keeps the best attempt)").argument("[lessonId]", "Id of the completed lesson (see: tostudy grades --course <slug> --json)").option("--clear", "Cancel the pending retry; validate goes back to the current lesson").option("--json", "Output structured JSON").action(async (lessonId, opts) => {
|
|
19709
20042
|
try {
|
|
19710
20043
|
if (opts.clear) {
|
|
19711
20044
|
const ws2 = await findWorkspaceState();
|
|
@@ -19766,8 +20099,8 @@ var retryCommand = new Command15("retry").description("Revalidate a lesson you a
|
|
|
19766
20099
|
init_session_store();
|
|
19767
20100
|
init_workspace_state();
|
|
19768
20101
|
init_formatter();
|
|
19769
|
-
import { Command as
|
|
19770
|
-
var menuCommand = new
|
|
20102
|
+
import { Command as Command18 } from "commander";
|
|
20103
|
+
var menuCommand = new Command18("menu").description("Show available commands and current study context").action(async () => {
|
|
19771
20104
|
const session = await getSession();
|
|
19772
20105
|
const wsResult = session ? await findWorkspaceState() : null;
|
|
19773
20106
|
const activeCourse = wsResult?.state ?? null;
|
|
@@ -19824,7 +20157,7 @@ var menuCommand = new Command16("menu").description("Show available commands and
|
|
|
19824
20157
|
|
|
19825
20158
|
// src/commands/init.ts
|
|
19826
20159
|
init_dist();
|
|
19827
|
-
import { Command as
|
|
20160
|
+
import { Command as Command19 } from "commander";
|
|
19828
20161
|
init_session_store();
|
|
19829
20162
|
init_course_state();
|
|
19830
20163
|
init_workspace_state();
|
|
@@ -20140,7 +20473,7 @@ Rode \`tostudy select <n\xFAmero>\` para ativar um curso.`,
|
|
|
20140
20473
|
deps.output(artifacts.learnerBrief, { json: false });
|
|
20141
20474
|
}
|
|
20142
20475
|
}
|
|
20143
|
-
var initCommand = new
|
|
20476
|
+
var initCommand = new Command19("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(
|
|
20144
20477
|
"--root-agents",
|
|
20145
20478
|
"Add the ToStudy block to an existing AGENTS.md in this folder without asking"
|
|
20146
20479
|
).option("--json", "Output structured JSON").action(async (opts) => {
|
|
@@ -20149,18 +20482,18 @@ var initCommand = new Command17("init").description("Generate tutor instructions
|
|
|
20149
20482
|
|
|
20150
20483
|
// src/commands/workspace.ts
|
|
20151
20484
|
init_dist();
|
|
20152
|
-
import { Command as
|
|
20485
|
+
import { Command as Command20 } from "commander";
|
|
20153
20486
|
|
|
20154
20487
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/workspace/setup-workspace.ts
|
|
20155
|
-
import
|
|
20156
|
-
import
|
|
20488
|
+
import fs13 from "node:fs/promises";
|
|
20489
|
+
import path16 from "node:path";
|
|
20157
20490
|
var WORKSPACE_DIRS = ["exercises", "generated", "notes", "diagrams", "vault"];
|
|
20158
20491
|
async function setupWorkspace(input2) {
|
|
20159
|
-
const workspacePath =
|
|
20492
|
+
const workspacePath = path16.join(input2.basePath, input2.courseSlug);
|
|
20160
20493
|
for (const dir of WORKSPACE_DIRS) {
|
|
20161
|
-
await
|
|
20494
|
+
await fs13.mkdir(path16.join(workspacePath, dir), { recursive: true });
|
|
20162
20495
|
}
|
|
20163
|
-
const configPath =
|
|
20496
|
+
const configPath = path16.join(workspacePath, ".ana-config.json");
|
|
20164
20497
|
const config2 = {
|
|
20165
20498
|
courseId: input2.courseId,
|
|
20166
20499
|
courseSlug: input2.courseSlug,
|
|
@@ -20170,7 +20503,7 @@ async function setupWorkspace(input2) {
|
|
|
20170
20503
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20171
20504
|
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20172
20505
|
};
|
|
20173
|
-
await
|
|
20506
|
+
await fs13.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
20174
20507
|
const readme = [
|
|
20175
20508
|
`# ${input2.courseName}`,
|
|
20176
20509
|
"",
|
|
@@ -20195,13 +20528,13 @@ async function setupWorkspace(input2) {
|
|
|
20195
20528
|
"tostudy vault sync # Sincronizar progresso",
|
|
20196
20529
|
"```"
|
|
20197
20530
|
].join("\n");
|
|
20198
|
-
await
|
|
20531
|
+
await fs13.writeFile(path16.join(workspacePath, "README.md"), readme, "utf-8");
|
|
20199
20532
|
return { workspacePath, directories: WORKSPACE_DIRS, configPath };
|
|
20200
20533
|
}
|
|
20201
20534
|
|
|
20202
20535
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/workspace/extract-exercise.ts
|
|
20203
|
-
import
|
|
20204
|
-
import
|
|
20536
|
+
import fs14 from "node:fs/promises";
|
|
20537
|
+
import path17 from "node:path";
|
|
20205
20538
|
|
|
20206
20539
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/workspace/templates.ts
|
|
20207
20540
|
var VITE_VERSION = "^6.0.0";
|
|
@@ -20313,16 +20646,16 @@ async function extractExercise(input2) {
|
|
|
20313
20646
|
const lessonData = resolveWorkspaceTokens(input2.lessonData, paths);
|
|
20314
20647
|
const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
|
|
20315
20648
|
const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
|
|
20316
|
-
const exercisePath =
|
|
20317
|
-
await
|
|
20649
|
+
const exercisePath = path17.join(workspacePath, "exercises", moduleDir, lessonDir);
|
|
20650
|
+
await fs14.mkdir(exercisePath, { recursive: true });
|
|
20318
20651
|
const extractedFiles = [];
|
|
20319
20652
|
let hasStarterCode = false;
|
|
20320
20653
|
if (lessonData.sandpackConfig?.files) {
|
|
20321
20654
|
for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
|
|
20322
20655
|
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
20323
|
-
const fullPath =
|
|
20324
|
-
await
|
|
20325
|
-
await
|
|
20656
|
+
const fullPath = path17.join(exercisePath, cleanPath);
|
|
20657
|
+
await fs14.mkdir(path17.dirname(fullPath), { recursive: true });
|
|
20658
|
+
await fs14.writeFile(fullPath, fileData.code, "utf-8");
|
|
20326
20659
|
extractedFiles.push(cleanPath);
|
|
20327
20660
|
hasStarterCode = true;
|
|
20328
20661
|
}
|
|
@@ -20330,13 +20663,13 @@ async function extractExercise(input2) {
|
|
|
20330
20663
|
const tierData = getTierData(lessonData.structuredData, exerciseTier2);
|
|
20331
20664
|
const tierCode = tierData?.code;
|
|
20332
20665
|
if (tierCode) {
|
|
20333
|
-
await
|
|
20666
|
+
await fs14.writeFile(path17.join(exercisePath, "exercise.js"), tierCode, "utf-8");
|
|
20334
20667
|
extractedFiles.push("exercise.js");
|
|
20335
20668
|
hasStarterCode = true;
|
|
20336
20669
|
} else {
|
|
20337
20670
|
const starter = getStarterCode(lessonData.structuredData);
|
|
20338
20671
|
if (starter) {
|
|
20339
|
-
await
|
|
20672
|
+
await fs14.writeFile(path17.join(exercisePath, "exercise.js"), starter, "utf-8");
|
|
20340
20673
|
extractedFiles.push("exercise.js");
|
|
20341
20674
|
hasStarterCode = true;
|
|
20342
20675
|
}
|
|
@@ -20354,8 +20687,8 @@ async function extractExercise(input2) {
|
|
|
20354
20687
|
...exerciseDeps
|
|
20355
20688
|
}
|
|
20356
20689
|
};
|
|
20357
|
-
await
|
|
20358
|
-
|
|
20690
|
+
await fs14.writeFile(
|
|
20691
|
+
path17.join(exercisePath, "package.json"),
|
|
20359
20692
|
JSON.stringify(pkgJson, null, 2),
|
|
20360
20693
|
"utf-8"
|
|
20361
20694
|
);
|
|
@@ -20367,20 +20700,20 @@ async function extractExercise(input2) {
|
|
|
20367
20700
|
);
|
|
20368
20701
|
for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
|
|
20369
20702
|
if (!sandpackFileNames.has(configFile)) {
|
|
20370
|
-
await
|
|
20703
|
+
await fs14.writeFile(path17.join(exercisePath, configFile), configContent, "utf-8");
|
|
20371
20704
|
extractedFiles.push(configFile);
|
|
20372
20705
|
}
|
|
20373
20706
|
}
|
|
20374
20707
|
const setupSh = `#!/bin/sh
|
|
20375
20708
|
${scaffold.setupScript}
|
|
20376
20709
|
`;
|
|
20377
|
-
await
|
|
20710
|
+
await fs14.writeFile(path17.join(exercisePath, "setup.sh"), setupSh, "utf-8");
|
|
20378
20711
|
extractedFiles.push("setup.sh");
|
|
20379
20712
|
}
|
|
20380
20713
|
}
|
|
20381
20714
|
const readme = generateReadme(lessonData, exerciseTier2);
|
|
20382
|
-
const readmePath =
|
|
20383
|
-
await
|
|
20715
|
+
const readmePath = path17.join(exercisePath, "README.md");
|
|
20716
|
+
await fs14.writeFile(readmePath, readme, "utf-8");
|
|
20384
20717
|
extractedFiles.push("README.md");
|
|
20385
20718
|
return {
|
|
20386
20719
|
exercisePath,
|
|
@@ -20436,11 +20769,11 @@ init_guards();
|
|
|
20436
20769
|
init_course_state();
|
|
20437
20770
|
init_resolve();
|
|
20438
20771
|
init_errors();
|
|
20439
|
-
import
|
|
20772
|
+
import path18 from "node:path";
|
|
20440
20773
|
import os8 from "node:os";
|
|
20441
|
-
import
|
|
20774
|
+
import fs15 from "node:fs/promises";
|
|
20442
20775
|
var logger19 = createLogger("cli:workspace");
|
|
20443
|
-
var workspaceCommand = new
|
|
20776
|
+
var workspaceCommand = new Command20("workspace").description(
|
|
20444
20777
|
getErrors().workspaceCommandDescription
|
|
20445
20778
|
);
|
|
20446
20779
|
workspaceCommand.command("setup").description(getErrors().workspaceSetupDescription).option("--path <dir>", "Diret\xF3rio base do workspace (omita para usar a pasta atual)").option("--json", "Output structured JSON").action(async (opts) => {
|
|
@@ -20452,14 +20785,14 @@ workspaceCommand.command("setup").description(getErrors().workspaceSetupDescript
|
|
|
20452
20785
|
let directories;
|
|
20453
20786
|
if (!opts.path && cwdIsWorkspace) {
|
|
20454
20787
|
const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
|
|
20455
|
-
workspacePath = resolvedCwd ??
|
|
20456
|
-
await
|
|
20788
|
+
workspacePath = resolvedCwd ?? path18.join(process.cwd(), ".tostudy");
|
|
20789
|
+
await fs15.mkdir(workspacePath, { recursive: true });
|
|
20457
20790
|
directories = ["exercises", "generated", "notes", "diagrams"];
|
|
20458
20791
|
for (const dir of directories) {
|
|
20459
|
-
await
|
|
20792
|
+
await fs15.mkdir(path18.join(workspacePath, dir), { recursive: true });
|
|
20460
20793
|
}
|
|
20461
|
-
const configPath =
|
|
20462
|
-
await
|
|
20794
|
+
const configPath = path18.join(workspacePath, ".ana-config.json");
|
|
20795
|
+
await fs15.writeFile(
|
|
20463
20796
|
configPath,
|
|
20464
20797
|
JSON.stringify(
|
|
20465
20798
|
{
|
|
@@ -20477,7 +20810,7 @@ workspaceCommand.command("setup").description(getErrors().workspaceSetupDescript
|
|
|
20477
20810
|
"utf-8"
|
|
20478
20811
|
);
|
|
20479
20812
|
} else {
|
|
20480
|
-
const basePath = opts.path ??
|
|
20813
|
+
const basePath = opts.path ?? path18.join(os8.homedir(), "study");
|
|
20481
20814
|
const result2 = await setupWorkspace({
|
|
20482
20815
|
courseId: activeCourse.courseId,
|
|
20483
20816
|
courseSlug: courseSlug(activeCourse.courseTitle),
|
|
@@ -20511,7 +20844,7 @@ Pr\xF3ximo passo: tostudy export
|
|
|
20511
20844
|
process.exit(1);
|
|
20512
20845
|
}
|
|
20513
20846
|
});
|
|
20514
|
-
workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
20847
|
+
workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path18.join(os8.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
20515
20848
|
try {
|
|
20516
20849
|
const activeCourse = await requireActiveCourse();
|
|
20517
20850
|
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
@@ -20528,40 +20861,40 @@ workspaceCommand.command("status").description("Mostrar status do workspace do c
|
|
|
20528
20861
|
const workspacePath = ws.workspacePath;
|
|
20529
20862
|
let configData = null;
|
|
20530
20863
|
try {
|
|
20531
|
-
const raw2 = await
|
|
20864
|
+
const raw2 = await fs15.readFile(path18.join(workspacePath, ".ana-config.json"), "utf-8");
|
|
20532
20865
|
configData = JSON.parse(raw2);
|
|
20533
20866
|
} catch {
|
|
20534
20867
|
configData = null;
|
|
20535
20868
|
}
|
|
20536
|
-
const exercisesDir =
|
|
20869
|
+
const exercisesDir = path18.join(workspacePath, "exercises");
|
|
20537
20870
|
let exerciseCount = 0;
|
|
20538
20871
|
try {
|
|
20539
|
-
const moduleDirs = await
|
|
20872
|
+
const moduleDirs = await fs15.readdir(exercisesDir);
|
|
20540
20873
|
for (const modDir of moduleDirs) {
|
|
20541
|
-
const modPath =
|
|
20542
|
-
const stat = await
|
|
20874
|
+
const modPath = path18.join(exercisesDir, modDir);
|
|
20875
|
+
const stat = await fs15.stat(modPath);
|
|
20543
20876
|
if (stat.isDirectory()) {
|
|
20544
|
-
const lessonDirs = await
|
|
20877
|
+
const lessonDirs = await fs15.readdir(modPath);
|
|
20545
20878
|
for (const lessonDir of lessonDirs) {
|
|
20546
|
-
const lessonPath2 =
|
|
20547
|
-
const lstat = await
|
|
20879
|
+
const lessonPath2 = path18.join(modPath, lessonDir);
|
|
20880
|
+
const lstat = await fs15.stat(lessonPath2);
|
|
20548
20881
|
if (lstat.isDirectory()) exerciseCount++;
|
|
20549
20882
|
}
|
|
20550
20883
|
}
|
|
20551
20884
|
}
|
|
20552
20885
|
} catch {
|
|
20553
20886
|
}
|
|
20554
|
-
const generatedDir =
|
|
20887
|
+
const generatedDir = path18.join(workspacePath, "generated");
|
|
20555
20888
|
let artifactCount = 0;
|
|
20556
20889
|
try {
|
|
20557
|
-
const files = await
|
|
20890
|
+
const files = await fs15.readdir(generatedDir);
|
|
20558
20891
|
artifactCount = files.length;
|
|
20559
20892
|
} catch {
|
|
20560
20893
|
}
|
|
20561
|
-
const diagramsDir =
|
|
20894
|
+
const diagramsDir = path18.join(workspacePath, "diagrams");
|
|
20562
20895
|
let diagramCount = 0;
|
|
20563
20896
|
try {
|
|
20564
|
-
const files = await
|
|
20897
|
+
const files = await fs15.readdir(diagramsDir);
|
|
20565
20898
|
diagramCount = files.length;
|
|
20566
20899
|
} catch {
|
|
20567
20900
|
}
|
|
@@ -20608,16 +20941,16 @@ workspaceCommand.command("status").description("Mostrar status do workspace do c
|
|
|
20608
20941
|
|
|
20609
20942
|
// src/commands/export.ts
|
|
20610
20943
|
init_dist();
|
|
20611
|
-
import { Command as
|
|
20944
|
+
import { Command as Command21 } from "commander";
|
|
20612
20945
|
init_guards();
|
|
20613
20946
|
init_course_state();
|
|
20614
20947
|
init_resolve();
|
|
20615
20948
|
init_errors();
|
|
20616
|
-
import
|
|
20949
|
+
import path19 from "node:path";
|
|
20617
20950
|
import os9 from "node:os";
|
|
20618
|
-
import
|
|
20951
|
+
import fs16 from "node:fs/promises";
|
|
20619
20952
|
var logger20 = createLogger("cli:export");
|
|
20620
|
-
var exportCommand = new
|
|
20953
|
+
var exportCommand = new Command21("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", path19.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
20621
20954
|
try {
|
|
20622
20955
|
const session = await requireSession();
|
|
20623
20956
|
const activeCourse = await requireActiveCourse();
|
|
@@ -20635,21 +20968,21 @@ var exportCommand = new Command19("export").description("Extrair exerc\xEDcio at
|
|
|
20635
20968
|
opts.path
|
|
20636
20969
|
);
|
|
20637
20970
|
if (ws.found && ws.source === "cwd" && ws.workspacePath) {
|
|
20638
|
-
const configPath =
|
|
20971
|
+
const configPath = path19.join(ws.workspacePath, ".ana-config.json");
|
|
20639
20972
|
let hasConfig = false;
|
|
20640
20973
|
try {
|
|
20641
|
-
await
|
|
20974
|
+
await fs16.access(configPath);
|
|
20642
20975
|
hasConfig = true;
|
|
20643
20976
|
} catch {
|
|
20644
20977
|
}
|
|
20645
20978
|
if (!hasConfig) {
|
|
20646
20979
|
const slug = courseSlug(activeCourse.courseTitle);
|
|
20647
20980
|
logger20.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
|
|
20648
|
-
await
|
|
20981
|
+
await fs16.mkdir(ws.workspacePath, { recursive: true });
|
|
20649
20982
|
for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
|
|
20650
|
-
await
|
|
20983
|
+
await fs16.mkdir(path19.join(ws.workspacePath, dir), { recursive: true });
|
|
20651
20984
|
}
|
|
20652
|
-
await
|
|
20985
|
+
await fs16.writeFile(
|
|
20653
20986
|
configPath,
|
|
20654
20987
|
JSON.stringify(
|
|
20655
20988
|
{
|
|
@@ -20667,7 +21000,7 @@ var exportCommand = new Command19("export").description("Extrair exerc\xEDcio at
|
|
|
20667
21000
|
"utf-8"
|
|
20668
21001
|
);
|
|
20669
21002
|
await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
|
|
20670
|
-
const isNamespaced = ws.workspacePath ===
|
|
21003
|
+
const isNamespaced = ws.workspacePath === path19.join(process.cwd(), ".tostudy");
|
|
20671
21004
|
process.stderr.write(
|
|
20672
21005
|
isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
|
|
20673
21006
|
` : `\u2728 Workspace inicializado nesta pasta.
|
|
@@ -20722,12 +21055,12 @@ init_guards();
|
|
|
20722
21055
|
init_course_state();
|
|
20723
21056
|
init_resolve();
|
|
20724
21057
|
init_errors();
|
|
20725
|
-
import { Command as
|
|
21058
|
+
import { Command as Command22 } from "commander";
|
|
20726
21059
|
import { execFile as execFile3 } from "node:child_process";
|
|
20727
|
-
import
|
|
21060
|
+
import path20 from "node:path";
|
|
20728
21061
|
import os10 from "node:os";
|
|
20729
21062
|
var logger21 = createLogger("cli:open");
|
|
20730
|
-
var openCommand = new
|
|
21063
|
+
var openCommand = new Command22("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path20.join(os10.homedir(), "study")).action(async (opts) => {
|
|
20731
21064
|
try {
|
|
20732
21065
|
const activeCourse = await requireActiveCourse();
|
|
20733
21066
|
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
@@ -20762,14 +21095,14 @@ var openCommand = new Command20("open").description("Abrir workspace do curso na
|
|
|
20762
21095
|
|
|
20763
21096
|
// src/commands/vault.ts
|
|
20764
21097
|
init_dist();
|
|
20765
|
-
import { Command as
|
|
21098
|
+
import { Command as Command23 } from "commander";
|
|
20766
21099
|
|
|
20767
21100
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/vault/generate-vault-files.ts
|
|
20768
21101
|
init_slug();
|
|
20769
21102
|
|
|
20770
21103
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/vault/write-vault.ts
|
|
20771
|
-
import
|
|
20772
|
-
import
|
|
21104
|
+
import fs17 from "node:fs/promises";
|
|
21105
|
+
import path21 from "node:path";
|
|
20773
21106
|
|
|
20774
21107
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/vault/types.ts
|
|
20775
21108
|
var VAULT_MARKER_FILENAME = ".ana-vault.json";
|
|
@@ -20778,20 +21111,20 @@ var VAULT_MARKER_VERSION = 1;
|
|
|
20778
21111
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/tostudy-core/src/vault/write-vault.ts
|
|
20779
21112
|
async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
|
|
20780
21113
|
for (const file2 of files) {
|
|
20781
|
-
const fullPath =
|
|
20782
|
-
await
|
|
20783
|
-
await
|
|
21114
|
+
const fullPath = path21.join(outputPath, file2.relativePath);
|
|
21115
|
+
await fs17.mkdir(path21.dirname(fullPath), { recursive: true });
|
|
21116
|
+
await fs17.writeFile(fullPath, file2.content, "utf-8");
|
|
20784
21117
|
}
|
|
20785
|
-
const vaultPath =
|
|
21118
|
+
const vaultPath = path21.join(outputPath, courseSlug2);
|
|
20786
21119
|
const marker = {
|
|
20787
21120
|
courseId,
|
|
20788
21121
|
courseSlug: courseSlug2,
|
|
20789
21122
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20790
21123
|
version: VAULT_MARKER_VERSION
|
|
20791
21124
|
};
|
|
20792
|
-
await
|
|
20793
|
-
await
|
|
20794
|
-
|
|
21125
|
+
await fs17.mkdir(vaultPath, { recursive: true });
|
|
21126
|
+
await fs17.writeFile(
|
|
21127
|
+
path21.join(vaultPath, VAULT_MARKER_FILENAME),
|
|
20795
21128
|
JSON.stringify(marker, null, 2),
|
|
20796
21129
|
"utf-8"
|
|
20797
21130
|
);
|
|
@@ -20804,12 +21137,12 @@ init_guards();
|
|
|
20804
21137
|
init_course_state();
|
|
20805
21138
|
init_resolve();
|
|
20806
21139
|
init_errors();
|
|
20807
|
-
import
|
|
21140
|
+
import path22 from "node:path";
|
|
20808
21141
|
import os11 from "node:os";
|
|
20809
|
-
import
|
|
21142
|
+
import fs18 from "node:fs/promises";
|
|
20810
21143
|
var logger22 = createLogger("cli:vault");
|
|
20811
|
-
var vaultCommand = new
|
|
20812
|
-
vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
21144
|
+
var vaultCommand = new Command23("vault").description("Gerenciar vault Obsidian do curso");
|
|
21145
|
+
vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path22.join(os11.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
20813
21146
|
try {
|
|
20814
21147
|
const session = await requireSession();
|
|
20815
21148
|
const activeCourse = await requireActiveCourse();
|
|
@@ -20890,7 +21223,7 @@ Para visualizar:
|
|
|
20890
21223
|
process.exit(1);
|
|
20891
21224
|
}
|
|
20892
21225
|
});
|
|
20893
|
-
vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
21226
|
+
vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path22.join(os11.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
20894
21227
|
try {
|
|
20895
21228
|
const session = await requireSession();
|
|
20896
21229
|
const activeCourse = await requireActiveCourse();
|
|
@@ -20916,8 +21249,8 @@ vaultCommand.command("sync").description("Sincronizar progresso do curso com o v
|
|
|
20916
21249
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
20917
21250
|
const deps = { data, logger: logger22 };
|
|
20918
21251
|
const progress = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
20919
|
-
const markerPath =
|
|
20920
|
-
const markerRaw = await
|
|
21252
|
+
const markerPath = path22.join(vaultPath, ".ana-vault.json");
|
|
21253
|
+
const markerRaw = await fs18.readFile(markerPath, "utf-8");
|
|
20921
21254
|
const marker = JSON.parse(markerRaw);
|
|
20922
21255
|
marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20923
21256
|
marker.progress = {
|
|
@@ -20925,10 +21258,10 @@ vaultCommand.command("sync").description("Sincronizar progresso do curso com o v
|
|
|
20925
21258
|
currentModule: progress.currentModule.title,
|
|
20926
21259
|
currentLesson: progress.currentLesson.title
|
|
20927
21260
|
};
|
|
20928
|
-
await
|
|
20929
|
-
const courseIndexPath =
|
|
21261
|
+
await fs18.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
21262
|
+
const courseIndexPath = path22.join(vaultPath, slug, "index.md");
|
|
20930
21263
|
try {
|
|
20931
|
-
let indexContent = await
|
|
21264
|
+
let indexContent = await fs18.readFile(courseIndexPath, "utf-8");
|
|
20932
21265
|
indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
|
|
20933
21266
|
const titleEnd = indexContent.indexOf("\n");
|
|
20934
21267
|
if (titleEnd !== -1) {
|
|
@@ -20939,7 +21272,7 @@ vaultCommand.command("sync").description("Sincronizar progresso do curso com o v
|
|
|
20939
21272
|
`;
|
|
20940
21273
|
indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
|
|
20941
21274
|
}
|
|
20942
|
-
await
|
|
21275
|
+
await fs18.writeFile(courseIndexPath, indexContent, "utf-8");
|
|
20943
21276
|
} catch {
|
|
20944
21277
|
}
|
|
20945
21278
|
const syncedAt = marker.lastSyncedAt;
|
|
@@ -20980,8 +21313,8 @@ init_dist();
|
|
|
20980
21313
|
init_formatter();
|
|
20981
21314
|
init_errors();
|
|
20982
21315
|
import os13 from "node:os";
|
|
20983
|
-
import
|
|
20984
|
-
import { Command as
|
|
21316
|
+
import path26 from "node:path";
|
|
21317
|
+
import { Command as Command25 } from "commander";
|
|
20985
21318
|
|
|
20986
21319
|
// ../../../../../Users/cleitonparis/www/pg/apps/cursos/packages/validators/src/authored-course.ts
|
|
20987
21320
|
import { createHash } from "node:crypto";
|
|
@@ -21254,12 +21587,12 @@ function assembleAuthoredCourse(manifest, files) {
|
|
|
21254
21587
|
init_guards();
|
|
21255
21588
|
|
|
21256
21589
|
// src/creator/state.ts
|
|
21257
|
-
import
|
|
21258
|
-
import
|
|
21259
|
-
var STATE_FILE =
|
|
21590
|
+
import fs19 from "node:fs/promises";
|
|
21591
|
+
import path23 from "node:path";
|
|
21592
|
+
var STATE_FILE = path23.join(".tostudy", "creator.json");
|
|
21260
21593
|
async function readCreatorState(root) {
|
|
21261
21594
|
try {
|
|
21262
|
-
const parsed = JSON.parse(await
|
|
21595
|
+
const parsed = JSON.parse(await fs19.readFile(path23.join(root, STATE_FILE), "utf-8"));
|
|
21263
21596
|
const hashes = parsed.baseHashes;
|
|
21264
21597
|
const wellFormed = Array.isArray(hashes) && hashes.every(
|
|
21265
21598
|
(mod) => Array.isArray(mod) && // wire shape of authoredCoursePushSchema.baseHashes; anything else is not a base we can send
|
|
@@ -21275,9 +21608,9 @@ async function readCreatorState(root) {
|
|
|
21275
21608
|
}
|
|
21276
21609
|
}
|
|
21277
21610
|
async function writeCreatorState(root, state) {
|
|
21278
|
-
const file2 =
|
|
21279
|
-
await
|
|
21280
|
-
await
|
|
21611
|
+
const file2 = path23.join(root, STATE_FILE);
|
|
21612
|
+
await fs19.mkdir(path23.dirname(file2), { recursive: true });
|
|
21613
|
+
await fs19.writeFile(file2, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
21281
21614
|
}
|
|
21282
21615
|
function changedSlots(baseHashes, remoteHashes, incomingHashes) {
|
|
21283
21616
|
const changed = [];
|
|
@@ -21294,9 +21627,9 @@ function changedSlots(baseHashes, remoteHashes, incomingHashes) {
|
|
|
21294
21627
|
// src/creator/workspace.ts
|
|
21295
21628
|
init_errors();
|
|
21296
21629
|
import { randomUUID } from "node:crypto";
|
|
21297
|
-
import
|
|
21630
|
+
import fs20 from "node:fs/promises";
|
|
21298
21631
|
import os12 from "node:os";
|
|
21299
|
-
import
|
|
21632
|
+
import path24 from "node:path";
|
|
21300
21633
|
var MANIFEST_FILE2 = "tostudy.json";
|
|
21301
21634
|
var STATE_GITIGNORE_ENTRY = ".tostudy/creator.json";
|
|
21302
21635
|
var CreatorWorkspaceError = class extends Error {
|
|
@@ -21310,10 +21643,10 @@ var CreatorWorkspaceError = class extends Error {
|
|
|
21310
21643
|
file;
|
|
21311
21644
|
};
|
|
21312
21645
|
function assertInsideRoot(root, relPath) {
|
|
21313
|
-
const abs =
|
|
21314
|
-
const back =
|
|
21315
|
-
const escapes = back === "" || back === ".." || back.startsWith(`..${
|
|
21316
|
-
if (
|
|
21646
|
+
const abs = path24.resolve(root, relPath);
|
|
21647
|
+
const back = path24.relative(path24.resolve(root), abs);
|
|
21648
|
+
const escapes = back === "" || back === ".." || back.startsWith(`..${path24.sep}`) || path24.isAbsolute(back);
|
|
21649
|
+
if (path24.isAbsolute(relPath) || escapes) {
|
|
21317
21650
|
throw new CreatorWorkspaceError(
|
|
21318
21651
|
"PATH_OUTSIDE_ROOT",
|
|
21319
21652
|
fillTemplate(getErrors().creator.pathOutsideRoot, { path: relPath }),
|
|
@@ -21323,9 +21656,9 @@ function assertInsideRoot(root, relPath) {
|
|
|
21323
21656
|
return abs;
|
|
21324
21657
|
}
|
|
21325
21658
|
function assertNotHome(dir, home = os12.homedir()) {
|
|
21326
|
-
const resolvedHome =
|
|
21327
|
-
const resolved =
|
|
21328
|
-
const prefix = resolved.endsWith(
|
|
21659
|
+
const resolvedHome = path24.resolve(home);
|
|
21660
|
+
const resolved = path24.resolve(dir);
|
|
21661
|
+
const prefix = resolved.endsWith(path24.sep) ? resolved : resolved + path24.sep;
|
|
21329
21662
|
if (resolved === resolvedHome || resolvedHome.startsWith(prefix)) {
|
|
21330
21663
|
throw new CreatorWorkspaceError("REFUSED_AT_HOME", getErrors().creator.refusedAtHome);
|
|
21331
21664
|
}
|
|
@@ -21344,7 +21677,7 @@ function manifestLessonPaths(manifestRaw) {
|
|
|
21344
21677
|
async function readManifest(root) {
|
|
21345
21678
|
let text2;
|
|
21346
21679
|
try {
|
|
21347
|
-
text2 = await
|
|
21680
|
+
text2 = await fs20.readFile(path24.join(root, MANIFEST_FILE2), "utf-8");
|
|
21348
21681
|
} catch {
|
|
21349
21682
|
throw new CreatorWorkspaceError(
|
|
21350
21683
|
"MANIFEST_MISSING",
|
|
@@ -21363,7 +21696,7 @@ async function readManifest(root) {
|
|
|
21363
21696
|
}
|
|
21364
21697
|
}
|
|
21365
21698
|
async function readCourseId(dir) {
|
|
21366
|
-
const manifest = await readManifest(
|
|
21699
|
+
const manifest = await readManifest(path24.resolve(dir));
|
|
21367
21700
|
const courseId = manifest?.courseId;
|
|
21368
21701
|
if (typeof courseId !== "string" || courseId.length === 0) {
|
|
21369
21702
|
throw new CreatorWorkspaceError(
|
|
@@ -21377,23 +21710,23 @@ async function readCourseId(dir) {
|
|
|
21377
21710
|
async function readInside(root, realRoot, relPath) {
|
|
21378
21711
|
let real;
|
|
21379
21712
|
try {
|
|
21380
|
-
real = await
|
|
21713
|
+
real = await fs20.realpath(path24.resolve(root, relPath));
|
|
21381
21714
|
} catch {
|
|
21382
21715
|
return null;
|
|
21383
21716
|
}
|
|
21384
|
-
if (!real.startsWith(realRoot +
|
|
21717
|
+
if (!real.startsWith(realRoot + path24.sep)) {
|
|
21385
21718
|
throw new CreatorWorkspaceError(
|
|
21386
21719
|
"PATH_OUTSIDE_ROOT",
|
|
21387
21720
|
fillTemplate(getErrors().creator.pathOutsideRoot, { path: relPath }),
|
|
21388
21721
|
relPath
|
|
21389
21722
|
);
|
|
21390
21723
|
}
|
|
21391
|
-
return
|
|
21724
|
+
return fs20.readFile(real, "utf-8");
|
|
21392
21725
|
}
|
|
21393
21726
|
async function readWorkspace(dir) {
|
|
21394
|
-
const root =
|
|
21727
|
+
const root = path24.resolve(dir);
|
|
21395
21728
|
const manifestRaw = await readManifest(root);
|
|
21396
|
-
const realRoot = await
|
|
21729
|
+
const realRoot = await fs20.realpath(root);
|
|
21397
21730
|
const files = {};
|
|
21398
21731
|
for (const relPath of manifestLessonPaths(manifestRaw).flat()) {
|
|
21399
21732
|
assertInsideRoot(root, relPath);
|
|
@@ -21405,18 +21738,18 @@ async function readWorkspace(dir) {
|
|
|
21405
21738
|
return { manifestRaw, files, root };
|
|
21406
21739
|
}
|
|
21407
21740
|
async function ensureGitignore(root) {
|
|
21408
|
-
const file2 =
|
|
21409
|
-
const current = await
|
|
21741
|
+
const file2 = path24.join(root, ".gitignore");
|
|
21742
|
+
const current = await fs20.readFile(file2, "utf-8").catch(() => "");
|
|
21410
21743
|
if (current.split(/\r?\n/).some((line) => line.trim() === STATE_GITIGNORE_ENTRY)) return false;
|
|
21411
21744
|
const separator = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
21412
|
-
await
|
|
21745
|
+
await fs20.writeFile(file2, `${current}${separator}${STATE_GITIGNORE_ENTRY}
|
|
21413
21746
|
`, "utf-8");
|
|
21414
21747
|
return true;
|
|
21415
21748
|
}
|
|
21416
21749
|
async function scaffoldWorkspace(dir, home) {
|
|
21417
|
-
const root =
|
|
21750
|
+
const root = path24.resolve(dir);
|
|
21418
21751
|
assertNotHome(root, home);
|
|
21419
|
-
await
|
|
21752
|
+
await fs20.mkdir(root, { recursive: true });
|
|
21420
21753
|
const files = [];
|
|
21421
21754
|
let courseId = randomUUID();
|
|
21422
21755
|
let manifestCreated = true;
|
|
@@ -21427,11 +21760,11 @@ async function scaffoldWorkspace(dir, home) {
|
|
|
21427
21760
|
modules: [{ title: "", description: "", objectives: [], lessons: [] }]
|
|
21428
21761
|
};
|
|
21429
21762
|
try {
|
|
21430
|
-
await
|
|
21763
|
+
await fs20.writeFile(path24.join(root, MANIFEST_FILE2), JSON.stringify(manifest, null, 2) + "\n", {
|
|
21431
21764
|
encoding: "utf-8",
|
|
21432
21765
|
flag: "wx"
|
|
21433
21766
|
});
|
|
21434
|
-
await
|
|
21767
|
+
await fs20.mkdir(path24.join(root, "modules", "01"), { recursive: true });
|
|
21435
21768
|
files.push(MANIFEST_FILE2, "modules/01/");
|
|
21436
21769
|
} catch (err) {
|
|
21437
21770
|
if (err.code !== "EEXIST") throw err;
|
|
@@ -21493,8 +21826,8 @@ async function validateWorkspace(dir, opts = {}) {
|
|
|
21493
21826
|
}
|
|
21494
21827
|
|
|
21495
21828
|
// src/creator/pull.ts
|
|
21496
|
-
import
|
|
21497
|
-
import
|
|
21829
|
+
import fs21 from "node:fs/promises";
|
|
21830
|
+
import path25 from "node:path";
|
|
21498
21831
|
init_errors();
|
|
21499
21832
|
function stripToSchema(schema, value) {
|
|
21500
21833
|
const current = structuredClone(value);
|
|
@@ -21519,17 +21852,17 @@ function stripToSchema(schema, value) {
|
|
|
21519
21852
|
}
|
|
21520
21853
|
async function exists(file2) {
|
|
21521
21854
|
try {
|
|
21522
|
-
await
|
|
21855
|
+
await fs21.access(file2);
|
|
21523
21856
|
return true;
|
|
21524
21857
|
} catch {
|
|
21525
21858
|
return false;
|
|
21526
21859
|
}
|
|
21527
21860
|
}
|
|
21528
21861
|
async function assertWritableInside(realRoot, root, rel) {
|
|
21529
|
-
const abs =
|
|
21862
|
+
const abs = path25.resolve(root, rel);
|
|
21530
21863
|
let target;
|
|
21531
21864
|
try {
|
|
21532
|
-
target = await
|
|
21865
|
+
target = await fs21.lstat(abs);
|
|
21533
21866
|
} catch {
|
|
21534
21867
|
target = void 0;
|
|
21535
21868
|
}
|
|
@@ -21540,10 +21873,10 @@ async function assertWritableInside(realRoot, root, rel) {
|
|
|
21540
21873
|
rel
|
|
21541
21874
|
);
|
|
21542
21875
|
}
|
|
21543
|
-
let dir =
|
|
21544
|
-
while (!await exists(dir)) dir =
|
|
21545
|
-
const realDir = await
|
|
21546
|
-
if (realDir !== realRoot && !realDir.startsWith(realRoot +
|
|
21876
|
+
let dir = path25.dirname(abs);
|
|
21877
|
+
while (!await exists(dir)) dir = path25.dirname(dir);
|
|
21878
|
+
const realDir = await fs21.realpath(dir);
|
|
21879
|
+
if (realDir !== realRoot && !realDir.startsWith(realRoot + path25.sep)) {
|
|
21547
21880
|
throw new CreatorWorkspaceError(
|
|
21548
21881
|
"PATH_OUTSIDE_ROOT",
|
|
21549
21882
|
fillTemplate(getErrors().creator.pathOutsideRoot, { path: rel }),
|
|
@@ -21552,7 +21885,7 @@ async function assertWritableInside(realRoot, root, rel) {
|
|
|
21552
21885
|
}
|
|
21553
21886
|
}
|
|
21554
21887
|
async function applyPull(root, manifestRaw, remoteModules) {
|
|
21555
|
-
const realRoot = await
|
|
21888
|
+
const realRoot = await fs21.realpath(root);
|
|
21556
21889
|
const local = manifestLessonPaths(manifestRaw);
|
|
21557
21890
|
const localShape = local.map((mod) => mod.length);
|
|
21558
21891
|
const remoteShape = remoteModules.map((mod) => mod.lessons.length);
|
|
@@ -21571,7 +21904,7 @@ async function applyPull(root, manifestRaw, remoteModules) {
|
|
|
21571
21904
|
assertInsideRoot(root, rel);
|
|
21572
21905
|
const contract = stripped.data;
|
|
21573
21906
|
const sidecarRel = sidecarPath(rel);
|
|
21574
|
-
if (await exists(
|
|
21907
|
+
if (await exists(path25.resolve(root, sidecarRel))) {
|
|
21575
21908
|
const { teachingContent, ...rest } = contract;
|
|
21576
21909
|
planned.push({ rel: sidecarRel, text: String(teachingContent ?? "") });
|
|
21577
21910
|
planned.push({ rel, text: `${JSON.stringify(rest, null, 2)}
|
|
@@ -21587,9 +21920,9 @@ async function applyPull(root, manifestRaw, remoteModules) {
|
|
|
21587
21920
|
await assertWritableInside(realRoot, root, file2.rel);
|
|
21588
21921
|
}
|
|
21589
21922
|
for (const file2 of planned) {
|
|
21590
|
-
const abs =
|
|
21591
|
-
await
|
|
21592
|
-
await
|
|
21923
|
+
const abs = path25.resolve(root, file2.rel);
|
|
21924
|
+
await fs21.mkdir(path25.dirname(abs), { recursive: true });
|
|
21925
|
+
await fs21.writeFile(abs, file2.text, "utf-8");
|
|
21593
21926
|
}
|
|
21594
21927
|
return { kind: "written", files: planned.map((file2) => file2.rel) };
|
|
21595
21928
|
}
|
|
@@ -21923,7 +22256,7 @@ function installCreatorSkill(input2) {
|
|
|
21923
22256
|
// src/commands/brief-open.ts
|
|
21924
22257
|
init_guards();
|
|
21925
22258
|
init_formatter();
|
|
21926
|
-
import { Command as
|
|
22259
|
+
import { Command as Command24 } from "commander";
|
|
21927
22260
|
import { execFile as execFile4 } from "node:child_process";
|
|
21928
22261
|
import { platform } from "node:process";
|
|
21929
22262
|
var BRIEF_URL = "https://tostudy.ai/student/settings/learner-brief";
|
|
@@ -21948,7 +22281,7 @@ function openUrl(url2, options = {}) {
|
|
|
21948
22281
|
}
|
|
21949
22282
|
});
|
|
21950
22283
|
}
|
|
21951
|
-
var briefOpenCommand = new
|
|
22284
|
+
var briefOpenCommand = new Command24("brief-open").description("Open the learner brief editor in your web browser").action(async () => {
|
|
21952
22285
|
await requireSession();
|
|
21953
22286
|
output(`Abrindo ${BRIEF_URL} no navegador...`, { json: false });
|
|
21954
22287
|
openUrl(BRIEF_URL);
|
|
@@ -21956,7 +22289,7 @@ var briefOpenCommand = new Command22("brief-open").description("Open the learner
|
|
|
21956
22289
|
|
|
21957
22290
|
// src/commands/creator.ts
|
|
21958
22291
|
var logger23 = createLogger("cli:creator");
|
|
21959
|
-
var creatorCommand = new
|
|
22292
|
+
var creatorCommand = new Command25("creator").description(
|
|
21960
22293
|
"Author a course with your own AI agent: init, validate, push, pull, audit, status, open"
|
|
21961
22294
|
);
|
|
21962
22295
|
function fail(err, opts, context) {
|
|
@@ -21970,7 +22303,7 @@ function fail(err, opts, context) {
|
|
|
21970
22303
|
creatorCommand.command("init").description("Create an authoring workspace (tostudy.json, modules/01) and install the skill").argument("[dir]", "Workspace folder", ".").option("--all-runtimes", "Install the skill for every supported runtime, detected or not").option("--json", "Output structured JSON").action(async (dir, opts) => {
|
|
21971
22304
|
try {
|
|
21972
22305
|
const home = os13.homedir();
|
|
21973
|
-
const scaffold = await scaffoldWorkspace(
|
|
22306
|
+
const scaffold = await scaffoldWorkspace(path26.resolve(process.cwd(), dir), home);
|
|
21974
22307
|
const kept = [];
|
|
21975
22308
|
const installed = installCreatorSkill({
|
|
21976
22309
|
cwd: scaffold.root,
|
|
@@ -22253,8 +22586,8 @@ creatorCommand.command("open").description("Open the course in the creator porta
|
|
|
22253
22586
|
init_guards();
|
|
22254
22587
|
init_course_state();
|
|
22255
22588
|
init_user_profile();
|
|
22256
|
-
import { Command as
|
|
22257
|
-
var profileCommand = new
|
|
22589
|
+
import { Command as Command26 } from "commander";
|
|
22590
|
+
var profileCommand = new Command26("profile").description("Show your learner profile for the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
22258
22591
|
const activeCourse = await requireActiveCourse();
|
|
22259
22592
|
const onboarding = await getCourseOnboardingState(activeCourse.courseId);
|
|
22260
22593
|
const profile = onboarding?.learnerProfile ?? await getUserProfile();
|
|
@@ -22316,10 +22649,10 @@ var profileCommand = new Command24("profile").description("Show your learner pro
|
|
|
22316
22649
|
init_dist();
|
|
22317
22650
|
init_guards();
|
|
22318
22651
|
init_formatter();
|
|
22319
|
-
import { Command as
|
|
22652
|
+
import { Command as Command27 } from "commander";
|
|
22320
22653
|
init_workspace_state();
|
|
22321
22654
|
var logger24 = createLogger("cli:sync");
|
|
22322
|
-
var syncCommand = new
|
|
22655
|
+
var syncCommand = new Command27("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").option(
|
|
22323
22656
|
"--all-runtimes",
|
|
22324
22657
|
"Write instruction files for every supported runtime, even undetected ones"
|
|
22325
22658
|
).option(
|
|
@@ -22391,10 +22724,10 @@ var syncCommand = new Command25("sync").description("Regenerate instruction file
|
|
|
22391
22724
|
// src/commands/brief.ts
|
|
22392
22725
|
init_dist();
|
|
22393
22726
|
init_guards();
|
|
22394
|
-
import { Command as
|
|
22727
|
+
import { Command as Command28 } from "commander";
|
|
22395
22728
|
init_formatter();
|
|
22396
22729
|
var logger25 = createLogger("cli:brief");
|
|
22397
|
-
var briefCommand = new
|
|
22730
|
+
var briefCommand = new Command28("brief").description("Show your base learner brief (T1) status and content").option("--json", "Output structured JSON").action(async (opts) => {
|
|
22398
22731
|
try {
|
|
22399
22732
|
const session = await requireSession();
|
|
22400
22733
|
const cached2 = await readBriefCache();
|
|
@@ -22435,10 +22768,10 @@ var briefCommand = new Command26("brief").description("Show your base learner br
|
|
|
22435
22768
|
// src/commands/brief-create.ts
|
|
22436
22769
|
init_dist();
|
|
22437
22770
|
init_guards();
|
|
22438
|
-
import { Command as
|
|
22771
|
+
import { Command as Command29 } from "commander";
|
|
22439
22772
|
init_formatter();
|
|
22440
22773
|
var logger26 = createLogger("cli:brief-create");
|
|
22441
|
-
var briefCreateCommand = new
|
|
22774
|
+
var briefCreateCommand = new Command29("brief-create").description("Create your base learner brief via interactive prompts (T1 bootstrap)").action(async () => {
|
|
22442
22775
|
try {
|
|
22443
22776
|
const session = await requireSession();
|
|
22444
22777
|
const answers = await collectBootstrapAnswers({ userName: session.userName });
|
|
@@ -22471,22 +22804,22 @@ var briefCreateCommand = new Command27("brief-create").description("Create your
|
|
|
22471
22804
|
init_dist();
|
|
22472
22805
|
init_guards();
|
|
22473
22806
|
init_workspace_state();
|
|
22474
|
-
import
|
|
22475
|
-
import { Command as
|
|
22807
|
+
import fs23 from "node:fs";
|
|
22808
|
+
import { Command as Command30 } from "commander";
|
|
22476
22809
|
|
|
22477
22810
|
// src/sessions/storage.ts
|
|
22478
22811
|
init_dist();
|
|
22479
|
-
import
|
|
22480
|
-
import
|
|
22812
|
+
import fs22 from "node:fs";
|
|
22813
|
+
import path27 from "node:path";
|
|
22481
22814
|
var logger27 = createLogger("cli:sessions");
|
|
22482
22815
|
function sessionsDir(workspacePath) {
|
|
22483
|
-
return
|
|
22816
|
+
return path27.join(workspacePath, ".tostudy", "sessions");
|
|
22484
22817
|
}
|
|
22485
22818
|
async function saveModuleSummary(workspacePath, input2) {
|
|
22486
22819
|
const dir = sessionsDir(workspacePath);
|
|
22487
|
-
|
|
22820
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
22488
22821
|
const filename = `module-${input2.moduleId}-summary.md`;
|
|
22489
|
-
const filePath =
|
|
22822
|
+
const filePath = path27.join(dir, filename);
|
|
22490
22823
|
const header = [
|
|
22491
22824
|
"---",
|
|
22492
22825
|
`moduleId: ${input2.moduleId}`,
|
|
@@ -22495,17 +22828,17 @@ async function saveModuleSummary(workspacePath, input2) {
|
|
|
22495
22828
|
"---",
|
|
22496
22829
|
""
|
|
22497
22830
|
].join("\n");
|
|
22498
|
-
|
|
22831
|
+
fs22.writeFileSync(filePath, header + input2.summary, { mode: 384 });
|
|
22499
22832
|
logger27.debug("Module summary saved", { moduleId: input2.moduleId, path: filePath });
|
|
22500
22833
|
return filePath;
|
|
22501
22834
|
}
|
|
22502
22835
|
async function loadSessionContext(workspacePath) {
|
|
22503
22836
|
const dir = sessionsDir(workspacePath);
|
|
22504
|
-
if (!
|
|
22505
|
-
const files =
|
|
22837
|
+
if (!fs22.existsSync(dir)) return { moduleSummaries: [] };
|
|
22838
|
+
const files = fs22.readdirSync(dir).filter((f) => f.startsWith("module-") && f.endsWith("-summary.md")).sort();
|
|
22506
22839
|
const summaries = [];
|
|
22507
22840
|
for (const file2 of files) {
|
|
22508
|
-
const content =
|
|
22841
|
+
const content = fs22.readFileSync(path27.join(dir, file2), "utf-8");
|
|
22509
22842
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
22510
22843
|
if (!frontmatterMatch) continue;
|
|
22511
22844
|
const meta3 = frontmatterMatch[1];
|
|
@@ -22521,7 +22854,7 @@ async function loadSessionContext(workspacePath) {
|
|
|
22521
22854
|
// src/commands/compact.ts
|
|
22522
22855
|
init_formatter();
|
|
22523
22856
|
var logger28 = createLogger("cli:compact");
|
|
22524
|
-
var compactCommand = new
|
|
22857
|
+
var compactCommand = new Command30("compact").description("Save a module study summary (LLM-generated, from stdin)").option("--module-id <id>", "Module ID").option("--module-title <title>", "Module title").option("--json", "Output structured JSON").action(async (opts) => {
|
|
22525
22858
|
try {
|
|
22526
22859
|
const activeCourse = await requireActiveCourse();
|
|
22527
22860
|
const ws = await findWorkspaceState();
|
|
@@ -22529,7 +22862,7 @@ var compactCommand = new Command28("compact").description("Save a module study s
|
|
|
22529
22862
|
if (opts.json) jsonError("no_workspace");
|
|
22530
22863
|
error("Nenhum workspace encontrado.");
|
|
22531
22864
|
}
|
|
22532
|
-
const summary =
|
|
22865
|
+
const summary = fs23.readFileSync("/dev/stdin", "utf-8").trim();
|
|
22533
22866
|
if (!summary) {
|
|
22534
22867
|
if (opts.json) jsonError("empty_summary", { message: "Summary vazio" });
|
|
22535
22868
|
error("Summary vazio. Envie o conte\xFAdo via stdin.");
|
|
@@ -22559,12 +22892,12 @@ var compactCommand = new Command28("compact").description("Save a module study s
|
|
|
22559
22892
|
// src/commands/context.ts
|
|
22560
22893
|
init_dist();
|
|
22561
22894
|
init_workspace_state();
|
|
22562
|
-
import { Command as
|
|
22895
|
+
import { Command as Command31 } from "commander";
|
|
22563
22896
|
init_course_state();
|
|
22564
22897
|
init_formatter();
|
|
22565
22898
|
init_errors();
|
|
22566
22899
|
var logger29 = createLogger("cli:context");
|
|
22567
|
-
var contextCommand = new
|
|
22900
|
+
var contextCommand = new Command31("context").description("Load session context (workspace state + module summaries) for LLM consumption").option("--json", "Output structured JSON").action(async (opts) => {
|
|
22568
22901
|
try {
|
|
22569
22902
|
const ws = await findWorkspaceState();
|
|
22570
22903
|
if (!ws) {
|
|
@@ -22616,13 +22949,13 @@ var contextCommand = new Command29("context").description("Load session context
|
|
|
22616
22949
|
|
|
22617
22950
|
// src/commands/memory.ts
|
|
22618
22951
|
init_dist();
|
|
22619
|
-
import { Command as
|
|
22952
|
+
import { Command as Command32 } from "commander";
|
|
22620
22953
|
init_workspace_state();
|
|
22621
22954
|
init_guards();
|
|
22622
22955
|
init_formatter();
|
|
22623
22956
|
init_errors();
|
|
22624
22957
|
var logger30 = createLogger("cli:memory");
|
|
22625
|
-
var memoryCommand = new
|
|
22958
|
+
var memoryCommand = new Command32("memory").description("Load accumulated student memory (learning profile + recent lessons) for the tutor").option("--json", "Output structured JSON").action(async (opts) => {
|
|
22626
22959
|
try {
|
|
22627
22960
|
const ws = await findWorkspaceState();
|
|
22628
22961
|
if (!ws) {
|
|
@@ -22653,14 +22986,14 @@ var memoryCommand = new Command30("memory").description("Load accumulated studen
|
|
|
22653
22986
|
|
|
22654
22987
|
// src/commands/insight.ts
|
|
22655
22988
|
init_dist();
|
|
22656
|
-
import { Command as
|
|
22989
|
+
import { Command as Command33 } from "commander";
|
|
22657
22990
|
init_workspace_state();
|
|
22658
22991
|
init_guards();
|
|
22659
22992
|
init_formatter();
|
|
22660
22993
|
init_errors();
|
|
22661
22994
|
var logger31 = createLogger("cli:insight");
|
|
22662
22995
|
var VALID_TYPES = ["difficulty", "breakthrough", "question"];
|
|
22663
|
-
var insightCommand = new
|
|
22996
|
+
var insightCommand = new Command33("insight").description(
|
|
22664
22997
|
"Persist a student cognitive insight (difficulty | breakthrough | question) into course memory"
|
|
22665
22998
|
).argument("<type>", "difficulty | breakthrough | question").argument("<content>", 'Short description, e.g. "confunde async/await com promises"').option("--module-id <id>", "Module the insight relates to").option("--json", "Output structured JSON").action(async (type, content, opts) => {
|
|
22666
22999
|
try {
|
|
@@ -22708,7 +23041,7 @@ var insightCommand = new Command31("insight").description(
|
|
|
22708
23041
|
|
|
22709
23042
|
// src/commands/level.ts
|
|
22710
23043
|
init_dist();
|
|
22711
|
-
import { Command as
|
|
23044
|
+
import { Command as Command34 } from "commander";
|
|
22712
23045
|
init_guards();
|
|
22713
23046
|
init_formatter();
|
|
22714
23047
|
var logger32 = createLogger("cli:level");
|
|
@@ -22723,7 +23056,7 @@ var LEVEL_LABELS2 = {
|
|
|
22723
23056
|
function isExerciseLevel2(value) {
|
|
22724
23057
|
return ALL_LEVELS.includes(value);
|
|
22725
23058
|
}
|
|
22726
|
-
var levelCommand = new
|
|
23059
|
+
var levelCommand = new Command34("level").description("Show or set the exercise scaffolding level for the active course").argument("[level]", "Target level: L0, L1, L2, L3, or L4").option("--json", "Output structured JSON").action(async (rawLevel, opts) => {
|
|
22727
23060
|
try {
|
|
22728
23061
|
const session = await requireSession();
|
|
22729
23062
|
const activeCourse = await requireActiveCourse();
|
|
@@ -22788,10 +23121,10 @@ init_dist();
|
|
|
22788
23121
|
init_guards();
|
|
22789
23122
|
init_formatter();
|
|
22790
23123
|
init_errors();
|
|
22791
|
-
import { Command as
|
|
23124
|
+
import { Command as Command35 } from "commander";
|
|
22792
23125
|
var logger33 = createLogger("cli:theory");
|
|
22793
23126
|
var EXERCISE_LEVELS2 = ["L0", "L1", "L2", "L3", "L4"];
|
|
22794
|
-
var theoryCommand = new
|
|
23127
|
+
var theoryCommand = new Command35("theory").description("Request more theory on the current lesson (escape hatch)").option("--focus <text>", "Optional focus area in PT-BR (max 500 chars)").option("--level <L0|L1|L2|L3|L4>", "Override current exercise level (otherwise auto-detect)").option("--lesson <uuid>", "Override lesson ID (defaults to current workspace lesson)").option("--json", "Output structured JSON").action(async (opts) => {
|
|
22795
23128
|
try {
|
|
22796
23129
|
const session = await requireSession();
|
|
22797
23130
|
const active = await requireActiveCourse();
|
|
@@ -22886,7 +23219,7 @@ var theoryCommand = new Command33("theory").description("Request more theory on
|
|
|
22886
23219
|
// src/commands/export-grades.ts
|
|
22887
23220
|
init_dist();
|
|
22888
23221
|
init_guards();
|
|
22889
|
-
import { Command as
|
|
23222
|
+
import { Command as Command36 } from "commander";
|
|
22890
23223
|
|
|
22891
23224
|
// src/formatters/grades.ts
|
|
22892
23225
|
function formatGradesJson(attempts) {
|
|
@@ -23254,7 +23587,7 @@ async function runExportGrades(opts, deps = defaultDeps5) {
|
|
|
23254
23587
|
if (!rendered.endsWith("\n")) rendered += "\n";
|
|
23255
23588
|
deps.stdoutWrite(rendered);
|
|
23256
23589
|
}
|
|
23257
|
-
var exportGradesCommand = new
|
|
23590
|
+
var exportGradesCommand = new Command36("export-grades").description("Exporta o hist\xF3rico de valida\xE7\xF5es (notas) do curso ativo").option(
|
|
23258
23591
|
"--course <slug>",
|
|
23259
23592
|
"Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, exporta o curso ativo."
|
|
23260
23593
|
).option("--format <json|csv|md>", "Formato de sa\xEDda (default: json)", "json").addHelpText(
|
|
@@ -23278,7 +23611,7 @@ C\xF3digos de sa\xEDda:
|
|
|
23278
23611
|
|
|
23279
23612
|
// src/commands/grades.ts
|
|
23280
23613
|
init_dist();
|
|
23281
|
-
import { Command as
|
|
23614
|
+
import { Command as Command37 } from "commander";
|
|
23282
23615
|
init_guards();
|
|
23283
23616
|
var logger35 = createLogger("cli:grades");
|
|
23284
23617
|
var defaultDeps6 = {
|
|
@@ -23434,7 +23767,7 @@ async function runGrades(opts, deps = defaultDeps6) {
|
|
|
23434
23767
|
);
|
|
23435
23768
|
deps.stdoutWrite(formatGradesAllEnrollmentsTable(summaries) + "\n");
|
|
23436
23769
|
}
|
|
23437
|
-
var gradesCommand = new
|
|
23770
|
+
var gradesCommand = new Command37("grades").description("Mostra um resumo das notas (todas as matr\xEDculas ou um curso espec\xEDfico)").option(
|
|
23438
23771
|
"--course <slug>",
|
|
23439
23772
|
"Slug do curso (gerado a partir do t\xEDtulo). Sem a flag, lista todos os cursos."
|
|
23440
23773
|
).addHelpText(
|
|
@@ -23458,7 +23791,7 @@ C\xF3digos de sa\xEDda:
|
|
|
23458
23791
|
// src/commands/review.ts
|
|
23459
23792
|
init_dist();
|
|
23460
23793
|
init_guards();
|
|
23461
|
-
import { Command as
|
|
23794
|
+
import { Command as Command38 } from "commander";
|
|
23462
23795
|
import { createInterface } from "node:readline/promises";
|
|
23463
23796
|
import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
|
|
23464
23797
|
var logger36 = createLogger("cli:review");
|
|
@@ -23608,7 +23941,7 @@ async function runReview(slug, opts, deps = defaultDeps7) {
|
|
|
23608
23941
|
}) + "\n"
|
|
23609
23942
|
);
|
|
23610
23943
|
}
|
|
23611
|
-
var reviewCommand = new
|
|
23944
|
+
var reviewCommand = new Command38("review").description("Revisa a \xFAltima tentativa de valida\xE7\xE3o de uma li\xE7\xE3o").argument("<slug>", "Slug do curso (gerado a partir do t\xEDtulo)").option("--json", "Sa\xEDda em JSON (sem prompt interativo)").addHelpText(
|
|
23612
23945
|
"after",
|
|
23613
23946
|
`
|
|
23614
23947
|
Exemplos:
|
|
@@ -23631,12 +23964,12 @@ C\xF3digos de sa\xEDda:
|
|
|
23631
23964
|
|
|
23632
23965
|
// src/cli.ts
|
|
23633
23966
|
function createProgram() {
|
|
23634
|
-
const program = new
|
|
23967
|
+
const program = new Command39();
|
|
23635
23968
|
program.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").option("--locale <code>", "Output locale (pt-BR | en-US); defaults to LANG env then pt-BR").addHelpText(
|
|
23636
23969
|
"before",
|
|
23637
23970
|
[
|
|
23638
23971
|
"",
|
|
23639
|
-
" Come\xE7ando: login \u2192 courses \u2192 select <n> \u2192 start",
|
|
23972
|
+
" Come\xE7ando: login \u2192 suggest \u2192 enroll <id> \u2192 courses \u2192 select <n> \u2192 start",
|
|
23640
23973
|
" Estudando: next \xB7 lesson \xB7 hint \xB7 validate \xB7 theory \xB7 progress",
|
|
23641
23974
|
" Com d\xFAvida: tostudy menu (mostra onde voc\xEA est\xE1 e o que fazer)",
|
|
23642
23975
|
""
|
|
@@ -23648,6 +23981,8 @@ function createProgram() {
|
|
|
23648
23981
|
program.addCommand(loginCommand);
|
|
23649
23982
|
program.addCommand(logoutCommand);
|
|
23650
23983
|
program.addCommand(coursesCommand);
|
|
23984
|
+
program.addCommand(suggestCommand);
|
|
23985
|
+
program.addCommand(enrollCommand);
|
|
23651
23986
|
program.addCommand(selectCommand);
|
|
23652
23987
|
program.addCommand(progressCommand);
|
|
23653
23988
|
program.addCommand(startCommand);
|
|
@@ -23683,12 +24018,12 @@ function createProgram() {
|
|
|
23683
24018
|
|
|
23684
24019
|
// src/auto-updater.ts
|
|
23685
24020
|
init_config_dir();
|
|
23686
|
-
import
|
|
23687
|
-
import
|
|
24021
|
+
import fs24 from "node:fs";
|
|
24022
|
+
import path28 from "node:path";
|
|
23688
24023
|
import { spawn as spawn2 } from "node:child_process";
|
|
23689
24024
|
var PACKAGE_NAME2 = "@tostudy-ai/cli";
|
|
23690
24025
|
function detectManager(entryRealPath) {
|
|
23691
|
-
const p = entryRealPath.split(
|
|
24026
|
+
const p = entryRealPath.split(path28.sep).join("/");
|
|
23692
24027
|
if (p.includes("/pnpm/")) return { cmd: "pnpm", args: ["add", "-g", `${PACKAGE_NAME2}@latest`] };
|
|
23693
24028
|
if (p.includes("/.bun/")) return { cmd: "bun", args: ["add", "-g", `${PACKAGE_NAME2}@latest`] };
|
|
23694
24029
|
if (p.includes(`/node_modules/${PACKAGE_NAME2}/`))
|
|
@@ -23696,10 +24031,10 @@ function detectManager(entryRealPath) {
|
|
|
23696
24031
|
return null;
|
|
23697
24032
|
}
|
|
23698
24033
|
function installDir(entryRealPath) {
|
|
23699
|
-
const normalized = entryRealPath.split(
|
|
24034
|
+
const normalized = entryRealPath.split(path28.sep).join("/");
|
|
23700
24035
|
const marker = `/node_modules/${PACKAGE_NAME2}/`;
|
|
23701
24036
|
const idx = normalized.indexOf(marker);
|
|
23702
|
-
if (idx === -1) return
|
|
24037
|
+
if (idx === -1) return path28.dirname(entryRealPath);
|
|
23703
24038
|
return entryRealPath.slice(0, idx + marker.length - 1);
|
|
23704
24039
|
}
|
|
23705
24040
|
function maybeAutoUpdate(currentVersion, opts = {}) {
|
|
@@ -23715,10 +24050,10 @@ function maybeAutoUpdate(currentVersion, opts = {}) {
|
|
|
23715
24050
|
if (cache.attemptedVersion === cache.latest) return;
|
|
23716
24051
|
const entryPath = opts.entryPath ?? argv[1];
|
|
23717
24052
|
if (!entryPath) return;
|
|
23718
|
-
const real =
|
|
24053
|
+
const real = fs24.realpathSync(entryPath);
|
|
23719
24054
|
const mgr = detectManager(real);
|
|
23720
24055
|
if (!mgr) return;
|
|
23721
|
-
|
|
24056
|
+
fs24.accessSync(installDir(real), fs24.constants.W_OK);
|
|
23722
24057
|
writeCache({ ...cache, attemptedVersion: cache.latest }, configDir);
|
|
23723
24058
|
process.stderr.write(
|
|
23724
24059
|
`
|